Skip to content

Commit 3433727

Browse files
authored
Merge pull request #26661 from DougGregor/one-way-constraints-5.1
[5.1] One-way constraints for function builders
2 parents 9fca165 + cfcca5a commit 3433727

38 files changed

+1647
-498
lines changed

include/swift/AST/Expr.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5353,6 +5353,33 @@ class KeyPathDotExpr : public Expr {
53535353
}
53545354
};
53555355

5356+
/// Expression node that effects a "one-way" constraint in
5357+
/// the constraint system, allowing type information to flow from the
5358+
/// subexpression outward but not the other way.
5359+
///
5360+
/// One-way expressions are generally implicit and synthetic, introduced by
5361+
/// the type checker. However, there is a built-in expression of the
5362+
/// form \c Builtin.one_way(x) that forms a one-way constraint coming out
5363+
/// of expression `x` that can be used for testing purposes.
5364+
class OneWayExpr : public Expr {
5365+
Expr *SubExpr;
5366+
5367+
public:
5368+
/// Construct an implicit one-way expression from the given subexpression.
5369+
OneWayExpr(Expr *subExpr)
5370+
: Expr(ExprKind::OneWay, /*isImplicit=*/true), SubExpr(subExpr) { }
5371+
5372+
SourceLoc getLoc() const { return SubExpr->getLoc(); }
5373+
SourceRange getSourceRange() const { return SubExpr->getSourceRange(); }
5374+
5375+
Expr *getSubExpr() const { return SubExpr; }
5376+
void setSubExpr(Expr *subExpr) { SubExpr = subExpr; }
5377+
5378+
static bool classof(const Expr *E) {
5379+
return E->getKind() == ExprKind::OneWay;
5380+
}
5381+
};
5382+
53565383
inline bool Expr::isInfixOperator() const {
53575384
return isa<BinaryExpr>(this) || isa<IfExpr>(this) ||
53585385
isa<AssignExpr>(this) || isa<ExplicitCastExpr>(this);

include/swift/AST/ExprNodes.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ EXPR(EditorPlaceholder, Expr)
190190
EXPR(ObjCSelector, Expr)
191191
EXPR(KeyPath, Expr)
192192
UNCHECKED_EXPR(KeyPathDot, Expr)
193+
UNCHECKED_EXPR(OneWay, Expr)
193194
EXPR(Tap, Expr)
194195
LAST_EXPR(Tap)
195196

include/swift/Basic/LangOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ namespace swift {
207207
/// before termination of the shrink phrase of the constraint solver.
208208
unsigned SolverShrinkUnsolvedThreshold = 10;
209209

210+
/// Enable one-way constraints in function builders.
211+
bool FunctionBuilderOneWayConstraints = true;
212+
210213
/// Disable the shrink phase of the expression type checker.
211214
bool SolverDisableShrink = false;
212215

include/swift/Basic/Statistics.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ FRONTEND_STATISTIC(Sema, NumLeafScopes)
175175
/// This is a measure of complexity of the contraction algorithm.
176176
FRONTEND_STATISTIC(Sema, NumConstraintsConsideredForEdgeContraction)
177177

178+
/// Number of constraint-solving scopes created in the typechecker, while
179+
/// solving expression type constraints. A rough proxy for "how much work the
180+
/// expression typechecker did".
181+
FRONTEND_STATISTIC(Sema, NumCyclicOneWayComponentsCollapsed)
182+
178183
/// Number of declarations that were deserialized. A rough proxy for the amount
179184
/// of material loaded from other modules.
180185
FRONTEND_STATISTIC(Sema, NumDeclsDeserialized)

include/swift/Option/FrontendOptions.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,14 @@ def Rmodule_interface_rebuild : Flag<["-"], "Rmodule-interface-rebuild">,
394394

395395
def solver_expression_time_threshold_EQ : Joined<["-"], "solver-expression-time-threshold=">;
396396

397+
def enable_function_builder_one_way_constraints : Flag<["-"],
398+
"enable-function-builder-one-way-constraints">,
399+
HelpText<"Enable one-way constraints in the function builder transformation">;
400+
401+
def disable_function_builder_one_way_constraints : Flag<["-"],
402+
"disable-function-builder-one-way-constraints">,
403+
HelpText<"Disable one-way constraints in the function builder transformation">;
404+
397405
def solver_disable_shrink :
398406
Flag<["-"], "solver-disable-shrink">,
399407
HelpText<"Disable the shrink phase of expression type checking">;

lib/AST/ASTDumper.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2745,6 +2745,13 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
27452745
PrintWithColorRAII(OS, ParenthesisColor) << ')';
27462746
}
27472747

2748+
void visitOneWayExpr(OneWayExpr *E) {
2749+
printCommon(E, "one_way_expr");
2750+
OS << '\n';
2751+
printRec(E->getSubExpr());
2752+
PrintWithColorRAII(OS, ParenthesisColor) << ')';
2753+
}
2754+
27482755
void visitTapExpr(TapExpr *E) {
27492756
printCommon(E, "tap_expr");
27502757
PrintWithColorRAII(OS, DeclColor) << " var=";

lib/AST/ASTWalker.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,6 +1087,18 @@ class Traversal : public ASTVisitor<Traversal, Expr*, Stmt*,
10871087

10881088
Expr *visitKeyPathDotExpr(KeyPathDotExpr *E) { return E; }
10891089

1090+
Expr *visitOneWayExpr(OneWayExpr *E) {
1091+
if (auto oldSubExpr = E->getSubExpr()) {
1092+
if (auto subExpr = doIt(oldSubExpr)) {
1093+
E->setSubExpr(subExpr);
1094+
} else {
1095+
return nullptr;
1096+
}
1097+
}
1098+
1099+
return E;
1100+
}
1101+
10901102
Expr *visitTapExpr(TapExpr *E) {
10911103
if (auto oldSubExpr = E->getSubExpr()) {
10921104
if (auto subExpr = doIt(oldSubExpr)) {

lib/AST/Expr.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ ConcreteDeclRef Expr::getReferencedDecl() const {
366366
NO_REFERENCE(ObjCSelector);
367367
NO_REFERENCE(KeyPath);
368368
NO_REFERENCE(KeyPathDot);
369+
PASS_THROUGH_REFERENCE(OneWay, getSubExpr);
369370
NO_REFERENCE(Tap);
370371

371372
#undef SIMPLE_REFERENCE
@@ -537,6 +538,7 @@ bool Expr::canAppendPostfixExpression(bool appendingPostfixOperator) const {
537538
case ExprKind::Error:
538539
case ExprKind::CodeCompletion:
539540
case ExprKind::LazyInitializer:
541+
case ExprKind::OneWay:
540542
return false;
541543

542544
case ExprKind::NilLiteral:

lib/Frontend/CompilerInvocation.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,10 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
419419

420420
if (Args.getLastArg(OPT_solver_disable_shrink))
421421
Opts.SolverDisableShrink = true;
422+
Opts.FunctionBuilderOneWayConstraints =
423+
Args.hasFlag(OPT_enable_function_builder_one_way_constraints,
424+
OPT_disable_function_builder_one_way_constraints,
425+
/*Default=*/true);
422426

423427
if (const Arg *A = Args.getLastArg(OPT_value_recursion_threshold)) {
424428
unsigned threshold;

lib/Sema/BuilderTransform.cpp

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ class BuilderClosureVisitor
5050

5151
private:
5252
/// Produce a builder call to the given named function with the given arguments.
53-
CallExpr *buildCallIfWanted(SourceLoc loc,
54-
Identifier fnName, ArrayRef<Expr *> args,
55-
ArrayRef<Identifier> argLabels = {}) {
53+
Expr *buildCallIfWanted(SourceLoc loc,
54+
Identifier fnName, ArrayRef<Expr *> args,
55+
ArrayRef<Identifier> argLabels,
56+
bool allowOneWay) {
5657
if (!wantExpr)
5758
return nullptr;
5859

@@ -81,9 +82,17 @@ class BuilderClosureVisitor
8182
typeExpr, loc, fnName, DeclNameLoc(loc), /*implicit=*/true);
8283
SourceLoc openLoc = args.empty() ? loc : args.front()->getStartLoc();
8384
SourceLoc closeLoc = args.empty() ? loc : args.back()->getEndLoc();
84-
return CallExpr::create(ctx, memberRef, openLoc, args,
85-
argLabels, argLabelLocs, closeLoc,
86-
/*trailing closure*/ nullptr, /*implicit*/true);
85+
Expr *result = CallExpr::create(ctx, memberRef, openLoc, args,
86+
argLabels, argLabelLocs, closeLoc,
87+
/*trailing closure*/ nullptr,
88+
/*implicit*/true);
89+
90+
if (ctx.LangOpts.FunctionBuilderOneWayConstraints && allowOneWay) {
91+
// Form a one-way constraint to prevent backward propagation.
92+
result = new (ctx) OneWayExpr(result);
93+
}
94+
95+
return result;
8796
}
8897

8998
/// Check whether the builder supports the given operation.
@@ -160,12 +169,17 @@ class BuilderClosureVisitor
160169
}
161170

162171
auto expr = node.get<Expr *>();
172+
if (wantExpr && ctx.LangOpts.FunctionBuilderOneWayConstraints)
173+
expr = new (ctx) OneWayExpr(expr);
174+
163175
expressions.push_back(expr);
164176
}
165177

166178
// Call Builder.buildBlock(... args ...)
167179
return buildCallIfWanted(braceStmt->getStartLoc(),
168-
ctx.Id_buildBlock, expressions);
180+
ctx.Id_buildBlock, expressions,
181+
/*argLabels=*/{ },
182+
/*allowOneWay=*/true);
169183
}
170184

171185
Expr *visitReturnStmt(ReturnStmt *stmt) {
@@ -190,7 +204,8 @@ class BuilderClosureVisitor
190204
if (!arg)
191205
return nullptr;
192206

193-
return buildCallIfWanted(doStmt->getStartLoc(), ctx.Id_buildDo, arg);
207+
return buildCallIfWanted(doStmt->getStartLoc(), ctx.Id_buildDo, arg,
208+
/*argLabels=*/{ }, /*allowOneWay=*/true);
194209
}
195210

196211
CONTROL_FLOW_STMT(Yield)
@@ -275,7 +290,12 @@ class BuilderClosureVisitor
275290
// so we just need to call `buildIf` now, since we're at the top level.
276291
if (isOptional) {
277292
chainExpr = buildCallIfWanted(ifStmt->getStartLoc(),
278-
ctx.Id_buildIf, chainExpr);
293+
ctx.Id_buildIf, chainExpr,
294+
/*argLabels=*/{ },
295+
/*allowOneWay=*/true);
296+
} else if (ctx.LangOpts.FunctionBuilderOneWayConstraints) {
297+
// Form a one-way constraint to prevent backward propagation.
298+
chainExpr = new (ctx) OneWayExpr(chainExpr);
279299
}
280300

281301
return chainExpr;
@@ -395,7 +415,8 @@ class BuilderClosureVisitor
395415
bool isSecond = (path & 1);
396416
operand = buildCallIfWanted(operand->getStartLoc(),
397417
ctx.Id_buildEither, operand,
398-
{isSecond ? ctx.Id_second : ctx.Id_first});
418+
{isSecond ? ctx.Id_second : ctx.Id_first},
419+
/*allowOneWay=*/false);
399420
}
400421

401422
// Inject into Optional if required. We'll be adding the call to

lib/Sema/CSApply.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4742,6 +4742,11 @@ namespace {
47424742
llvm_unreachable("found KeyPathDotExpr in CSApply");
47434743
}
47444744

4745+
Expr *visitOneWayExpr(OneWayExpr *E) {
4746+
auto type = simplifyType(cs.getType(E));
4747+
return coerceToType(E->getSubExpr(), type, cs.getConstraintLocator(E));
4748+
}
4749+
47454750
Expr *visitTapExpr(TapExpr *E) {
47464751
auto type = simplifyType(cs.getType(E));
47474752

lib/Sema/CSBindings.cpp

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -209,14 +209,14 @@ bool ConstraintSystem::PotentialBindings::isViable(
209209
static bool hasNilLiteralConstraint(TypeVariableType *typeVar,
210210
ConstraintSystem &CS) {
211211
// Look for a literal-conformance constraint on the type variable.
212-
llvm::SetVector<Constraint *> constraints;
213-
CS.getConstraintGraph().gatherConstraints(
214-
typeVar, constraints, ConstraintGraph::GatheringKind::EquivalenceClass,
215-
[](Constraint *constraint) -> bool {
216-
return constraint->getKind() == ConstraintKind::LiteralConformsTo &&
217-
constraint->getProtocol()->isSpecificProtocol(
218-
KnownProtocolKind::ExpressibleByNilLiteral);
219-
});
212+
auto constraints =
213+
CS.getConstraintGraph().gatherConstraints(
214+
typeVar, ConstraintGraph::GatheringKind::EquivalenceClass,
215+
[](Constraint *constraint) -> bool {
216+
return constraint->getKind() == ConstraintKind::LiteralConformsTo &&
217+
constraint->getProtocol()->isSpecificProtocol(
218+
KnownProtocolKind::ExpressibleByNilLiteral);
219+
});
220220

221221
for (auto constraint : constraints)
222222
if (CS.simplifyType(constraint->getFirstType())->isEqual(typeVar))
@@ -392,9 +392,9 @@ ConstraintSystem::getPotentialBindings(TypeVariableType *typeVar) {
392392
}
393393

394394
// Gather the constraints associated with this type variable.
395-
llvm::SetVector<Constraint *> constraints;
396-
getConstraintGraph().gatherConstraints(
397-
typeVar, constraints, ConstraintGraph::GatheringKind::EquivalenceClass);
395+
auto constraints =
396+
getConstraintGraph().gatherConstraints(
397+
typeVar, ConstraintGraph::GatheringKind::EquivalenceClass);
398398

399399
PotentialBindings result(typeVar);
400400

@@ -632,6 +632,18 @@ ConstraintSystem::getPotentialBindings(TypeVariableType *typeVar) {
632632
result.FullyBound = true;
633633
}
634634
break;
635+
636+
case ConstraintKind::OneWayEqual: {
637+
// Don't produce any bindings if this type variable is on the left-hand
638+
// side of a one-way binding.
639+
auto firstType = constraint->getFirstType();
640+
if (auto *tv = firstType->getAs<TypeVariableType>()) {
641+
if (tv->getImpl().getRepresentative(nullptr) == typeVar)
642+
return {typeVar};
643+
}
644+
645+
break;
646+
}
635647
}
636648
}
637649

lib/Sema/CSDiag.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7319,9 +7319,8 @@ bool FailureDiagnosis::diagnoseAmbiguousGenericParameters() {
73197319
// because type B would have no constraints associated with it.
73207320
unsigned numConstraints = 0;
73217321
{
7322-
llvm::SetVector<Constraint *> constraints;
7323-
CS.getConstraintGraph().gatherConstraints(
7324-
tv, constraints, ConstraintGraph::GatheringKind::EquivalenceClass,
7322+
auto constraints = CS.getConstraintGraph().gatherConstraints(
7323+
tv, ConstraintGraph::GatheringKind::EquivalenceClass,
73257324
[&](Constraint *constraint) -> bool {
73267325
// We are not interested in ConformsTo constraints because
73277326
// we can't derive any concrete type information from them.

lib/Sema/CSGen.cpp

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -532,9 +532,8 @@ namespace {
532532

533533
llvm::SmallSetVector<ProtocolDecl *, 2> literalProtos;
534534
if (auto argTypeVar = argTy->getAs<TypeVariableType>()) {
535-
llvm::SetVector<Constraint *> constraints;
536-
CS.getConstraintGraph().gatherConstraints(
537-
argTypeVar, constraints,
535+
auto constraints = CS.getConstraintGraph().gatherConstraints(
536+
argTypeVar,
538537
ConstraintGraph::GatheringKind::EquivalenceClass,
539538
[](Constraint *constraint) {
540539
return constraint->getKind() == ConstraintKind::LiteralConformsTo;
@@ -3027,6 +3026,14 @@ namespace {
30273026
llvm_unreachable("found KeyPathDotExpr in CSGen");
30283027
}
30293028

3029+
Type visitOneWayExpr(OneWayExpr *expr) {
3030+
auto locator = CS.getConstraintLocator(expr);
3031+
auto resultTypeVar = CS.createTypeVariable(locator, 0);
3032+
CS.addConstraint(ConstraintKind::OneWayEqual, resultTypeVar,
3033+
CS.getType(expr->getSubExpr()), locator);
3034+
return resultTypeVar;
3035+
}
3036+
30303037
Type visitTapExpr(TapExpr *expr) {
30313038
DeclContext *varDC = expr->getVar()->getDeclContext();
30323039
assert(varDC == CS.DC || (varDC && isa<AbstractClosureExpr>(varDC)) &&
@@ -3060,7 +3067,8 @@ namespace {
30603067
Join,
30613068
JoinInout,
30623069
JoinMeta,
3063-
JoinNonexistent
3070+
JoinNonexistent,
3071+
OneWay,
30643072
};
30653073

30663074
static TypeOperation getTypeOperation(UnresolvedDotExpr *UDE,
@@ -3074,6 +3082,7 @@ namespace {
30743082

30753083
return llvm::StringSwitch<TypeOperation>(
30763084
UDE->getName().getBaseIdentifier().str())
3085+
.Case("one_way", TypeOperation::OneWay)
30773086
.Case("type_join", TypeOperation::Join)
30783087
.Case("type_join_inout", TypeOperation::JoinInout)
30793088
.Case("type_join_meta", TypeOperation::JoinMeta)
@@ -3082,14 +3091,14 @@ namespace {
30823091
}
30833092

30843093
Type resultOfTypeOperation(TypeOperation op, Expr *Arg) {
3085-
auto *tuple = dyn_cast<TupleExpr>(Arg);
3086-
assert(tuple && "Expected argument tuple for join operations!");
3094+
auto *tuple = cast<TupleExpr>(Arg);
30873095

30883096
auto *lhs = tuple->getElement(0);
30893097
auto *rhs = tuple->getElement(1);
30903098

30913099
switch (op) {
30923100
case TypeOperation::None:
3101+
case TypeOperation::OneWay:
30933102
llvm_unreachable(
30943103
"We should have a valid type operation at this point!");
30953104

@@ -3453,18 +3462,23 @@ namespace {
34533462
/// Once we've visited the children of the given expression,
34543463
/// generate constraints from the expression.
34553464
Expr *walkToExprPost(Expr *expr) override {
3456-
3457-
// Handle the Builtin.type_join* family of calls by replacing
3458-
// them with dot_self_expr of type_expr with the type being the
3459-
// result of the join.
3465+
// Translate special type-checker Builtin calls into simpler expressions.
34603466
if (auto *apply = dyn_cast<ApplyExpr>(expr)) {
34613467
auto fnExpr = apply->getFn();
34623468
if (auto *UDE = dyn_cast<UnresolvedDotExpr>(fnExpr)) {
34633469
auto &CS = CG.getConstraintSystem();
34643470
auto typeOperation =
34653471
ConstraintGenerator::getTypeOperation(UDE, CS.getASTContext());
34663472

3467-
if (typeOperation != ConstraintGenerator::TypeOperation::None) {
3473+
if (typeOperation == ConstraintGenerator::TypeOperation::OneWay) {
3474+
// For a one-way constraint, create the OneWayExpr node.
3475+
auto *arg = cast<ParenExpr>(apply->getArg())->getSubExpr();
3476+
expr = new (CS.getASTContext()) OneWayExpr(arg);
3477+
} else if (typeOperation !=
3478+
ConstraintGenerator::TypeOperation::None) {
3479+
// Handle the Builtin.type_join* family of calls by replacing
3480+
// them with dot_self_expr of type_expr with the type being the
3481+
// result of the join.
34683482
auto joinMetaTy =
34693483
CG.resultOfTypeOperation(typeOperation, apply->getArg());
34703484
auto joinTy = joinMetaTy->castTo<MetatypeType>()->getInstanceType();

0 commit comments

Comments
 (0)