Skip to content

Commit db2a9a4

Browse files
authored
Merge pull request #60803 from hamishknight/part-and-partial
2 parents b6878ce + cebcbb0 commit db2a9a4

File tree

5 files changed

+203
-35
lines changed

5 files changed

+203
-35
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,33 @@ enum class FreeTypeVariableBinding {
107107
UnresolvedType
108108
};
109109

110+
/// Describes whether or not a result builder method is supported.
111+
struct ResultBuilderOpSupport {
112+
enum Classification {
113+
Unsupported,
114+
Unavailable,
115+
Supported
116+
};
117+
Classification Kind;
118+
119+
ResultBuilderOpSupport(Classification Kind) : Kind(Kind) {}
120+
121+
/// Returns whether or not the builder method is supported. If
122+
/// \p requireAvailable is true, an unavailable method will be considered
123+
/// unsupported.
124+
bool isSupported(bool requireAvailable) const {
125+
switch (Kind) {
126+
case Unsupported:
127+
return false;
128+
case Unavailable:
129+
return !requireAvailable;
130+
case Supported:
131+
return true;
132+
}
133+
llvm_unreachable("Unhandled case in switch!");
134+
}
135+
};
136+
110137
namespace constraints {
111138

112139
struct ResultBuilder {
@@ -115,7 +142,9 @@ struct ResultBuilder {
115142
/// An implicit variable that represents `Self` type of the result builder.
116143
VarDecl *BuilderSelf;
117144
Type BuilderType;
118-
llvm::SmallDenseMap<DeclName, bool> SupportedOps;
145+
146+
/// Cache of supported result builder operations.
147+
llvm::SmallDenseMap<DeclName, ResultBuilderOpSupport> SupportedOps;
119148

120149
Identifier BuildOptionalId;
121150

@@ -143,6 +172,13 @@ struct ResultBuilder {
143172

144173
bool supportsOptional() { return supports(getBuildOptionalId()); }
145174

175+
/// Checks whether the `buildPartialBlock` method is supported.
176+
bool supportsBuildPartialBlock(bool checkAvailability);
177+
178+
/// Checks whether the builder can use `buildPartialBlock` to combine
179+
/// expressions, instead of `buildBlock`.
180+
bool canUseBuildPartialBlock();
181+
146182
Expr *buildCall(SourceLoc loc, Identifier fnName,
147183
ArrayRef<Expr *> argExprs,
148184
ArrayRef<Identifier> argLabels) const;

lib/Sema/BuilderTransform.cpp

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -432,12 +432,7 @@ class BuilderClosureVisitor
432432
// If the builder supports `buildPartialBlock(first:)` and
433433
// `buildPartialBlock(accumulated:next:)`, use this to combine
434434
// subexpressions pairwise.
435-
if (!expressions.empty() &&
436-
builder.supports(ctx.Id_buildPartialBlock, {ctx.Id_first},
437-
/*checkAvailability*/ true) &&
438-
builder.supports(ctx.Id_buildPartialBlock,
439-
{ctx.Id_accumulated, ctx.Id_next},
440-
/*checkAvailability*/ true)) {
435+
if (!expressions.empty() && builder.canUseBuildPartialBlock()) {
441436
// NOTE: The current implementation uses one-way constraints in between
442437
// subexpressions. It's functionally equivalent to the following:
443438
// let v0 = Builder.buildPartialBlock(first: arg_0)
@@ -1087,12 +1082,7 @@ class ResultBuilderTransform
10871082
// If the builder supports `buildPartialBlock(first:)` and
10881083
// `buildPartialBlock(accumulated:next:)`, use this to combine
10891084
// sub-expressions pairwise.
1090-
if (!buildBlockArguments.empty() &&
1091-
builder.supports(ctx.Id_buildPartialBlock, {ctx.Id_first},
1092-
/*checkAvailability*/ true) &&
1093-
builder.supports(ctx.Id_buildPartialBlock,
1094-
{ctx.Id_accumulated, ctx.Id_next},
1095-
/*checkAvailability*/ true)) {
1085+
if (!buildBlockArguments.empty() && builder.canUseBuildPartialBlock()) {
10961086
// let v0 = Builder.buildPartialBlock(first: arg_0)
10971087
// let v1 = Builder.buildPartialBlock(accumulated: v0, next: arg_1)
10981088
// ...
@@ -2777,11 +2767,22 @@ std::vector<ReturnStmt *> TypeChecker::findReturnStatements(AnyFunctionRef fn) {
27772767
return precheck.getReturnStmts();
27782768
}
27792769

2780-
bool TypeChecker::typeSupportsBuilderOp(
2770+
ResultBuilderOpSupport TypeChecker::checkBuilderOpSupport(
27812771
Type builderType, DeclContext *dc, Identifier fnName,
2782-
ArrayRef<Identifier> argLabels, SmallVectorImpl<ValueDecl *> *allResults,
2783-
bool checkAvailability) {
2772+
ArrayRef<Identifier> argLabels, SmallVectorImpl<ValueDecl *> *allResults) {
2773+
2774+
auto isUnavailable = [&](Decl *D) -> bool {
2775+
if (AvailableAttr::isUnavailable(D))
2776+
return true;
2777+
2778+
auto loc = extractNearestSourceLoc(dc);
2779+
auto context = ExportContext::forFunctionBody(dc, loc);
2780+
return TypeChecker::checkDeclarationAvailability(D, context).hasValue();
2781+
};
2782+
27842783
bool foundMatch = false;
2784+
bool foundUnavailable = false;
2785+
27852786
SmallVector<ValueDecl *, 4> foundDecls;
27862787
dc->lookupQualified(
27872788
builderType, DeclNameRef(fnName),
@@ -2800,17 +2801,12 @@ bool TypeChecker::typeSupportsBuilderOp(
28002801
continue;
28012802
}
28022803

2803-
// If we are checking availability, the candidate must have enough
2804-
// availability in the calling context.
2805-
if (checkAvailability) {
2806-
if (AvailableAttr::isUnavailable(func))
2807-
continue;
2808-
if (TypeChecker::checkDeclarationAvailability(
2809-
func, ExportContext::forFunctionBody(
2810-
dc, extractNearestSourceLoc(dc))))
2811-
continue;
2804+
// Check if the the candidate has a suitable availability for the
2805+
// calling context.
2806+
if (isUnavailable(func)) {
2807+
foundUnavailable = true;
2808+
continue;
28122809
}
2813-
28142810
foundMatch = true;
28152811
break;
28162812
}
@@ -2819,7 +2815,24 @@ bool TypeChecker::typeSupportsBuilderOp(
28192815
if (allResults)
28202816
allResults->append(foundDecls.begin(), foundDecls.end());
28212817

2822-
return foundMatch;
2818+
if (!foundMatch) {
2819+
return foundUnavailable ? ResultBuilderOpSupport::Unavailable
2820+
: ResultBuilderOpSupport::Unsupported;
2821+
}
2822+
// If the builder type itself isn't available, don't consider any builder
2823+
// method available.
2824+
if (auto *D = builderType->getAnyNominal()) {
2825+
if (isUnavailable(D))
2826+
return ResultBuilderOpSupport::Unavailable;
2827+
}
2828+
return ResultBuilderOpSupport::Supported;
2829+
}
2830+
2831+
bool TypeChecker::typeSupportsBuilderOp(
2832+
Type builderType, DeclContext *dc, Identifier fnName,
2833+
ArrayRef<Identifier> argLabels, SmallVectorImpl<ValueDecl *> *allResults) {
2834+
return checkBuilderOpSupport(builderType, dc, fnName, argLabels, allResults)
2835+
.isSupported(/*requireAvailable*/ false);
28232836
}
28242837

28252838
Type swift::inferResultBuilderComponentType(NominalTypeDecl *builder) {
@@ -2978,18 +2991,43 @@ ResultBuilder::ResultBuilder(ConstraintSystem *CS, DeclContext *DC,
29782991
}
29792992
}
29802993

2994+
bool ResultBuilder::supportsBuildPartialBlock(bool checkAvailability) {
2995+
auto &ctx = DC->getASTContext();
2996+
return supports(ctx.Id_buildPartialBlock, {ctx.Id_first},
2997+
checkAvailability) &&
2998+
supports(ctx.Id_buildPartialBlock, {ctx.Id_accumulated, ctx.Id_next},
2999+
checkAvailability);
3000+
}
3001+
3002+
bool ResultBuilder::canUseBuildPartialBlock() {
3003+
// If buildPartialBlock doesn't exist at all, we can't use it.
3004+
if (!supportsBuildPartialBlock(/*checkAvailability*/ false))
3005+
return false;
3006+
3007+
// If buildPartialBlock exists and is available, use it.
3008+
if (supportsBuildPartialBlock(/*checkAvailability*/ true))
3009+
return true;
3010+
3011+
// We have buildPartialBlock, but it is unavailable. We can however still
3012+
// use it if buildBlock is also unavailable.
3013+
auto &ctx = DC->getASTContext();
3014+
return supports(ctx.Id_buildBlock) &&
3015+
!supports(ctx.Id_buildBlock, /*labels*/ {},
3016+
/*checkAvailability*/ true);
3017+
}
3018+
29813019
bool ResultBuilder::supports(Identifier fnBaseName,
29823020
ArrayRef<Identifier> argLabels,
29833021
bool checkAvailability) {
29843022
DeclName name(DC->getASTContext(), fnBaseName, argLabels);
29853023
auto known = SupportedOps.find(name);
2986-
if (known != SupportedOps.end()) {
2987-
return known->second;
2988-
}
3024+
if (known != SupportedOps.end())
3025+
return known->second.isSupported(checkAvailability);
29893026

2990-
return SupportedOps[name] = TypeChecker::typeSupportsBuilderOp(
2991-
BuilderType, DC, fnBaseName, argLabels, /*allResults*/ {},
2992-
checkAvailability);
3027+
auto support = TypeChecker::checkBuilderOpSupport(
3028+
BuilderType, DC, fnBaseName, argLabels, /*allResults*/ {});
3029+
SupportedOps.insert({name, support});
3030+
return support.isSupported(checkAvailability);
29933031
}
29943032

29953033
Expr *ResultBuilder::buildCall(SourceLoc loc, Identifier fnName,

lib/Sema/TypeChecker.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1234,10 +1234,20 @@ UnresolvedMemberExpr *getUnresolvedMemberChainBase(Expr *expr);
12341234
/// Checks whether a result builder type has a well-formed result builder
12351235
/// method with the given name. If provided and non-empty, the argument labels
12361236
/// are verified against any candidates.
1237+
ResultBuilderOpSupport
1238+
checkBuilderOpSupport(Type builderType, DeclContext *dc, Identifier fnName,
1239+
ArrayRef<Identifier> argLabels = {},
1240+
SmallVectorImpl<ValueDecl *> *allResults = nullptr);
1241+
1242+
/// Checks whether a result builder type has a well-formed result builder
1243+
/// method with the given name. If provided and non-empty, the argument labels
1244+
/// are verified against any candidates.
1245+
///
1246+
/// This will return \c true even if the builder method is unavailable. Use
1247+
/// \c checkBuilderOpSupport if availability should be checked.
12371248
bool typeSupportsBuilderOp(Type builderType, DeclContext *dc, Identifier fnName,
12381249
ArrayRef<Identifier> argLabels = {},
1239-
SmallVectorImpl<ValueDecl *> *allResults = nullptr,
1240-
bool checkAvailability = false);
1250+
SmallVectorImpl<ValueDecl *> *allResults = nullptr);
12411251

12421252
/// Forces all changes specified by the module's access notes file to be
12431253
/// applied to this declaration. It is safe to call this function more than

test/Constraints/result_builder_availability.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,59 @@ tuplifyWithAvailabilityErasure(true) { cond in
167167
globalFuncAvailableOn10_52()
168168
}
169169
}
170+
171+
// rdar://97533700 – Make sure we can prefer an unavailable buildPartialBlock if
172+
// buildBlock also isn't available.
173+
174+
@resultBuilder
175+
struct UnavailableBuildPartialBlock {
176+
static func buildPartialBlock(first: Int) -> Int { 0 }
177+
178+
@available(*, unavailable)
179+
static func buildPartialBlock(accumulated: Int, next: Int) -> Int { 0 }
180+
181+
static func buildBlock(_ x: Int...) -> Int { 0 }
182+
}
183+
184+
@UnavailableBuildPartialBlock
185+
func testUnavailableBuildPartialBlock() -> Int {
186+
// We can use buildBlock here.
187+
2
188+
3
189+
}
190+
191+
@resultBuilder
192+
struct UnavailableBuildPartialBlockAndBuildBlock {
193+
@available(*, unavailable)
194+
static func buildPartialBlock(first: Int) -> Int { 0 }
195+
// expected-note@-1 {{'buildPartialBlock(first:)' has been explicitly marked unavailable here}}
196+
197+
static func buildPartialBlock(accumulated: Int, next: Int) -> Int { 0 }
198+
199+
@available(*, unavailable)
200+
static func buildBlock(_ x: Int...) -> Int { 0 }
201+
}
202+
203+
// We can still use buildPartialBlock here as both are unavailable.
204+
@UnavailableBuildPartialBlockAndBuildBlock
205+
func testUnavailableBuildPartialBlockAndBuildBlock() -> Int {
206+
// expected-error@-1 {{'buildPartialBlock(first:)' is unavailable}}
207+
2
208+
3
209+
}
210+
211+
@available(*, unavailable)
212+
@resultBuilder
213+
struct UnavailableBuilderWithPartialBlock { // expected-note {{'UnavailableBuilderWithPartialBlock' has been explicitly marked unavailable here}}
214+
@available(*, unavailable)
215+
static func buildPartialBlock(first: String) -> Int { 0 }
216+
static func buildPartialBlock(accumulated: Int, next: Int) -> Int { 0 }
217+
static func buildBlock(_ x: Int...) -> Int { 0 }
218+
}
219+
220+
@UnavailableBuilderWithPartialBlock // expected-error {{'UnavailableBuilderWithPartialBlock' is unavailable}}
221+
func testUnavailableBuilderWithPartialBlock() -> Int {
222+
// The builder itself is unavailable, so we can still opt for buildPartialBlock.
223+
2 // expected-error {{cannot convert value of type 'Int' to expected argument type 'String'}}
224+
3
225+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// RUN: %target-typecheck-verify-swift -enable-bare-slash-regex -target %target-cpu-apple-macosx12.0
2+
3+
// REQUIRES: OS=macosx
4+
5+
import RegexBuilder
6+
7+
// rdar://97533700 – Make sure we can emit an availability diagnostic here.
8+
9+
let _ = Regex { // expected-error {{'Regex' is only available in macOS 13.0 or newer}}
10+
// expected-error@-1 {{'init(_:)' is only available in macOS 13.0 or newer}}
11+
// expected-note@-2 2{{add 'if #available' version check}}
12+
13+
Capture {} // expected-error {{'Capture' is only available in macOS 13.0 or newer}}
14+
// expected-error@-1 {{'init(_:)' is only available in macOS 13.0 or newer}}
15+
// expected-note@-2 2{{add 'if #available' version check}}
16+
}
17+
18+
let _ = Regex { // expected-error {{'Regex' is only available in macOS 13.0 or newer}}
19+
// expected-error@-1 {{'init(_:)' is only available in macOS 13.0 or newer}}
20+
// expected-error@-2 {{'buildPartialBlock(accumulated:next:)' is only available in macOS 13.0 or newer}}
21+
// expected-note@-3 3{{add 'if #available' version check}}
22+
23+
/abc/ // expected-error {{'Regex' is only available in macOS 13.0 or newer}}
24+
// expected-note@-1 {{add 'if #available' version check}}
25+
26+
/def/ // expected-error {{'Regex' is only available in macOS 13.0 or newer}}
27+
// expected-note@-1 {{add 'if #available' version check}}
28+
}

0 commit comments

Comments
 (0)