Skip to content

Commit cde899d

Browse files
authored
Merge pull request swiftlang#32994 from DougGregor/function-builder-limited-availability
[Function builders] Use buildLimitedAvailability() for #available block
2 parents 356bfee + 9409e9d commit cde899d

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
@@ -5127,6 +5127,10 @@ NOTE(function_builder_infer_add_return, none,
51275127
NOTE(function_builder_infer_pick_specific, none,
51285128
"apply function builder %0 (inferred from %select{protocol|dynamic replacement of}1 %2)",
51295129
(Type, unsigned, DeclName))
5130+
WARNING(function_builder_missing_limited_availability, none,
5131+
"function builder %0 does not implement `buildLimitedAvailability`; "
5132+
"this code may crash on earlier versions of the OS",
5133+
(Type))
51305134

51315135
//------------------------------------------------------------------------------
51325136
// MARK: Tuple Shuffle Diagnostics

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ IDENTIFIER(buildEither)
3838
IDENTIFIER(buildExpression)
3939
IDENTIFIER(buildFinalResult)
4040
IDENTIFIER(buildIf)
41+
IDENTIFIER(buildLimitedAvailability)
4142
IDENTIFIER(buildOptional)
4243
IDENTIFIER(callAsFunction)
4344
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 *> {
@@ -495,10 +514,20 @@ class BuilderClosureVisitor
495514
if (!cs || !thenVar || (elseChainVar && !*elseChainVar))
496515
return nullptr;
497516

517+
// If there is a #available in the condition, the 'then' will need to
518+
// be wrapped in a call to buildAvailabilityErasure(_:), if available.
519+
Expr *thenVarRefExpr = buildVarRef(
520+
thenVar, ifStmt->getThenStmt()->getEndLoc());
521+
if (findAvailabilityCondition(ifStmt->getCond()) &&
522+
builderSupports(ctx.Id_buildLimitedAvailability)) {
523+
thenVarRefExpr = buildCallIfWanted(
524+
ifStmt->getThenStmt()->getEndLoc(), ctx.Id_buildLimitedAvailability,
525+
{ thenVarRefExpr }, { Identifier() });
526+
}
527+
498528
// Prepare the `then` operand by wrapping it to produce a chain result.
499529
Expr *thenExpr = buildWrappedChainPayload(
500-
buildVarRef(thenVar, ifStmt->getThenStmt()->getEndLoc()),
501-
payloadIndex, numPayloads, isOptional);
530+
thenVarRefExpr, payloadIndex, numPayloads, isOptional);
502531

503532
// Prepare the `else operand:
504533
Expr *elseExpr;
@@ -1172,6 +1201,35 @@ class BuilderClosureRewriter
11721201
capturedThen.first, {capturedThen.second.front()}));
11731202
ifStmt->setThenStmt(newThen);
11741203

1204+
// Look for a #available condition. If there is one, we need to check
1205+
// that the resulting type of the "then" does refer to any types that
1206+
// are unavailable in the enclosing context.
1207+
//
1208+
// Note that this is for staging in support for
1209+
if (auto availabilityCond = findAvailabilityCondition(ifStmt->getCond())) {
1210+
SourceLoc loc = availabilityCond->getStartLoc();
1211+
Type thenBodyType = solution.simplifyType(
1212+
solution.getType(target.captured.second[0]));
1213+
thenBodyType.findIf([&](Type type) {
1214+
auto nominal = type->getAnyNominal();
1215+
if (!nominal)
1216+
return false;
1217+
1218+
if (auto reason = TypeChecker::checkDeclarationAvailability(
1219+
nominal, loc, dc)) {
1220+
// Note that the problem is with the function builder, not the body.
1221+
// This is for staging only. We want to disable #available in
1222+
// function builders that don't support this operation.
1223+
ctx.Diags.diagnose(
1224+
loc, diag::function_builder_missing_limited_availability,
1225+
builderTransform.builderType);
1226+
return true;
1227+
}
1228+
1229+
return false;
1230+
});
1231+
}
1232+
11751233
if (auto elseBraceStmt =
11761234
dyn_cast_or_null<BraceStmt>(ifStmt->getElseStmt())) {
11771235
// 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)