Skip to content

Commit 62efa55

Browse files
authored
Merge pull request swiftlang#32995 from DougGregor/function-builder-limited-availability-5.3
[5.3] [Function builders] Use buildLimitedAvailability() for #available block
2 parents 0b70435 + f506a67 commit 62efa55

File tree

4 files changed

+123
-5
lines changed

4 files changed

+123
-5
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5056,6 +5056,10 @@ NOTE(function_builder_infer_add_return, none,
50565056
NOTE(function_builder_infer_pick_specific, none,
50575057
"apply function builder %0 (inferred from %select{protocol|dynamic replacement of}1 %2)",
50585058
(Type, unsigned, DeclName))
5059+
WARNING(function_builder_missing_limited_availability, none,
5060+
"function builder %0 does not implement `buildLimitedAvailability`; "
5061+
"this code may crash on earlier versions of the OS",
5062+
(Type))
50595063

50605064
//------------------------------------------------------------------------------
50615065
// MARK: Tuple Shuffle Diagnostics

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ IDENTIFIER(buildEither)
3737
IDENTIFIER(buildExpression)
3838
IDENTIFIER(buildFinalResult)
3939
IDENTIFIER(buildIf)
40+
IDENTIFIER(buildLimitedAvailability)
4041
IDENTIFIER(buildOptional)
4142
IDENTIFIER(callAsFunction)
4243
IDENTIFIER(Change)

lib/Sema/BuilderTransform.cpp

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "MiscDiagnostics.h"
2020
#include "SolutionResult.h"
2121
#include "TypeChecker.h"
22+
#include "TypeCheckAvailability.h"
2223
#include "swift/AST/ASTVisitor.h"
2324
#include "swift/AST/ASTWalker.h"
2425
#include "swift/AST/NameLookup.h"
@@ -38,6 +39,24 @@ using namespace constraints;
3839

3940
namespace {
4041

42+
/// Find the first #available condition within the statement condition,
43+
/// or return NULL if there isn't one.
44+
const StmtConditionElement *findAvailabilityCondition(StmtCondition stmtCond) {
45+
for (const auto &cond : stmtCond) {
46+
switch (cond.getKind()) {
47+
case StmtConditionElement::CK_Boolean:
48+
case StmtConditionElement::CK_PatternBinding:
49+
continue;
50+
51+
case StmtConditionElement::CK_Availability:
52+
return &cond;
53+
break;
54+
}
55+
}
56+
57+
return nullptr;
58+
}
59+
4160
/// Visitor to classify the contents of the given closure.
4261
class BuilderClosureVisitor
4362
: private StmtVisitor<BuilderClosureVisitor, VarDecl *> {
@@ -502,10 +521,20 @@ class BuilderClosureVisitor
502521
if (!cs || !thenVar || (elseChainVar && !*elseChainVar))
503522
return nullptr;
504523

524+
// If there is a #available in the condition, the 'then' will need to
525+
// be wrapped in a call to buildAvailabilityErasure(_:), if available.
526+
Expr *thenVarRefExpr = buildVarRef(
527+
thenVar, ifStmt->getThenStmt()->getEndLoc());
528+
if (findAvailabilityCondition(ifStmt->getCond()) &&
529+
builderSupports(ctx.Id_buildLimitedAvailability)) {
530+
thenVarRefExpr = buildCallIfWanted(
531+
ifStmt->getThenStmt()->getEndLoc(), ctx.Id_buildLimitedAvailability,
532+
{ thenVarRefExpr }, { Identifier() });
533+
}
534+
505535
// Prepare the `then` operand by wrapping it to produce a chain result.
506536
Expr *thenExpr = buildWrappedChainPayload(
507-
buildVarRef(thenVar, ifStmt->getThenStmt()->getEndLoc()),
508-
payloadIndex, numPayloads, isOptional);
537+
thenVarRefExpr, payloadIndex, numPayloads, isOptional);
509538

510539
// Prepare the `else operand:
511540
Expr *elseExpr;
@@ -1054,6 +1083,35 @@ class BuilderClosureRewriter
10541083
capturedThen.first, {capturedThen.second.front()}));
10551084
ifStmt->setThenStmt(newThen);
10561085

1086+
// Look for a #available condition. If there is one, we need to check
1087+
// that the resulting type of the "then" does refer to any types that
1088+
// are unavailable in the enclosing context.
1089+
//
1090+
// Note that this is for staging in support for
1091+
if (auto availabilityCond = findAvailabilityCondition(ifStmt->getCond())) {
1092+
SourceLoc loc = availabilityCond->getStartLoc();
1093+
Type thenBodyType = solution.simplifyType(
1094+
solution.getType(target.captured.second[0]));
1095+
thenBodyType.findIf([&](Type type) {
1096+
auto nominal = type->getAnyNominal();
1097+
if (!nominal)
1098+
return false;
1099+
1100+
if (auto reason = TypeChecker::checkDeclarationAvailability(
1101+
nominal, loc, dc)) {
1102+
// Note that the problem is with the function builder, not the body.
1103+
// This is for staging only. We want to disable #available in
1104+
// function builders that don't support this operation.
1105+
ctx.Diags.diagnose(
1106+
loc, diag::function_builder_missing_limited_availability,
1107+
builderTransform.builderType);
1108+
return true;
1109+
}
1110+
1111+
return false;
1112+
});
1113+
}
1114+
10571115
if (auto elseBraceStmt =
10581116
dyn_cast_or_null<BraceStmt>(ifStmt->getElseStmt())) {
10591117
// Translate the "else" branch when it's a stmt-brace.

test/Constraints/function_builder_availability.swift

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ struct TupleBuilder {
1616
static func buildBlock<T1, T2>(_ t1: T1, _ t2: T2) -> (T1, T2) {
1717
return (t1, t2)
1818
}
19-
19+
2020
static func buildBlock<T1, T2, T3>(_ t1: T1, _ t2: T2, _ t3: T3)
2121
-> (T1, T2, T3) {
2222
return (t1, t2, t3)
@@ -55,14 +55,17 @@ func globalFuncAvailableOn10_9() -> Int { return 9 }
5555
func globalFuncAvailableOn10_51() -> Int { return 10 }
5656

5757
@available(OSX, introduced: 10.52)
58-
func globalFuncAvailableOn10_52() -> Int { return 11 }
58+
struct Only10_52 { }
59+
60+
@available(OSX, introduced: 10.52)
61+
func globalFuncAvailableOn10_52() -> Only10_52 { .init() }
5962

6063
tuplify(true) { cond in
6164
globalFuncAvailableOn10_9()
6265
if #available(OSX 10.51, *) {
6366
globalFuncAvailableOn10_51()
6467
tuplify(false) { cond2 in
65-
if cond, #available(OSX 10.52, *) {
68+
if cond, #available(OSX 10.52, *) { // expected-warning{{function builder 'TupleBuilder' does not implement `buildLimitedAvailability`; this code may crash on earlier versions of the OS}}
6669
cond2
6770
globalFuncAvailableOn10_52()
6871
} else {
@@ -72,3 +75,55 @@ tuplify(true) { cond in
7275
}
7376
}
7477
}
78+
79+
// Function builder that can perform type erasure for #available.
80+
@_functionBuilder
81+
struct TupleBuilderAvailability {
82+
static func buildBlock<T1>(_ t1: T1) -> (T1) {
83+
return (t1)
84+
}
85+
86+
static func buildBlock<T1, T2>(_ t1: T1, _ t2: T2) -> (T1, T2) {
87+
return (t1, t2)
88+
}
89+
90+
static func buildBlock<T1, T2, T3>(_ t1: T1, _ t2: T2, _ t3: T3)
91+
-> (T1, T2, T3) {
92+
return (t1, t2, t3)
93+
}
94+
95+
static func buildBlock<T1, T2, T3, T4>(_ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4)
96+
-> (T1, T2, T3, T4) {
97+
return (t1, t2, t3, t4)
98+
}
99+
100+
static func buildBlock<T1, T2, T3, T4, T5>(
101+
_ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4, _ t5: T5
102+
) -> (T1, T2, T3, T4, T5) {
103+
return (t1, t2, t3, t4, t5)
104+
}
105+
106+
static func buildDo<T>(_ value: T) -> T { return value }
107+
static func buildIf<T>(_ value: T?) -> T? { return value }
108+
109+
static func buildEither<T,U>(first value: T) -> Either<T,U> {
110+
return .first(value)
111+
}
112+
static func buildEither<T,U>(second value: U) -> Either<T,U> {
113+
return .second(value)
114+
}
115+
116+
static func buildLimitedAvailability<T>(_ value: T) -> Any {
117+
return value
118+
}
119+
}
120+
121+
func tuplifyWithAvailabilityErasure<T>(_ cond: Bool, @TupleBuilderAvailability body: (Bool) -> T) {
122+
print(body(cond))
123+
}
124+
125+
tuplifyWithAvailabilityErasure(true) { cond in
126+
if cond, #available(OSX 10.52, *) {
127+
globalFuncAvailableOn10_52()
128+
}
129+
}

0 commit comments

Comments
 (0)