Skip to content

Commit 335b766

Browse files
committed
[ResultBuilders] Enable SE-0348 buildPartialBlock by default.
Enables SE-0348 `buildPartialBlock` feature by default. The frontend flag is kept for compatibility with existing clients, but is now ineffective. Also includes some improved error messages for invalid result builder call sites.
1 parent 7390f7d commit 335b766

14 files changed

+331
-597
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6122,9 +6122,6 @@ WARNING(result_builder_missing_limited_availability, none,
61226122
"result builder %0 does not implement 'buildLimitedAvailability'; "
61236123
"this code may crash on earlier versions of the OS",
61246124
(Type))
6125-
ERROR(result_builder_static_buildblock, none,
6126-
"result builder must provide at least one static 'buildBlock' "
6127-
"method", ())
61286125
ERROR(result_builder_static_buildblock_or_buildpartialblock, none,
61296126
"result builder must provide at least one static 'buildBlock' "
61306127
"method, or both 'buildPartialBlock(first:)' and "

include/swift/AST/EducationalNotes.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ EDUCATIONAL_NOTES(type_cannot_conform, "protocol-type-non-conformance.md")
7373
EDUCATIONAL_NOTES(unlabeled_trailing_closure_deprecated,
7474
"trailing-closure-matching.md")
7575

76-
EDUCATIONAL_NOTES(result_builder_static_buildblock,
76+
EDUCATIONAL_NOTES(result_builder_static_buildblock_or_buildpartialblock,
7777
"result-builder-methods.md")
7878
EDUCATIONAL_NOTES(result_builder_missing_limited_availability,
7979
"result-builder-methods.md")

include/swift/Basic/LangOptions.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,6 @@ namespace swift {
349349
/// Enable experimental 'move only' features.
350350
bool EnableExperimentalMoveOnly = false;
351351

352-
/// Enable experimental pairwise `buildBlock` for result builders.
353-
bool EnableExperimentalPairwiseBuildBlock = false;
354-
355352
/// Enable variadic generics.
356353
bool EnableExperimentalVariadicGenerics = false;
357354

lib/Frontend/CompilerInvocation.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -470,9 +470,6 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
470470
Opts.EnableExperimentalMoveOnly |=
471471
Args.hasArg(OPT_enable_experimental_move_only);
472472

473-
Opts.EnableExperimentalPairwiseBuildBlock |=
474-
Args.hasArg(OPT_enable_experimental_pairwise_build_block);
475-
476473
Opts.EnableInferPublicSendable |=
477474
Args.hasFlag(OPT_enable_infer_public_concurrent_value,
478475
OPT_disable_infer_public_concurrent_value,

lib/Sema/BuilderTransform.cpp

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,7 @@ class BuilderClosureVisitor
374374
// If the builder supports `buildPartialBlock(first:)` and
375375
// `buildPartialBlock(accumulated:next:)`, use this to combine
376376
// subexpressions pairwise.
377-
if (ctx.LangOpts.EnableExperimentalPairwiseBuildBlock &&
378-
!expressions.empty() &&
377+
if (!expressions.empty() &&
379378
builderSupports(ctx.Id_buildPartialBlock, {ctx.Id_first},
380379
/*checkAvailability*/ true) &&
381380
builderSupports(ctx.Id_buildPartialBlock,
@@ -398,31 +397,10 @@ class BuilderClosureVisitor
398397
{ctx.Id_accumulated, ctx.Id_next});
399398
}
400399
}
401-
// TODO: Remove support for the old method name,
402-
// `buildBlock(combining:into:)`.
403-
else if (ctx.LangOpts.EnableExperimentalPairwiseBuildBlock &&
404-
!expressions.empty() &&
405-
builderSupports(ctx.Id_buildBlock,
406-
{ctx.Id_combining, ctx.Id_into})) {
407-
// NOTE: The current implementation uses one-way constraints in between
408-
// subexpressions. It's functionally equivalent to the following:
409-
// let v0 = Builder.buildBlock(arg_0)
410-
// let v1 = Builder.buildBlock(combining: arg_1, into: v0)
411-
// ...
412-
// return Builder.buildBlock(combining: arg_n, into: ...)
413-
call = buildCallIfWanted(braceStmt->getStartLoc(), ctx.Id_buildBlock,
414-
{expressions.front()}, /*argLabels=*/{});
415-
for (auto *expr : llvm::drop_begin(expressions)) {
416-
call = buildCallIfWanted(braceStmt->getStartLoc(), ctx.Id_buildBlock,
417-
{expr, new (ctx) OneWayExpr(call)},
418-
{ctx.Id_combining, ctx.Id_into});
419-
}
420-
}
421400
// If `buildBlock` does not exist at this point, it could be the case that
422401
// `buildPartialBlock` did not have the sufficient availability for this
423402
// call site. Diagnose it.
424-
else if (ctx.LangOpts.EnableExperimentalPairwiseBuildBlock &&
425-
!builderSupports(ctx.Id_buildBlock)) {
403+
else if (!builderSupports(ctx.Id_buildBlock)) {
426404
ctx.Diags.diagnose(
427405
braceStmt->getStartLoc(),
428406
diag::result_builder_missing_available_buildpartialblock,

lib/Sema/TypeCheckAttr.cpp

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3280,9 +3280,7 @@ void AttributeChecker::visitResultBuilderAttr(ResultBuilderAttr *attr) {
32803280
bool supportsBuildBlock = TypeChecker::typeSupportsBuilderOp(
32813281
nominal->getDeclaredType(), nominal, ctx.Id_buildBlock,
32823282
/*argLabels=*/{}, &potentialMatches);
3283-
bool isBuildPartialBlockFeatureEnabled =
3284-
ctx.LangOpts.EnableExperimentalPairwiseBuildBlock;
3285-
bool supportsBuildPartialBlock = isBuildPartialBlockFeatureEnabled &&
3283+
bool supportsBuildPartialBlock =
32863284
TypeChecker::typeSupportsBuilderOp(
32873285
nominal->getDeclaredType(), nominal,
32883286
ctx.Id_buildPartialBlock,
@@ -3296,9 +3294,7 @@ void AttributeChecker::visitResultBuilderAttr(ResultBuilderAttr *attr) {
32963294
{
32973295
auto diag = diagnose(
32983296
nominal->getLoc(),
3299-
isBuildPartialBlockFeatureEnabled
3300-
? diag::result_builder_static_buildblock_or_buildpartialblock
3301-
: diag::result_builder_static_buildblock);
3297+
diag::result_builder_static_buildblock_or_buildpartialblock);
33023298

33033299
// If there were no close matches, propose adding a stub.
33043300
SourceLoc buildInsertionLoc;

test/Constraints/result_builder.swift

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,3 +919,258 @@ func test_custom_tilde_equals_operator_matching() {
919919
}
920920
}
921921
}
922+
923+
struct Values<T> {
924+
var values: T
925+
926+
init(values: T) {
927+
self.values = values
928+
}
929+
930+
func map<R>(_ f: (T) -> R) -> Values<R> {
931+
.init(values: f(values))
932+
}
933+
}
934+
935+
@resultBuilder
936+
enum NestedTupleBuilder {
937+
static func buildPartialBlock<T>(first x: T) -> Values<T> {
938+
.init(values: x)
939+
}
940+
941+
static func buildPartialBlock<T, U>(
942+
accumulated: Values<T>, next: U
943+
) -> Values<(T, U)> {
944+
.init(values: (accumulated.values, next))
945+
}
946+
}
947+
948+
extension Values {
949+
init(@NestedTupleBuilder nested values: () -> Self) {
950+
self = values()
951+
}
952+
}
953+
954+
let nestedValues = Values(nested: {
955+
1
956+
"2"
957+
3.0
958+
"yes"
959+
})
960+
print(nestedValues)
961+
962+
@resultBuilder
963+
enum NestedTupleBuilder_Not {
964+
@available(*, unavailable)
965+
static func buildPartialBlock<T>(first x: T) -> Values<T> {
966+
.init(values: x)
967+
}
968+
969+
@available(*, unavailable)
970+
static func buildPartialBlock<T, U>(
971+
accumulated: Values<T>, next: U
972+
) -> Values<(T, U)> {
973+
.init(values: (accumulated.values, next))
974+
}
975+
976+
#if os(macOS)
977+
@available(macOS 9999, *)
978+
static func buildPartialBlock(first x: Never) -> Values<Never> {
979+
fatalError()
980+
}
981+
982+
@available(macOS 9999, *)
983+
static func buildPartialBlock(
984+
accumulated: Values<Never>, next: Never
985+
) -> Values<Never> {
986+
fatalError()
987+
}
988+
#endif
989+
990+
// This one will be called because no `buildPartialBlock` is available.
991+
static func buildBlock(_ x: Any...) -> Values<[Any]> {
992+
.init(values: x)
993+
}
994+
}
995+
996+
extension Values {
997+
init(@NestedTupleBuilder_Not nested_not values: () -> Self) {
998+
self = values()
999+
}
1000+
}
1001+
1002+
let nestedValues_not = Values(nested_not: {
1003+
1
1004+
"2"
1005+
3.0
1006+
"yes"
1007+
})
1008+
print(nestedValues_not)
1009+
1010+
// CHECK: Values<Array<Any>>(values: [1, "2", 3.0, "yes"])
1011+
1012+
@resultBuilder
1013+
enum FlatTupleBuilder {
1014+
static func buildExpression<T>(_ x: T) -> Values<T> {
1015+
.init(values: x)
1016+
}
1017+
1018+
static func buildPartialBlock<T>(first x: Values<T>) -> Values<T> {
1019+
.init(values: x.values)
1020+
}
1021+
1022+
static func buildPartialBlock<T, N>(
1023+
accumulated: Values<T>,
1024+
next: Values<N>
1025+
) -> Values<(T, N)> {
1026+
.init(values: (accumulated.values, next.values))
1027+
}
1028+
1029+
static func buildPartialBlock<T0, T1, N>(
1030+
accumulated: Values<(T0, T1)>,
1031+
next: Values<N>
1032+
) -> Values<(T0, T1, N)> {
1033+
.init(values: (accumulated.values.0, accumulated.values.1, next.values))
1034+
}
1035+
1036+
static func buildPartialBlock<T0, T1, T2, N>(
1037+
accumulated: Values<(T0, T1, T2)>,
1038+
next: Values<N>
1039+
) -> Values<(T0, T1, T2, N)> {
1040+
.init(values: (accumulated.values.0, accumulated.values.1, accumulated.values.2, next.values))
1041+
}
1042+
1043+
static func buildPartialBlock<T0, T1, T2, T3, N>(
1044+
accumulated: Values<(T0, T1, T2, T3)>,
1045+
next: Values<N>
1046+
) -> Values<(T0, T1, T2, T3, N)> {
1047+
.init(values: (accumulated.values.0, accumulated.values.1, accumulated.values.2, accumulated.values.3, next.values))
1048+
}
1049+
1050+
static func buildBlock(_ x: Never...) -> Values<()> {
1051+
assert(x.isEmpty, "I should never be called unless it's nullary")
1052+
return .init(values: ())
1053+
}
1054+
1055+
static func buildEither<T>(first: T) -> T {
1056+
first
1057+
}
1058+
1059+
static func buildEither<T>(second: T) -> T {
1060+
second
1061+
}
1062+
1063+
static func buildOptional<T>(_ x: Values<T>?) -> Values<T?> {
1064+
x?.map { $0 } ?? .init(values: nil)
1065+
}
1066+
1067+
static func buildLimitedAvailability<T>(_ x: Values<T>) -> Values<T> {
1068+
x
1069+
}
1070+
}
1071+
1072+
extension Values {
1073+
init(@FlatTupleBuilder flat values: () -> Self) {
1074+
self = values()
1075+
}
1076+
}
1077+
1078+
let flatValues0 = Values(flat: {})
1079+
print(flatValues0)
1080+
// CHECK: Values<()>(values: ())
1081+
1082+
let flatValues1 = Values(flat: {
1083+
1
1084+
"2"
1085+
3.0
1086+
})
1087+
print(flatValues1)
1088+
// CHECK: Values<(Int, String, Double)>(values: (1, "2", 3.0))
1089+
1090+
let flatValues2 = Values(flat: {
1091+
1
1092+
"2"
1093+
let y = 3.0 + 4.0
1094+
#if false
1095+
"not gonna happen"
1096+
#endif
1097+
if true {
1098+
"yes"
1099+
} else {
1100+
"no"
1101+
}
1102+
#warning("Beware of pairwise block building")
1103+
#if true
1104+
if false {
1105+
"nah"
1106+
}
1107+
if #available(*) {
1108+
5.0
1109+
}
1110+
#endif
1111+
})
1112+
print(flatValues2)
1113+
1114+
// CHECK: Values<(Int, String, String, Optional<String>, Optional<Double>)>(values: (1, "2", "yes", nil, Optional(5.0)))
1115+
1116+
struct Nil: CustomStringConvertible {
1117+
var description: String {
1118+
"nil"
1119+
}
1120+
}
1121+
struct Cons<Head, Tail>: CustomStringConvertible {
1122+
var head: Head
1123+
var tail: Tail
1124+
1125+
var description: String {
1126+
"(cons \(String(reflecting: head)) \(tail))"
1127+
}
1128+
}
1129+
1130+
@resultBuilder
1131+
enum ListBuilder {
1132+
static func buildBlock() -> Nil {
1133+
Nil()
1134+
}
1135+
1136+
static func buildPartialBlock<T>(first x: T) -> Cons<T, Nil> {
1137+
.init(head: x, tail: Nil())
1138+
}
1139+
1140+
static func buildPartialBlock<New, T>(accumulated: T, next: New) -> Cons<New, T> {
1141+
.init(head: next, tail: accumulated)
1142+
}
1143+
1144+
static func buildBlock<T>(_ x: T...) -> [T] {
1145+
fatalError("I should never be called!")
1146+
}
1147+
}
1148+
1149+
func list<T>(@ListBuilder f: () -> T) -> T {
1150+
f()
1151+
}
1152+
1153+
let list0 = list {}
1154+
print(list0)
1155+
// CHECK: nil
1156+
1157+
let list1 = list { "1" }
1158+
print(list1)
1159+
// Check: (cons 1 nil)
1160+
1161+
let list2 = list {
1162+
1
1163+
2
1164+
}
1165+
print(list2)
1166+
// CHECK: (cons 2 (cons 1 nil))
1167+
let list3 = list {
1168+
1
1169+
list {
1170+
2.0
1171+
"3"
1172+
}
1173+
"4"
1174+
}
1175+
print(list3)
1176+
// CHECK: (cons "4" (cons (cons "3" (cons 2.0 nil)) (cons 1 nil)))

0 commit comments

Comments
 (0)