Skip to content

Commit 9409e9d

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 63593d1 commit 9409e9d

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 *> {
@@ -498,10 +517,20 @@ class BuilderClosureVisitor
498517
if (!cs || !thenVar || (elseChainVar && !*elseChainVar))
499518
return nullptr;
500519

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

506535
// Prepare the `else operand:
507536
Expr *elseExpr;
@@ -1175,6 +1204,35 @@ class BuilderClosureRewriter
11751204
capturedThen.first, {capturedThen.second.front()}));
11761205
ifStmt->setThenStmt(newThen);
11771206

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