Skip to content

Commit f506a67

Browse files
committed
[Function builders] Use buildLimitedAvailability() for #available block
The use of "if #available" in function builders can subvert availability checking if the function builder carries all type information for the values within the "then" block outside of the "else" block. Tighten up the model in two ways: * Check whether the type coming out of an "if #available" references any declarations that are not available in the outer context, to close up the model. * If the function builder provides a buildLimitedAvailability(_:) operation, call that on the result of the "then" block in an "if that it cannot leak out of the "if #available"; if it doesn't, the check above will still fire. Stage this in with a warning so function builders out there in the wild can adapt. We'll upgrade the warning to an error later. Fixes rdar://problem/65021017.
1 parent 63f79b6 commit f506a67

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)