Skip to content

Commit cebcbb0

Browse files
committed
[CS] Improve diagnostics when buildPartialBlock is unavailable
If `buildBlock` is also unavailable, or the builder itself is unavailable, continue to solve using `buildPartialBlock` to get better diagnostics. This behavior technically differs from what is specified in SE-0348, but only affects the invalid case where no builder methods are available to use. In particular, this improves diagnostics for RegexComponentBuilder when the deployment target is too low. Previously we would try to solve using `buildBlock` (as `buildPartialBlock` is unavailable), but RegexComponentBuilder only defines `buildBlock` for the empty body case, leading to unhelpful diagnostics that ultimately preferred not to use the result builder at all. rdar://97533700
1 parent dd1da22 commit cebcbb0

File tree

4 files changed

+124
-12
lines changed

4 files changed

+124
-12
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,13 @@ struct ResultBuilder {
172172

173173
bool supportsOptional() { return supports(getBuildOptionalId()); }
174174

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+
175182
Expr *buildCall(SourceLoc loc, Identifier fnName,
176183
ArrayRef<Expr *> argExprs,
177184
ArrayRef<Identifier> argLabels) const;

lib/Sema/BuilderTransform.cpp

Lines changed: 33 additions & 12 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
// ...
@@ -2822,6 +2812,12 @@ ResultBuilderOpSupport TypeChecker::checkBuilderOpSupport(
28222812
return foundUnavailable ? ResultBuilderOpSupport::Unavailable
28232813
: ResultBuilderOpSupport::Unsupported;
28242814
}
2815+
// If the builder type itself isn't available, don't consider any builder
2816+
// method available.
2817+
if (auto *D = builderType->getAnyNominal()) {
2818+
if (isUnavailable(D))
2819+
return ResultBuilderOpSupport::Unavailable;
2820+
}
28252821
return ResultBuilderOpSupport::Supported;
28262822
}
28272823

@@ -2988,6 +2984,31 @@ ResultBuilder::ResultBuilder(ConstraintSystem *CS, DeclContext *DC,
29882984
}
29892985
}
29902986

2987+
bool ResultBuilder::supportsBuildPartialBlock(bool checkAvailability) {
2988+
auto &ctx = DC->getASTContext();
2989+
return supports(ctx.Id_buildPartialBlock, {ctx.Id_first},
2990+
checkAvailability) &&
2991+
supports(ctx.Id_buildPartialBlock, {ctx.Id_accumulated, ctx.Id_next},
2992+
checkAvailability);
2993+
}
2994+
2995+
bool ResultBuilder::canUseBuildPartialBlock() {
2996+
// If buildPartialBlock doesn't exist at all, we can't use it.
2997+
if (!supportsBuildPartialBlock(/*checkAvailability*/ false))
2998+
return false;
2999+
3000+
// If buildPartialBlock exists and is available, use it.
3001+
if (supportsBuildPartialBlock(/*checkAvailability*/ true))
3002+
return true;
3003+
3004+
// We have buildPartialBlock, but it is unavailable. We can however still
3005+
// use it if buildBlock is also unavailable.
3006+
auto &ctx = DC->getASTContext();
3007+
return supports(ctx.Id_buildBlock) &&
3008+
!supports(ctx.Id_buildBlock, /*labels*/ {},
3009+
/*checkAvailability*/ true);
3010+
}
3011+
29913012
bool ResultBuilder::supports(Identifier fnBaseName,
29923013
ArrayRef<Identifier> argLabels,
29933014
bool checkAvailability) {

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)