Skip to content

Commit 83b5b2f

Browse files
committed
Support if-else chains on function builders.
A substantial amount of this patch goes towards trying to get at least minimal diagnostics working, since of course I messed up the rule a few times when implementing this. rdar://50149837
1 parent 7475bcd commit 83b5b2f

File tree

3 files changed

+333
-28
lines changed

3 files changed

+333
-28
lines changed

include/swift/AST/KnownIdentifiers.def

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ IDENTIFIER(ArrayLiteralElement)
3232
IDENTIFIER(atIndexedSubscript)
3333
IDENTIFIER_(bridgeToObjectiveC)
3434
IDENTIFIER(buildBlock)
35-
IDENTIFIER(buildIf)
3635
IDENTIFIER(buildDo)
36+
IDENTIFIER(buildEither)
37+
IDENTIFIER(buildIf)
3738
IDENTIFIER(Change)
3839
IDENTIFIER_WITH_NAME(code_, "_code")
3940
IDENTIFIER(CodingKeys)
@@ -62,6 +63,7 @@ IDENTIFIER(Encoder)
6263
IDENTIFIER(encoder)
6364
IDENTIFIER(error)
6465
IDENTIFIER(errorDomain)
66+
IDENTIFIER(first)
6567
IDENTIFIER(forKeyedSubscript)
6668
IDENTIFIER(Foundation)
6769
IDENTIFIER(for)
@@ -96,6 +98,7 @@ IDENTIFIER(parameter)
9698
IDENTIFIER(Protocol)
9799
IDENTIFIER(rawValue)
98100
IDENTIFIER(RawValue)
101+
IDENTIFIER(second)
99102
IDENTIFIER(Selector)
100103
IDENTIFIER(self)
101104
IDENTIFIER(Self)

lib/Sema/ConstraintSystem.cpp

Lines changed: 236 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2781,20 +2781,30 @@ namespace {
27812781

27822782
private:
27832783
/// Produce a builder call to the given named function with the given arguments.
2784-
CallExpr *buildCallIfWanted(Identifier fnName, ArrayRef<Expr *> args) {
2784+
CallExpr *buildCallIfWanted(SourceLoc loc,
2785+
Identifier fnName, ArrayRef<Expr *> args,
2786+
ArrayRef<Identifier> argLabels = {}) {
27852787
if (!wantExpr)
27862788
return nullptr;
27872789

2788-
auto typeExpr = new (ctx) TypeExpr(TypeLoc());
2790+
// FIXME: Setting a TypeLoc on this expression is necessary in order
2791+
// to get diagnostics if something about this builder call fails,
2792+
// e.g. if there isn't a matching overload for `buildBlock`.
2793+
// But we can only do this if there isn't a type variable in the type.
2794+
TypeLoc typeLoc(nullptr,
2795+
builderType->hasTypeVariable() ? Type() : builderType);
2796+
2797+
auto typeExpr = new (ctx) TypeExpr(typeLoc);
27892798
cs.setType(typeExpr, builderType);
27902799
typeExpr->setImplicit();
27912800
auto memberRef = new (ctx) UnresolvedDotExpr(
2792-
typeExpr, SourceLoc(), fnName, DeclNameLoc(), /*implicit=*/true);
2793-
return CallExpr::createImplicit(ctx, memberRef, args, { });
2801+
typeExpr, loc, fnName, DeclNameLoc(), /*implicit=*/true);
2802+
return CallExpr::createImplicit(ctx, memberRef, args, argLabels);
27942803
}
27952804

27962805
/// Check whether the builder supports the given operation.
2797-
bool builderSupports(Identifier fnName) {
2806+
bool builderSupports(Identifier fnName,
2807+
ArrayRef<Identifier> argLabels = {}) {
27982808
auto known = supportedOps.find(fnName);
27992809
if (known != supportedOps.end()) {
28002810
return known->second;
@@ -2803,10 +2813,21 @@ namespace {
28032813
bool found = false;
28042814
for (auto decl : builder->lookupDirect(fnName)) {
28052815
if (auto func = dyn_cast<FuncDecl>(decl)) {
2806-
if (func->isStatic()) {
2807-
found = true;
2808-
break;
2816+
// Function must be static.
2817+
if (!func->isStatic())
2818+
continue;
2819+
2820+
// Function must have the right argument labels, if provided.
2821+
if (!argLabels.empty()) {
2822+
auto funcLabels = func->getFullName().getArgumentNames();
2823+
if (argLabels.size() > funcLabels.size() ||
2824+
funcLabels.slice(0, argLabels.size()) != argLabels)
2825+
continue;
28092826
}
2827+
2828+
// Okay, it's a good-enough match.
2829+
found = true;
2830+
break;
28102831
}
28112832
}
28122833

@@ -2850,7 +2871,8 @@ namespace {
28502871
}
28512872

28522873
// Call Builder.buildBlock(... args ...)
2853-
return buildCallIfWanted(ctx.Id_buildBlock, expressions);
2874+
return buildCallIfWanted(braceStmt->getStartLoc(),
2875+
ctx.Id_buildBlock, expressions);
28542876
}
28552877

28562878
Expr *visitReturnStmt(ReturnStmt *returnStmt) {
@@ -2868,7 +2890,7 @@ namespace {
28682890
if (!arg)
28692891
return nullptr;
28702892

2871-
return buildCallIfWanted(ctx.Id_buildDo, arg);
2893+
return buildCallIfWanted(doStmt->getStartLoc(), ctx.Id_buildDo, arg);
28722894
}
28732895

28742896
CONTROL_FLOW_STMT(Yield)
@@ -2881,42 +2903,229 @@ namespace {
28812903
return condition.front().getBooleanOrNull();
28822904
}
28832905

2906+
static bool isBuildableIfChainRecursive(IfStmt *ifStmt,
2907+
unsigned &numPayloads,
2908+
bool &isOptional) {
2909+
// The conditional must be trivial.
2910+
if (!getTrivialBooleanCondition(ifStmt->getCond()))
2911+
return false;
2912+
2913+
// The 'then' clause contributes a payload.
2914+
numPayloads++;
2915+
2916+
// If there's an 'else' clause, it contributes payloads:
2917+
if (auto elseStmt = ifStmt->getElseStmt()) {
2918+
// If it's 'else if', it contributes payloads recursively.
2919+
if (auto elseIfStmt = dyn_cast<IfStmt>(elseStmt)) {
2920+
return isBuildableIfChainRecursive(elseIfStmt, numPayloads,
2921+
isOptional);
2922+
// Otherwise it's just the one.
2923+
} else {
2924+
numPayloads++;
2925+
}
2926+
2927+
// If not, the chain result is at least optional.
2928+
} else {
2929+
isOptional = true;
2930+
}
2931+
2932+
return true;
2933+
}
2934+
2935+
bool isBuildableIfChain(IfStmt *ifStmt, unsigned &numPayloads,
2936+
bool &isOptional) {
2937+
if (!isBuildableIfChainRecursive(ifStmt, numPayloads, isOptional))
2938+
return false;
2939+
2940+
// If there's a missing 'else', we need 'buildIf' to exist.
2941+
if (isOptional && !builderSupports(ctx.Id_buildIf))
2942+
return false;
2943+
2944+
// If there are multiple clauses, we need 'buildEither(first:)' and
2945+
// 'buildEither(second:)' to both exist.
2946+
if (numPayloads > 1) {
2947+
if (!builderSupports(ctx.Id_buildEither, {ctx.Id_first}) ||
2948+
!builderSupports(ctx.Id_buildEither, {ctx.Id_second}))
2949+
return false;
2950+
}
2951+
2952+
return true;
2953+
}
2954+
28842955
Expr *visitIfStmt(IfStmt *ifStmt) {
2885-
if (!builderSupports(ctx.Id_buildIf) ||
2886-
ifStmt->getElseStmt() ||
2887-
!getTrivialBooleanCondition(ifStmt->getCond())) {
2956+
// Check whether the chain is buildable and whether it terminates
2957+
// without an `else`.
2958+
bool isOptional = false;
2959+
unsigned numPayloads = 0;
2960+
if (!isBuildableIfChain(ifStmt, numPayloads, isOptional)) {
28882961
if (!unhandledNode)
28892962
unhandledNode = ifStmt;
28902963
return nullptr;
28912964
}
28922965

2893-
auto thenArg = visit(ifStmt->getThenStmt());
2894-
if (!thenArg)
2966+
// Attempt to build the chain, propagating short-circuits, which
2967+
// might arise either do to error or not wanting an expression.
2968+
auto chainExpr =
2969+
buildIfChainRecursive(ifStmt, 0, numPayloads, isOptional);
2970+
if (!chainExpr)
28952971
return nullptr;
2972+
assert(wantExpr);
28962973

2897-
if (!wantExpr)
2974+
// The operand should have optional type if we had optional results,
2975+
// so we just need to call `buildIf` now, since we're at the top level.
2976+
if (isOptional) {
2977+
chainExpr = buildCallIfWanted(ifStmt->getStartLoc(),
2978+
ctx.Id_buildIf, chainExpr);
2979+
}
2980+
2981+
return chainExpr;
2982+
}
2983+
2984+
/// Recursively build an if-chain: build an expression which will have
2985+
/// a value of the chain result type before any call to `buildIf`.
2986+
/// The expression will perform any necessary calls to `buildEither`,
2987+
/// and the result will have optional type if `isOptional` is true.
2988+
Expr *buildIfChainRecursive(IfStmt *ifStmt, unsigned payloadIndex,
2989+
unsigned numPayloads, bool isOptional) {
2990+
assert(payloadIndex < numPayloads);
2991+
// Make sure we recursively visit both sides even if we're not
2992+
// building expressions.
2993+
2994+
// Build the then clause. This will have the corresponding payload
2995+
// type (i.e. not wrapped in any way).
2996+
Expr *thenArg = visit(ifStmt->getThenStmt());
2997+
2998+
// Build the else clause, if present. If this is from an else-if,
2999+
// this will be fully wrapped; otherwise it will have the corresponding
3000+
// payload type (at index `payloadIndex + 1`).
3001+
assert(ifStmt->getElseStmt() || isOptional);
3002+
bool isElseIf = false;
3003+
Optional<Expr *> elseChain;
3004+
if (auto elseStmt = ifStmt->getElseStmt()) {
3005+
if (auto elseIfStmt = dyn_cast<IfStmt>(elseStmt)) {
3006+
isElseIf = true;
3007+
elseChain = buildIfChainRecursive(elseIfStmt, payloadIndex + 1,
3008+
numPayloads, isOptional);
3009+
} else {
3010+
elseChain = visit(elseStmt);
3011+
}
3012+
}
3013+
3014+
// Short-circuit if appropriate.
3015+
if (!wantExpr || !thenArg || (elseChain && !*elseChain))
28983016
return nullptr;
28993017

3018+
// Okay, build the conditional expression.
3019+
3020+
// Prepare the `then` operand by wrapping it to produce a chain result.
3021+
SourceLoc thenLoc = ifStmt->getThenStmt()->getStartLoc();
3022+
Expr *thenExpr = buildWrappedChainPayload(thenArg, payloadIndex,
3023+
numPayloads, isOptional);
3024+
3025+
// Prepare the `else operand:
3026+
Expr *elseExpr;
3027+
SourceLoc elseLoc;
3028+
3029+
// - If there's no `else` clause, use `Optional.none`.
3030+
if (!elseChain) {
3031+
assert(isOptional);
3032+
elseLoc = ifStmt->getEndLoc();
3033+
elseExpr = buildNoneExpr(elseLoc);
3034+
3035+
// - If there's an `else if`, the chain expression from that
3036+
// should already be producing a chain result.
3037+
} else if (isElseIf) {
3038+
elseExpr = *elseChain;
3039+
elseLoc = ifStmt->getElseLoc();
3040+
3041+
// - Otherwise, wrap it to produce a chain result.
3042+
} else {
3043+
elseLoc = ifStmt->getElseLoc();
3044+
elseExpr = buildWrappedChainPayload(*elseChain,
3045+
payloadIndex + 1, numPayloads,
3046+
isOptional);
3047+
}
3048+
3049+
Expr *condition = getTrivialBooleanCondition(ifStmt->getCond());
3050+
assert(condition && "checked by isBuildableIfChain");
3051+
3052+
auto ifExpr = new (ctx) IfExpr(condition, thenLoc, thenExpr,
3053+
elseLoc, elseExpr);
3054+
ifExpr->setImplicit();
3055+
return ifExpr;
3056+
}
3057+
3058+
/// Wrap a payload value in an expression which will produce a chain
3059+
/// result (without `buildIf`).
3060+
Expr *buildWrappedChainPayload(Expr *operand, unsigned payloadIndex,
3061+
unsigned numPayloads, bool isOptional) {
3062+
assert(payloadIndex < numPayloads);
3063+
3064+
// Inject into the appropriate chain position.
3065+
//
3066+
// We produce a (left-biased) balanced binary tree of Eithers in order
3067+
// to prevent requiring a linear number of injections in the worst case.
3068+
// That is, if we have 13 clauses, we want to produce:
3069+
//
3070+
// /------------------Either------------\
3071+
// /-------Either-------\ /--Either--\
3072+
// /--Either--\ /--Either--\ /--Either--\ \
3073+
// /-E-\ /-E-\ /-E-\ /-E-\ /-E-\ /-E-\ \
3074+
// 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100
3075+
//
3076+
// Note that a prefix of length D of the payload index acts as a path
3077+
// through the tree to the node at depth D. On the rightmost path
3078+
// through the tree (when this prefix is equal to the corresponding
3079+
// prefix of the maximum payload index), the bits of the index mark
3080+
// where Eithers are required.
3081+
//
3082+
// Since we naturally want to build from the innermost Either out, and
3083+
// therefore work with progressively shorter prefixes, we can do it all
3084+
// with right-shifts.
3085+
for (auto path = payloadIndex, maxPath = numPayloads - 1;
3086+
maxPath != 0; path >>= 1, maxPath >>= 1) {
3087+
// Skip making Eithers on the rightmost path where they aren't required.
3088+
// This isn't just an optimization: adding spurious Eithers could
3089+
// leave us with unresolvable type variables if `buildEither` has
3090+
// a signature like:
3091+
// static func buildEither<T,U>(first value: T) -> Either<T,U>
3092+
// which relies on unification to work.
3093+
if (path == maxPath && !(maxPath & 1)) continue;
3094+
3095+
bool isSecond = (path & 1);
3096+
operand = buildCallIfWanted(operand->getStartLoc(),
3097+
ctx.Id_buildEither, operand,
3098+
{isSecond ? ctx.Id_second : ctx.Id_first});
3099+
}
3100+
3101+
// Inject into Optional if required. We'll be adding the call to
3102+
// `buildIf` after all the recursive calls are complete.
3103+
if (isOptional) {
3104+
operand = buildSomeExpr(operand);
3105+
}
3106+
3107+
return operand;
3108+
}
3109+
3110+
Expr *buildSomeExpr(Expr *arg) {
29003111
auto optionalDecl = ctx.getOptionalDecl();
29013112
auto optionalType = optionalDecl->getDeclaredType();
29023113

29033114
auto optionalTypeExpr = TypeExpr::createImplicit(optionalType, ctx);
29043115
auto someRef = new (ctx) UnresolvedDotExpr(
29053116
optionalTypeExpr, SourceLoc(), ctx.getIdentifier("some"),
29063117
DeclNameLoc(), /*implicit=*/true);
2907-
auto someCall = CallExpr::createImplicit(ctx, someRef, thenArg, { });
3118+
return CallExpr::createImplicit(ctx, someRef, arg, { });
3119+
}
29083120

2909-
optionalTypeExpr = TypeExpr::createImplicit(optionalType, ctx);
2910-
auto noneRef = new (ctx) UnresolvedDotExpr(
2911-
optionalTypeExpr, ifStmt->getEndLoc(), ctx.getIdentifier("none"),
2912-
DeclNameLoc(ifStmt->getEndLoc()), /*implicit=*/true);
3121+
Expr *buildNoneExpr(SourceLoc endLoc) {
3122+
auto optionalDecl = ctx.getOptionalDecl();
3123+
auto optionalType = optionalDecl->getDeclaredType();
29133124

2914-
auto ifExpr = new (ctx) IfExpr(
2915-
getTrivialBooleanCondition(ifStmt->getCond()),
2916-
SourceLoc(), someCall,
2917-
SourceLoc(), noneRef);
2918-
ifExpr->setImplicit();
2919-
return buildCallIfWanted(ctx.Id_buildIf, ifExpr);
3125+
auto optionalTypeExpr = TypeExpr::createImplicit(optionalType, ctx);
3126+
return new (ctx) UnresolvedDotExpr(
3127+
optionalTypeExpr, endLoc, ctx.getIdentifier("none"),
3128+
DeclNameLoc(endLoc), /*implicit=*/true);
29203129
}
29213130

29223131
CONTROL_FLOW_STMT(Guard)

0 commit comments

Comments
 (0)