Skip to content

Commit b03b356

Browse files
authored
Merge pull request swiftlang#28880 from xedin/port-multi-stmt-closure-diags
[Diagnostics] Diagnose inability to infer (complex) closure return type
2 parents ec0a2ca + a7bb827 commit b03b356

File tree

17 files changed

+108
-181
lines changed

17 files changed

+108
-181
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -302,11 +302,11 @@ ERROR(cannot_invoke_closure_type,none,
302302
ERROR(cannot_infer_closure_type,none,
303303
"unable to infer closure type in the current context", ())
304304
ERROR(cannot_infer_closure_result_type,none,
305-
"unable to infer complex closure return type; "
306-
"add explicit type to disambiguate", ())
307-
FIXIT(insert_closure_return_type,
308-
"%select{| () }1-> %0 %select{|in }1",
309-
(Type, bool))
305+
"unable to infer%select{ complex|}0 closure return type; "
306+
"add explicit type to disambiguate", (bool))
307+
FIXIT(insert_closure_return_type_placeholder,
308+
"%select{| () }0-> <#Result#> %select{|in }0",
309+
(bool))
310310

311311
ERROR(incorrect_explicit_closure_result,none,
312312
"declared closure result %0 is incompatible with contextual type %1",

lib/Sema/CSBindings.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,11 @@ bool TypeVariableBinding::attempt(ConstraintSystem &cs) const {
999999
cs.getConstraintLocator(dstLocator->getAnchor(), path.drop_back()));
10001000
if (cs.recordFix(fix))
10011001
return true;
1002+
} else if (TypeVar->getImpl().isClosureResultType()) {
1003+
auto *fix = SpecifyClosureReturnType::create(
1004+
cs, TypeVar->getImpl().getLocator());
1005+
if (cs.recordFix(fix))
1006+
return true;
10021007
}
10031008
}
10041009
}

lib/Sema/CSDiag.cpp

Lines changed: 0 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,6 @@ class FailureDiagnosis :public ASTVisitor<FailureDiagnosis, /*exprresult*/bool>{
220220
std::pair<Type, ContextualTypePurpose>
221221
validateContextualType(Type contextualType, ContextualTypePurpose CTP);
222222

223-
/// Check the specified closure to see if it is a multi-statement closure with
224-
/// an uninferred type. If so, diagnose the problem with an error and return
225-
/// true.
226-
bool diagnoseAmbiguousMultiStatementClosure(ClosureExpr *closure);
227-
228223
/// Given a result of name lookup that had no viable results, diagnose the
229224
/// unviable ones.
230225
void diagnoseUnviableLookupResults(MemberLookupResult &lookupResults,
@@ -2694,138 +2689,6 @@ FailureDiagnosis::validateContextualType(Type contextualType,
26942689
return {contextualType, CTP};
26952690
}
26962691

2697-
/// Check the specified closure to see if it is a multi-statement closure with
2698-
/// an uninferred type. If so, diagnose the problem with an error and return
2699-
/// true.
2700-
bool FailureDiagnosis::
2701-
diagnoseAmbiguousMultiStatementClosure(ClosureExpr *closure) {
2702-
if (closure->hasSingleExpressionBody() ||
2703-
closure->hasExplicitResultType())
2704-
return false;
2705-
2706-
auto closureType = CS.getType(closure)->getAs<AnyFunctionType>();
2707-
if (!closureType ||
2708-
!(closureType->getResult()->hasUnresolvedType() ||
2709-
closureType->getResult()->hasTypeVariable()))
2710-
return false;
2711-
2712-
// Okay, we have a multi-statement closure expr that has no inferred result,
2713-
// type, in the context of a larger expression. The user probably expected
2714-
// the compiler to infer the result type of the closure from the body of the
2715-
// closure, which Swift doesn't do for multi-statement closures. Try to be
2716-
// helpful by digging into the body of the closure, looking for a return
2717-
// statement, and inferring the result type from it. If we can figure that
2718-
// out, we can produce a fixit hint.
2719-
class ReturnStmtFinder : public ASTWalker {
2720-
SmallVectorImpl<ReturnStmt*> &returnStmts;
2721-
public:
2722-
ReturnStmtFinder(SmallVectorImpl<ReturnStmt*> &returnStmts)
2723-
: returnStmts(returnStmts) {}
2724-
2725-
// Walk through statements, so we find returns hiding in if/else blocks etc.
2726-
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
2727-
// Keep track of any return statements we find.
2728-
if (auto RS = dyn_cast<ReturnStmt>(S))
2729-
returnStmts.push_back(RS);
2730-
return { true, S };
2731-
}
2732-
2733-
// Don't walk into anything else, since they cannot contain statements
2734-
// that can return from the current closure.
2735-
std::pair<bool, Expr *> walkToExprPre(Expr *E) override {
2736-
return { false, E };
2737-
}
2738-
std::pair<bool, Pattern*> walkToPatternPre(Pattern *P) override {
2739-
return { false, P };
2740-
}
2741-
bool walkToDeclPre(Decl *D) override { return false; }
2742-
bool walkToTypeLocPre(TypeLoc &TL) override { return false; }
2743-
bool walkToTypeReprPre(TypeRepr *T) override { return false; }
2744-
bool walkToParameterListPre(ParameterList *PL) override { return false; }
2745-
};
2746-
2747-
SmallVector<ReturnStmt*, 4> Returns;
2748-
closure->getBody()->walk(ReturnStmtFinder(Returns));
2749-
2750-
// If we found a return statement inside of the closure expression, then go
2751-
// ahead and type check the body to see if we can determine a type.
2752-
for (auto RS : Returns) {
2753-
llvm::SaveAndRestore<DeclContext *> SavedDC(CS.DC, closure);
2754-
2755-
// Otherwise, we're ok to type check the subexpr.
2756-
Type resultType;
2757-
if (RS->hasResult()) {
2758-
auto resultExpr = RS->getResult();
2759-
ConcreteDeclRef decl = nullptr;
2760-
2761-
// If return expression uses closure parameters, which have/are
2762-
// type variables, such means that we won't be able to
2763-
// type-check result correctly and, unfortunately,
2764-
// we are going to leak type variables from the parent
2765-
// constraint system through declaration types.
2766-
bool hasUnresolvedParams = false;
2767-
resultExpr->forEachChildExpr([&](Expr *childExpr) -> Expr *{
2768-
if (auto DRE = dyn_cast<DeclRefExpr>(childExpr)) {
2769-
if (auto param = dyn_cast<ParamDecl>(DRE->getDecl())) {
2770-
auto paramType =
2771-
param->hasInterfaceType() ? param->getType() : Type();
2772-
if (!paramType || paramType->hasTypeVariable()) {
2773-
hasUnresolvedParams = true;
2774-
return nullptr;
2775-
}
2776-
}
2777-
}
2778-
return childExpr;
2779-
});
2780-
2781-
if (hasUnresolvedParams)
2782-
continue;
2783-
2784-
ConstraintSystem::preCheckExpression(resultExpr, CS.DC, &CS);
2785-
2786-
// Obtain type of the result expression without applying solutions,
2787-
// because otherwise this might result in leaking of type variables,
2788-
// since we are not resetting result statement and if expression is
2789-
// successfully type-checked its type cleanup is going to be disabled
2790-
// (we are allowing unresolved types), and as a side-effect it might
2791-
// also be transformed e.g. OverloadedDeclRefExpr -> DeclRefExpr.
2792-
auto type = TypeChecker::getTypeOfExpressionWithoutApplying(
2793-
resultExpr, CS.DC, decl, FreeTypeVariableBinding::UnresolvedType);
2794-
if (type)
2795-
resultType = type->getRValueType();
2796-
}
2797-
2798-
// If we found a type, presuppose it was the intended result and insert a
2799-
// fixit hint.
2800-
if (resultType && !isUnresolvedOrTypeVarType(resultType)) {
2801-
// If there is a location for an 'in' token, then the argument list was
2802-
// specified somehow but no return type was. Insert a "-> ReturnType "
2803-
// before the in token.
2804-
if (closure->getInLoc().isValid()) {
2805-
diagnose(closure->getLoc(), diag::cannot_infer_closure_result_type)
2806-
.fixItInsert(closure->getInLoc(), diag::insert_closure_return_type,
2807-
resultType, /*argListSpecified*/ false);
2808-
return true;
2809-
}
2810-
2811-
// Otherwise, the closure must take zero arguments. We know this
2812-
// because the if one or more argument is specified, a multi-statement
2813-
// closure *must* name them, or explicitly ignore them with "_ in".
2814-
//
2815-
// As such, we insert " () -> ReturnType in " right after the '{' that
2816-
// starts the closure body.
2817-
diagnose(closure->getLoc(), diag::cannot_infer_closure_result_type)
2818-
.fixItInsertAfter(closure->getBody()->getLBraceLoc(),
2819-
diag::insert_closure_return_type, resultType,
2820-
/*argListSpecified*/ true);
2821-
return true;
2822-
}
2823-
}
2824-
2825-
diagnose(closure->getLoc(), diag::cannot_infer_closure_result_type);
2826-
return true;
2827-
}
2828-
28292692
/// Emit an ambiguity diagnostic about the specified expression.
28302693
void FailureDiagnosis::diagnoseAmbiguity(Expr *E) {
28312694
if (auto *assignment = dyn_cast<AssignExpr>(E)) {
@@ -2855,11 +2718,6 @@ void FailureDiagnosis::diagnoseAmbiguity(Expr *E) {
28552718
// Unresolved/Anonymous ClosureExprs are common enough that we should give
28562719
// them tailored diagnostics.
28572720
if (auto CE = dyn_cast<ClosureExpr>(E->getValueProvidingExpr())) {
2858-
// If this is a multi-statement closure with no explicit result type, emit
2859-
// a note to clue the developer in.
2860-
if (diagnoseAmbiguousMultiStatementClosure(CE))
2861-
return;
2862-
28632721
diagnose(E->getLoc(), diag::cannot_infer_closure_type)
28642722
.highlight(E->getSourceRange());
28652723
return;
@@ -2901,22 +2759,6 @@ void FailureDiagnosis::diagnoseAmbiguity(Expr *E) {
29012759
return;
29022760
}
29032761

2904-
// A very common cause of this diagnostic is a situation where a closure expr
2905-
// has no inferred type, due to being a multiline closure. Check to see if
2906-
// this is the case and (if so), speculatively diagnose that as the problem.
2907-
bool didDiagnose = false;
2908-
E->forEachChildExpr([&](Expr *subExpr) -> Expr*{
2909-
auto closure = dyn_cast<ClosureExpr>(subExpr);
2910-
if (!didDiagnose && closure)
2911-
didDiagnose = diagnoseAmbiguousMultiStatementClosure(closure);
2912-
2913-
return subExpr;
2914-
});
2915-
2916-
if (didDiagnose) return;
2917-
2918-
2919-
29202762
// Attempt to re-type-check the entire expression, allowing ambiguity, but
29212763
// ignoring a contextual type.
29222764
if (expr == E) {

lib/Sema/CSDiagnostics.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5915,3 +5915,31 @@ bool MissingContextualBaseInMemberRefFailure::diagnoseAsError() {
59155915
.highlight(anchor->getSourceRange());
59165916
return true;
59175917
}
5918+
5919+
bool UnableToInferClosureReturnType::diagnoseAsError() {
5920+
auto *closure = cast<ClosureExpr>(getRawAnchor());
5921+
5922+
auto diagnostic =
5923+
emitDiagnostic(closure->getLoc(),
5924+
diag::cannot_infer_closure_result_type,
5925+
closure->hasSingleExpressionBody());
5926+
5927+
// If there is a location for an 'in' token, then the argument list was
5928+
// specified somehow but no return type was. Insert a "-> ReturnType "
5929+
// before the in token.
5930+
if (closure->getInLoc().isValid()) {
5931+
diagnostic.fixItInsert(closure->getInLoc(),
5932+
diag::insert_closure_return_type_placeholder,
5933+
/*argListSpecified=*/false);
5934+
} else if (closure->getParameters()->size() == 0) {
5935+
// Otherwise, the closure must take zero arguments.
5936+
//
5937+
// As such, we insert " () -> ReturnType in " right after the '{' that
5938+
// starts the closure body.
5939+
diagnostic.fixItInsertAfter(closure->getBody()->getLBraceLoc(),
5940+
diag::insert_closure_return_type_placeholder,
5941+
/*argListSpecified=*/true);
5942+
}
5943+
5944+
return true;
5945+
}

lib/Sema/CSDiagnostics.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,6 +1912,15 @@ class MissingContextualBaseInMemberRefFailure final : public FailureDiagnostic {
19121912
bool diagnoseAsError();
19131913
};
19141914

1915+
class UnableToInferClosureReturnType final : public FailureDiagnostic {
1916+
public:
1917+
UnableToInferClosureReturnType(ConstraintSystem &cs,
1918+
ConstraintLocator *locator)
1919+
: FailureDiagnostic(cs, locator) {}
1920+
1921+
bool diagnoseAsError();
1922+
};
1923+
19151924
} // end namespace constraints
19161925
} // end namespace swift
19171926

lib/Sema/CSFix.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,3 +1155,15 @@ SpecifyBaseTypeForContextualMember *SpecifyBaseTypeForContextualMember::create(
11551155
return new (cs.getAllocator())
11561156
SpecifyBaseTypeForContextualMember(cs, member, locator);
11571157
}
1158+
1159+
bool SpecifyClosureReturnType::diagnose(bool asNote) const {
1160+
auto &cs = getConstraintSystem();
1161+
UnableToInferClosureReturnType failure(cs, getLocator());
1162+
return failure.diagnose(asNote);
1163+
}
1164+
1165+
SpecifyClosureReturnType *
1166+
SpecifyClosureReturnType::create(ConstraintSystem &cs,
1167+
ConstraintLocator *locator) {
1168+
return new (cs.getAllocator()) SpecifyClosureReturnType(cs, locator);
1169+
}

lib/Sema/CSFix.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ enum class FixKind : uint8_t {
231231
/// Base type in reference to the contextual member e.g. `.foo` couldn't be
232232
/// inferred and has to be specified explicitly.
233233
SpecifyBaseTypeForContextualMember,
234+
235+
/// Closure return type has to be explicitly specified because it can't be
236+
/// inferred in current context e.g. because it's a multi-statement closure.
237+
SpecifyClosureReturnType,
234238
};
235239

236240
class ConstraintFix {
@@ -1605,6 +1609,21 @@ class SpecifyBaseTypeForContextualMember final : public ConstraintFix {
16051609
create(ConstraintSystem &cs, DeclNameRef member, ConstraintLocator *locator);
16061610
};
16071611

1612+
class SpecifyClosureReturnType final : public ConstraintFix {
1613+
SpecifyClosureReturnType(ConstraintSystem &cs, ConstraintLocator *locator)
1614+
: ConstraintFix(cs, FixKind::SpecifyClosureReturnType, locator) {}
1615+
1616+
public:
1617+
std::string getName() const {
1618+
return "specify closure return type";
1619+
}
1620+
1621+
bool diagnose(bool asNote = false) const;
1622+
1623+
static SpecifyClosureReturnType *create(ConstraintSystem &cs,
1624+
ConstraintLocator *locator);
1625+
};
1626+
16081627
} // end namespace constraints
16091628
} // end namespace swift
16101629

lib/Sema/CSGen.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2430,7 +2430,8 @@ namespace {
24302430
CS.getConstraintLocator(expr, ConstraintLocator::ClosureResult);
24312431

24322432
if (expr->hasEmptyBody()) {
2433-
resultTy = CS.createTypeVariable(locator, 0);
2433+
resultTy = CS.createTypeVariable(
2434+
locator, expr->hasSingleExpressionBody() ? 0 : TVO_CanBindToHole);
24342435

24352436
// Closures with empty bodies should be inferred to return
24362437
// ().
@@ -2442,7 +2443,8 @@ namespace {
24422443
} else {
24432444
// If no return type was specified, create a fresh type
24442445
// variable for it.
2445-
resultTy = CS.createTypeVariable(locator, 0);
2446+
resultTy = CS.createTypeVariable(
2447+
locator, expr->hasSingleExpressionBody() ? 0 : TVO_CanBindToHole);
24462448

24472449
if (closureHasNoResult(expr)) {
24482450
// Allow it to default to () if there are no return statements.

lib/Sema/CSSimplify.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8594,6 +8594,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
85948594
case FixKind::AllowMutatingMemberOnRValueBase:
85958595
case FixKind::AllowTupleSplatForSingleParameter:
85968596
case FixKind::AllowInvalidUseOfTrailingClosure:
8597+
case FixKind::SpecifyClosureReturnType:
85978598
llvm_unreachable("handled elsewhere");
85988599
}
85998600

lib/Sema/ConstraintSystem.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,9 @@ class TypeVariableType::Implementation {
297297
/// Retrieve the generic parameter opened by this type variable.
298298
GenericTypeParamType *getGenericParameter() const;
299299

300+
/// Determine whether this type variable represents a closure result type.
301+
bool isClosureResultType() const;
302+
300303
/// Retrieve the representative of the equivalence class to which this
301304
/// type variable belongs.
302305
///

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ TypeVariableType::Implementation::getGenericParameter() const {
8383
return locator ? locator->getGenericParameter() : nullptr;
8484
}
8585

86+
bool TypeVariableType::Implementation::isClosureResultType() const {
87+
if (!(locator && locator->getAnchor()))
88+
return false;
89+
90+
return isa<ClosureExpr>(locator->getAnchor()) &&
91+
locator->isLastElement<LocatorPathElt::ClosureResult>();
92+
}
93+
8694
void *operator new(size_t bytes, ConstraintSystem& cs,
8795
size_t alignment) {
8896
return cs.getAllocator().Allocate(bytes, alignment);

0 commit comments

Comments
 (0)