Skip to content

[TypeChecker] SE-0326: Enable multi-statement closure inference by default #39989

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
3f78f4d
[CSFix] Diagnose multiple solutions with missing closure param as un-…
xedin Oct 25, 2021
990d8c8
[Sema] DebuggerTestingTransform: avoid re-using AST nodes in generate…
xedin Oct 25, 2021
3927f56
[ConstraintSystem] Warn about discarded expressions found in multi-st…
xedin Oct 26, 2021
8303319
[TypeChecker] Fix constraint solver to respect `LeaveClosureBodyUnche…
xedin Oct 27, 2021
ceb6d7e
[IDE] Skip complex closures while checking pattern bindings
xedin Oct 27, 2021
3555299
[TypeChecker] Teach declaration type-checking about `LeaveClosureBody…
xedin Oct 28, 2021
9509eef
[TypeChecker/Constness] Diagnostics: Walk into multi-statement closur…
xedin Oct 28, 2021
8d1aeac
[CSClosure] Warn about `defer` being the last element in the closure …
xedin Oct 28, 2021
7b0f68c
[Diagnostics] Apply "unhandled throw" diagnostic for `for-in` loop in…
xedin Oct 29, 2021
8dddc89
[PreCheck] Avoid patterns that appear in closures when multi-statemen…
xedin Oct 29, 2021
3072a68
[Sema/CodeCompletion] Leave complex closure bodies unchecked
xedin Oct 29, 2021
8b90154
[MiscDiagnostics] Introduce a base class for diagnostic walkers
xedin Oct 30, 2021
aa3b886
[ConstraintSystem] Attempt conjunction before closure result or gener…
xedin Oct 30, 2021
67d87e1
[Tests] NFC: Adjust all the test-cases improved by multi-statement in…
xedin Oct 30, 2021
d5a82c5
[TypeChecker] NFC: Fix merge conflict in `typeCheckBinding`
xedin Nov 1, 2021
49c3668
[BuilderTransform] Don't skip multi-statement closures during pre-check
xedin Nov 3, 2021
b231cde
[TypeChecker] SE-0326: Enable multi-statement closure inference by de…
xedin Nov 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/swift/Basic/LangOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,7 @@ namespace swift {

/// Enable experimental support for type inference through multi-statement
/// closures.
bool EnableMultiStatementClosureInference = false;
bool EnableMultiStatementClosureInference = true;

/// See \ref FrontendOptions.PrintFullConvention
bool PrintFullConvention = false;
Expand Down
8 changes: 8 additions & 0 deletions include/swift/Sema/CSBindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,14 @@ class BindingSet {

TypeVariableType *getTypeVariable() const { return Info.TypeVar; }

/// Check whether this binding set belongs to a type variable
/// that represents a result type of a closure.
bool forClosureResult() const;

/// Check whether this binding set belongs to a type variable
/// that represents a generic parameter.
bool forGenericParameter() const;

bool canBeNil() const;

/// If this type variable doesn't have any viable bindings, or
Expand Down
4 changes: 4 additions & 0 deletions include/swift/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -2282,6 +2282,10 @@ class SpecifyClosureParameterType final : public ConstraintFix {

bool diagnose(const Solution &solution, bool asNote = false) const override;

bool diagnoseForAmbiguity(CommonFixesArray commonFixes) const override {
return diagnose(*commonFixes.front().first);
}

static SpecifyClosureParameterType *create(ConstraintSystem &cs,
ConstraintLocator *locator);

Expand Down
15 changes: 12 additions & 3 deletions include/swift/Sema/Constraint.h
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,8 @@ class Constraint final : public llvm::ilist_node<Constraint>,
ASTNode Element;
/// Contextual information associated with the element (if any).
ContextualTypeInfo Context;
/// Identifies whether result of this node is unused.
bool IsDiscarded;
} ClosureElement;
};

Expand Down Expand Up @@ -489,7 +491,7 @@ class Constraint final : public llvm::ilist_node<Constraint>,
SmallPtrSetImpl<TypeVariableType *> &typeVars);

/// Construct a closure body element constraint.
Constraint(ASTNode node, ContextualTypeInfo context,
Constraint(ASTNode node, ContextualTypeInfo context, bool isDiscarded,
ConstraintLocator *locator,
SmallPtrSetImpl<TypeVariableType *> &typeVars);

Expand Down Expand Up @@ -579,12 +581,14 @@ class Constraint final : public llvm::ilist_node<Constraint>,

static Constraint *createClosureBodyElement(ConstraintSystem &cs,
ASTNode node,
ConstraintLocator *locator);
ConstraintLocator *locator,
bool isDiscarded = false);

static Constraint *createClosureBodyElement(ConstraintSystem &cs,
ASTNode node,
ContextualTypeInfo context,
ConstraintLocator *locator);
ConstraintLocator *locator,
bool isDiscarded = false);

/// Determine the kind of constraint.
ConstraintKind getKind() const { return Kind; }
Expand Down Expand Up @@ -850,6 +854,11 @@ class Constraint final : public llvm::ilist_node<Constraint>,
return ClosureElement.Context;
}

bool isDiscardedElement() const {
assert(Kind == ConstraintKind::ClosureBodyElement);
return ClosureElement.IsDiscarded;
}

/// For an applicable function constraint, retrieve the trailing closure
/// matching rule.
Optional<TrailingClosureMatching> getTrailingClosureMatching() const;
Expand Down
29 changes: 14 additions & 15 deletions include/swift/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -4845,8 +4845,8 @@ class ConstraintSystem {
/// Simplify a closure body element constraint by generating required
/// constraints to represent the given element in constraint system.
SolutionKind simplifyClosureBodyElementConstraint(
ASTNode element, ContextualTypeInfo context, TypeMatchOptions flags,
ConstraintLocatorBuilder locator);
ASTNode element, ContextualTypeInfo context, bool isDiscarded,
TypeMatchOptions flags, ConstraintLocatorBuilder locator);

public: // FIXME: Public for use by static functions.
/// Simplify a conversion constraint with a fix applied to it.
Expand Down Expand Up @@ -5005,7 +5005,8 @@ class ConstraintSystem {
/// \param replaceInvalidRefsWithErrors Indicates whether it's allowed
/// to replace any discovered invalid member references with `ErrorExpr`.
static bool preCheckExpression(Expr *&expr, DeclContext *dc,
bool replaceInvalidRefsWithErrors);
bool replaceInvalidRefsWithErrors,
bool leaveClosureBodiesUnchecked);

/// Solve the system of constraints generated from provided target.
///
Expand Down Expand Up @@ -5236,6 +5237,16 @@ class ConstraintSystem {
/// imported from C/ObjectiveC.
bool isArgumentOfImportedDecl(ConstraintLocatorBuilder locator);

/// Check whether given closure should participate in inference e.g.
/// if it's a single-expression closure - it always does, but
/// multi-statement closures require special flags.
bool participatesInInference(ClosureExpr *closure) const;

/// Visit each subexpression that will be part of the constraint system
/// of the given expression, including those in closure bodies that will be
/// part of the constraint system.
void forEachExpr(Expr *expr, llvm::function_ref<Expr *(Expr *)> callback);

SWIFT_DEBUG_DUMP;
SWIFT_DEBUG_DUMPER(dump(Expr *));

Expand Down Expand Up @@ -6059,18 +6070,6 @@ BraceStmt *applyResultBuilderTransform(
constraints::SolutionApplicationTarget)>
rewriteTarget);

/// Determine whether the given closure expression should be type-checked
/// within the context of its enclosing expression. Otherwise, it will be
/// separately type-checked once its enclosing expression has determined all
/// of the parameter and result types without looking at the body.
bool shouldTypeCheckInEnclosingExpression(ClosureExpr *expr);

/// Visit each subexpression that will be part of the constraint system
/// of the given expression, including those in closure bodies that will be
/// part of the constraint system.
void forEachExprInConstraintSystem(
Expr *expr, llvm::function_ref<Expr *(Expr *)> callback);

/// Whether the given parameter requires an argument.
bool parameterRequiresArgument(
ArrayRef<AnyFunctionType::Param> params,
Expand Down
3 changes: 2 additions & 1 deletion include/swift/Sema/IDETypeChecking.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ namespace swift {
struct PrintOptions;

/// Typecheck binding initializer at \p bindingIndex.
void typeCheckPatternBinding(PatternBindingDecl *PBD, unsigned bindingIndex);
void typeCheckPatternBinding(PatternBindingDecl *PBD, unsigned bindingIndex,
bool leaveClosureBodiesUnchecked);

/// Check if T1 is convertible to T2.
///
Expand Down
3 changes: 2 additions & 1 deletion lib/IDE/ExprContextAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ void swift::ide::typeCheckContextAt(DeclContext *DC, SourceLoc Loc) {
[](VarDecl *VD) { (void)VD->getInterfaceType(); });
if (PBD->getInit(i)) {
if (!PBD->isInitializerChecked(i))
typeCheckPatternBinding(PBD, i);
typeCheckPatternBinding(PBD, i,
/*LeaveClosureBodyUnchecked=*/true);
}
}
} else if (auto *defaultArg = dyn_cast<DefaultArgumentInitializer>(DC)) {
Expand Down
4 changes: 3 additions & 1 deletion lib/Sema/BuilderTransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1932,7 +1932,9 @@ class PreCheckResultBuilderApplication : public ASTWalker {
DiagnosticTransaction transaction(diagEngine);

HasError |= ConstraintSystem::preCheckExpression(
E, DC, /*replaceInvalidRefsWithErrors=*/true);
E, DC, /*replaceInvalidRefsWithErrors=*/true,
/*leaveClosureBodiesUnchecked=*/false);

HasError |= transaction.hasErrors();

if (!HasError)
Expand Down
3 changes: 1 addition & 2 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4107,8 +4107,7 @@ namespace {
if (expr->isLiteralInit()) {
auto *literalInit = expr->getSubExpr();
if (auto *call = dyn_cast<CallExpr>(literalInit)) {
forEachExprInConstraintSystem(call->getFn(),
[&](Expr *subExpr) -> Expr * {
cs.forEachExpr(call->getFn(), [&](Expr *subExpr) -> Expr * {
auto *TE = dyn_cast<TypeExpr>(subExpr);
if (!TE)
return subExpr;
Expand Down
8 changes: 8 additions & 0 deletions lib/Sema/CSBindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ using namespace swift;
using namespace constraints;
using namespace inference;

bool BindingSet::forClosureResult() const {
return Info.TypeVar->getImpl().isClosureResultType();
}

bool BindingSet::forGenericParameter() const {
return bool(Info.TypeVar->getImpl().getGenericParameter());
}

bool BindingSet::canBeNil() const {
auto &ctx = CS.getASTContext();
return Literals.count(
Expand Down
82 changes: 55 additions & 27 deletions lib/Sema/CSClosure.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
//
//===----------------------------------------------------------------------===//

#include "MiscDiagnostics.h"
#include "TypeChecker.h"
#include "swift/Sema/ConstraintSystem.h"

Expand Down Expand Up @@ -162,8 +163,8 @@ static bool isViableElement(ASTNode element) {
return true;
}

using ElementInfo =
std::tuple<ASTNode, ContextualTypeInfo, ConstraintLocator *>;
using ElementInfo = std::tuple<ASTNode, ContextualTypeInfo,
/*isDiscarded=*/bool, ConstraintLocator *>;

static void createConjunction(ConstraintSystem &cs,
ArrayRef<ElementInfo> elements,
Expand All @@ -190,7 +191,8 @@ static void createConjunction(ConstraintSystem &cs,
for (const auto &entry : elements) {
ASTNode element = std::get<0>(entry);
ContextualTypeInfo context = std::get<1>(entry);
ConstraintLocator *elementLoc = std::get<2>(entry);
bool isDiscarded = std::get<2>(entry);
ConstraintLocator *elementLoc = std::get<3>(entry);

if (!isViableElement(element))
continue;
Expand All @@ -201,8 +203,8 @@ static void createConjunction(ConstraintSystem &cs,
if (isIsolated)
element.walk(paramCollector);

constraints.push_back(
Constraint::createClosureBodyElement(cs, element, context, elementLoc));
constraints.push_back(Constraint::createClosureBodyElement(
cs, element, context, elementLoc, isDiscarded));
}

// It's possible that there are no viable elements in the body,
Expand All @@ -220,8 +222,9 @@ static void createConjunction(ConstraintSystem &cs,
}

ElementInfo makeElement(ASTNode node, ConstraintLocator *locator,
ContextualTypeInfo context = ContextualTypeInfo()) {
return std::make_tuple(node, context, locator);
ContextualTypeInfo context = ContextualTypeInfo(),
bool isDiscarded = false) {
return std::make_tuple(node, context, isDiscarded, locator);
}

static ProtocolDecl *getSequenceProtocol(ASTContext &ctx, SourceLoc loc,
Expand Down Expand Up @@ -751,6 +754,8 @@ class ClosureConstraintGenerator

void visitBraceStmt(BraceStmt *braceStmt) {
if (isSupportedMultiStatementClosure()) {
auto &ctx = cs.getASTContext();

if (isChildOf(StmtKind::Case)) {
auto *caseStmt = cast<CaseStmt>(
locator->castLastElementTo<LocatorPathElt::ClosureBodyElement>()
Expand All @@ -765,10 +770,15 @@ class ClosureConstraintGenerator

SmallVector<ElementInfo, 4> elements;
for (auto element : braceStmt->getElements()) {
bool isDiscarded =
element.is<Expr *>() &&
(!ctx.LangOpts.Playground && !ctx.LangOpts.DebuggerSupport);

elements.push_back(makeElement(
element,
cs.getConstraintLocator(
locator, LocatorPathElt::ClosureBodyElement(element))));
locator, LocatorPathElt::ClosureBodyElement(element)),
/*contextualInfo=*/{}, isDiscarded));
}

createConjunction(cs, elements, locator);
Expand Down Expand Up @@ -845,7 +855,7 @@ class ClosureConstraintGenerator

bool isSupportedMultiStatementClosure() const {
return !closure->hasSingleExpressionBody() &&
shouldTypeCheckInEnclosingExpression(closure);
cs.participatesInInference(closure);
}

#define UNSUPPORTED_STMT(STMT) void visit##STMT##Stmt(STMT##Stmt *) { \
Expand Down Expand Up @@ -876,7 +886,7 @@ class ClosureConstraintGenerator
bool ConstraintSystem::generateConstraints(ClosureExpr *closure) {
auto &ctx = closure->getASTContext();

if (shouldTypeCheckInEnclosingExpression(closure)) {
if (participatesInInference(closure)) {
ClosureConstraintGenerator generator(*this, closure,
getConstraintLocator(closure));
generator.visit(closure->getBody());
Expand Down Expand Up @@ -925,28 +935,22 @@ bool isConditionOfStmt(ConstraintLocatorBuilder locator) {

ConstraintSystem::SolutionKind
ConstraintSystem::simplifyClosureBodyElementConstraint(
ASTNode element, ContextualTypeInfo context, TypeMatchOptions flags,
ConstraintLocatorBuilder locator) {
ASTNode element, ContextualTypeInfo context, bool isDiscarded,
TypeMatchOptions flags, ConstraintLocatorBuilder locator) {
auto *closure = castToExpr<ClosureExpr>(locator.getAnchor());

ClosureConstraintGenerator generator(*this, closure,
getConstraintLocator(locator));

if (auto *expr = element.dyn_cast<Expr *>()) {
if (context.purpose != CTP_Unused) {
SolutionApplicationTarget target(expr, closure, context.purpose,
context.getType(),
/*isDiscarded=*/false);

if (generateConstraints(target, FreeTypeVariableBinding::Disallow))
return SolutionKind::Error;

setSolutionApplicationTarget(expr, target);
return SolutionKind::Solved;
}
SolutionApplicationTarget target(expr, closure, context.purpose,
context.getType(), isDiscarded);

if (!generateConstraints(expr, closure, /*isInputExpression=*/false))
if (generateConstraints(target, FreeTypeVariableBinding::Disallow))
return SolutionKind::Error;

setSolutionApplicationTarget(expr, target);
return SolutionKind::Solved;
} else if (auto *stmt = element.dyn_cast<Stmt *>()) {
generator.visit(stmt);
} else if (auto *cond = element.dyn_cast<StmtCondition *>()) {
Expand Down Expand Up @@ -1160,12 +1164,17 @@ class ClosureConstraintApplication

auto forEachTarget =
rewriteTarget(*cs.getSolutionApplicationTarget(forEachStmt));

if (!forEachTarget)
hadError = true;

auto body = visit(forEachStmt->getBody()).get<Stmt *>();
forEachStmt->setBody(cast<BraceStmt>(body));

// Check to see if the sequence expr is throwing (in async context),
// if so require the stmt to have a `try`.
hadError |= diagnoseUnhandledThrowsInAsyncContext(closure, forEachStmt);

return forEachStmt;
}

Expand Down Expand Up @@ -1241,13 +1250,32 @@ class ClosureConstraintApplication
}

ASTNode visitBraceStmt(BraceStmt *braceStmt) {
auto &cs = solution.getConstraintSystem();

// Diagnose defer statement being last one in block.
if (!braceStmt->empty()) {
if (auto stmt = braceStmt->getLastElement().dyn_cast<Stmt *>()) {
if (auto deferStmt = dyn_cast<DeferStmt>(stmt)) {
auto &diags = closure->getASTContext().Diags;
diags
.diagnose(deferStmt->getStartLoc(), diag::defer_stmt_at_block_end)
.fixItReplace(deferStmt->getStartLoc(), "do");
}
}
}

for (auto &node : braceStmt->getElements()) {
if (auto expr = node.dyn_cast<Expr *>()) {
// Rewrite the expression.
if (auto rewrittenExpr = rewriteExpr(expr))
node = rewrittenExpr;
else
auto target = *cs.getSolutionApplicationTarget(expr);
if (auto rewrittenTarget = rewriteTarget(target)) {
node = rewrittenTarget->getAsExpr();

if (target.isDiscardedExpr())
TypeChecker::checkIgnoredExpr(castToExpr(node));
} else {
hadError = true;
}
} else if (auto stmt = node.dyn_cast<Stmt *>()) {
node = visit(stmt);
} else {
Expand Down
Loading