Skip to content

Commit 59ad670

Browse files
authored
Merge pull request #40799 from rxwei/pairwise-buildblock
[ResultBuilders] `buildBlock(combining:into:)` for pairwise combination.
2 parents ad95f59 + b6e679f commit 59ad670

File tree

6 files changed

+248
-4
lines changed

6 files changed

+248
-4
lines changed

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ IDENTIFIER_WITH_NAME(code_, "_code")
4747
IDENTIFIER(CodingKeys)
4848
IDENTIFIER(codingPath)
4949
IDENTIFIER(combine)
50+
IDENTIFIER(combining)
5051
IDENTIFIER_(Concurrency)
5152
IDENTIFIER(container)
5253
IDENTIFIER(Context)

include/swift/Basic/LangOptions.h

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

338+
/// Enable experimental pairwise `buildBlock` for result builders.
339+
bool EnableExperimentalPairwiseBuildBlock = false;
340+
338341
/// Disable the implicit import of the _Concurrency module.
339342
bool DisableImplicitConcurrencyModuleImport =
340343
!SWIFT_IMPLICIT_CONCURRENCY_IMPORT;

include/swift/Option/FrontendOptions.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,10 @@ def enable_experimental_eager_clang_module_diagnostics :
295295
Flag<["-"], "enable-experimental-eager-clang-module-diagnostics">,
296296
HelpText<"Enable experimental eager diagnostics reporting on the importability of all referenced C, C++, and Objective-C libraries">;
297297

298+
def enable_experimental_pairwise_build_block :
299+
Flag<["-"], "enable-experimental-pairwise-build-block">,
300+
HelpText<"Enable experimental pairwise 'buildBlock' for result builders">;
301+
298302
def enable_resilience : Flag<["-"], "enable-resilience">,
299303
HelpText<"Deprecated, use -enable-library-evolution instead">;
300304
}

lib/Frontend/CompilerInvocation.cpp

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

457+
Opts.EnableExperimentalPairwiseBuildBlock |=
458+
Args.hasArg(OPT_enable_experimental_pairwise_build_block);
459+
457460
Opts.EnableInferPublicSendable |=
458461
Args.hasFlag(OPT_enable_infer_public_concurrent_value,
459462
OPT_disable_infer_public_concurrent_value,

lib/Sema/BuilderTransform.cpp

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -367,10 +367,34 @@ class BuilderClosureVisitor
367367
if (!cs || hadError)
368368
return nullptr;
369369

370-
// Call Builder.buildBlock(... args ...)
371-
auto call = buildCallIfWanted(braceStmt->getStartLoc(),
372-
ctx.Id_buildBlock, expressions,
373-
/*argLabels=*/{ });
370+
Expr *call = nullptr;
371+
// If the builder supports `buildBlock(combining:into:)`, use this to
372+
// combine subexpressions pairwise.
373+
if (ctx.LangOpts.EnableExperimentalPairwiseBuildBlock &&
374+
!expressions.empty() &&
375+
builderSupports(ctx.Id_buildBlock, {ctx.Id_combining, ctx.Id_into})) {
376+
// NOTE: The current implementation uses one-way constraints in between
377+
// subexpressions. It's functionally equivalent to the following:
378+
// let v0 = Builder.buildBlock(arg_0)
379+
// let v1 = Builder.buildBlock(combining: arg_1, into: v0)
380+
// ...
381+
// return Builder.buildBlock(combining: arg_n, into: ...)
382+
call = buildCallIfWanted(braceStmt->getStartLoc(), ctx.Id_buildBlock,
383+
{expressions.front()}, /*argLabels=*/{});
384+
for (auto *expr : llvm::drop_begin(expressions)) {
385+
call = buildCallIfWanted(braceStmt->getStartLoc(), ctx.Id_buildBlock,
386+
{expr, new (ctx) OneWayExpr(call)},
387+
{ctx.Id_combining, ctx.Id_into});
388+
}
389+
}
390+
// Otherwise, call `buildBlock` on all subexpressions.
391+
else {
392+
// Call Builder.buildBlock(... args ...)
393+
call = buildCallIfWanted(braceStmt->getStartLoc(),
394+
ctx.Id_buildBlock, expressions,
395+
/*argLabels=*/{ });
396+
}
397+
374398
if (!call)
375399
return nullptr;
376400

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-pairwise-build-block) | %FileCheck %s
2+
// REQUIRES: executable_test
3+
4+
struct Values<T> {
5+
var values: T
6+
7+
init(values: T) {
8+
self.values = values
9+
}
10+
11+
func map<R>(_ f: (T) -> R) -> Values<R> {
12+
.init(values: f(values))
13+
}
14+
}
15+
16+
@resultBuilder
17+
enum NestedTupleBuilder {
18+
static func buildBlock<T>(_ x: T) -> Values<T> {
19+
.init(values: x)
20+
}
21+
22+
static func buildBlock<T, U>(
23+
combining next: U, into combined: Values<T>
24+
) -> Values<(T, U)> {
25+
.init(values: (combined.values, next))
26+
}
27+
}
28+
29+
extension Values {
30+
init(@NestedTupleBuilder nested values: () -> Self) {
31+
self = values()
32+
}
33+
}
34+
35+
let nestedValues = Values(nested: {
36+
1
37+
"2"
38+
3.0
39+
"yes"
40+
})
41+
print(nestedValues)
42+
43+
// CHECK: Values<(((Int, String), Double), String)>(values: (((1, "2"), 3.0), "yes"))
44+
45+
@resultBuilder
46+
enum FlatTupleBuilder {
47+
static func buildExpression<T>(_ x: T) -> Values<T> {
48+
.init(values: x)
49+
}
50+
51+
static func buildBlock<T>(_ x: Values<T>) -> Values<T> {
52+
.init(values: x.values)
53+
}
54+
55+
static func buildBlock<T, N>(
56+
combining new: Values<N>,
57+
into combined: Values<T>
58+
) -> Values<(T, N)> {
59+
.init(values: (combined.values, new.values))
60+
}
61+
62+
static func buildBlock<T0, T1, N>(
63+
combining new: Values<N>,
64+
into combined: Values<(T0, T1)>
65+
) -> Values<(T0, T1, N)> {
66+
.init(values: (combined.values.0, combined.values.1, new.values))
67+
}
68+
69+
static func buildBlock<T0, T1, T2, N>(
70+
combining new: Values<N>,
71+
into combined: Values<(T0, T1, T2)>
72+
) -> Values<(T0, T1, T2, N)> {
73+
.init(values: (combined.values.0, combined.values.1, combined.values.2, new.values))
74+
}
75+
76+
static func buildBlock<T0, T1, T2, T3, N>(
77+
combining new: Values<N>,
78+
into combined: Values<(T0, T1, T2, T3)>
79+
) -> Values<(T0, T1, T2, T3, N)> {
80+
.init(values: (combined.values.0, combined.values.1, combined.values.2, combined.values.3, new.values))
81+
}
82+
83+
static func buildBlock(_ x: Never...) -> Values<()> {
84+
assert(x.isEmpty, "I should never be called unless it's nullary")
85+
return .init(values: ())
86+
}
87+
88+
static func buildEither<T>(first: T) -> T {
89+
first
90+
}
91+
92+
static func buildEither<T>(second: T) -> T {
93+
second
94+
}
95+
96+
static func buildOptional<T>(_ x: Values<T>?) -> Values<T?> {
97+
x?.map { $0 } ?? .init(values: nil)
98+
}
99+
100+
static func buildLimitedAvailability<T>(_ x: Values<T>) -> Values<T> {
101+
x
102+
}
103+
}
104+
105+
extension Values {
106+
init(@FlatTupleBuilder flat values: () -> Self) {
107+
self = values()
108+
}
109+
}
110+
111+
let flatValues0 = Values(flat: {})
112+
print(flatValues0)
113+
// CHECK: Values<()>(values: ())
114+
115+
let flatValues1 = Values(flat: {
116+
1
117+
"2"
118+
3.0
119+
})
120+
print(flatValues1)
121+
// CHECK: Values<(Int, String, Double)>(values: (1, "2", 3.0))
122+
123+
let flatValues2 = Values(flat: {
124+
1
125+
"2"
126+
let y = 3.0 + 4.0
127+
#if false
128+
"not gonna happen"
129+
#endif
130+
if true {
131+
"yes"
132+
} else {
133+
"no"
134+
}
135+
#warning("Beware of pairwise block building")
136+
#if true
137+
if false {
138+
"nah"
139+
}
140+
if #available(SwiftStdlib 5.0, *) {
141+
5.0
142+
}
143+
#endif
144+
})
145+
print(flatValues2)
146+
147+
// CHECK: Values<(Int, String, String, Optional<String>, Optional<Double>)>(values: (1, "2", "yes", nil, Optional(5.0)))
148+
149+
struct Nil: CustomStringConvertible {
150+
var description: String {
151+
"nil"
152+
}
153+
}
154+
struct Cons<Head, Tail>: CustomStringConvertible {
155+
var head: Head
156+
var tail: Tail
157+
158+
var description: String {
159+
"(cons \(String(reflecting: head)) \(tail))"
160+
}
161+
}
162+
163+
@resultBuilder
164+
enum ListBuilder {
165+
static func buildBlock() -> Nil {
166+
Nil()
167+
}
168+
169+
static func buildBlock<T>(_ x: T) -> Cons<T, Nil> {
170+
.init(head: x, tail: Nil())
171+
}
172+
173+
static func buildBlock<New, T>(combining new: New, into combined: T) -> Cons<New, T> {
174+
.init(head: new, tail: combined)
175+
}
176+
177+
static func buildBlock<T>(_ x: T...) -> [T] {
178+
fatalError("I should never be called!")
179+
}
180+
}
181+
182+
func list<T>(@ListBuilder f: () -> T) -> T {
183+
f()
184+
}
185+
186+
let list0 = list {}
187+
print(list0)
188+
// CHECK: nil
189+
190+
let list1 = list { "1" }
191+
print(list1)
192+
// Check: (cons 1 nil)
193+
194+
let list2 = list {
195+
1
196+
2
197+
}
198+
print(list2)
199+
// CHECK: (cons 2 (cons 1 nil))
200+
let list3 = list {
201+
1
202+
list {
203+
2.0
204+
"3"
205+
}
206+
"4"
207+
}
208+
print(list3)
209+
// CHECK: (cons "4" (cons (cons "3" (cons 2.0 nil)) (cons 1 nil)))

0 commit comments

Comments
 (0)