mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Prior to this patch, we pre-checked the result of applying the function-builder transformation, but only when we hadn't already pre-checked the closure before. This causes two problems to arise when the transformation is applied to the same closure along multiple branches of a disjunction. The first is that any expressions that are synthesized by the transformation will not be pre-checked the second time through, which is a problem if we try to apply different builder types to the same closure (we do cache expressions for identical builder types). The second is that the pre-check will rewrite sub-expressions in place *in the synthesized expression*, which means that top-level expressions in the original closure body (including `if` conditions) that are now nested in the synthesized expression will not be rewritten in the original closure and therefore will be encountered in their raw state the second time through. This patch causes all expressions in the original closure body to be pre-checked before doing any other work. We then pre-check the synthesized expression immediately before generating constraints for it in order to set up the AST appropriately for CSGen; this could be skipped if we just synthesized expressions the way that CSGen wants them, but that seems to be somewhat involved. Pre-checking is safe to apply to an expression multiple times, so it's fine if we take this path and then decide not to use a function builder. I've also merged the check for `return` statements into this same walk, which was convenient. Fixes rdar://53325810 at least, and probably also some bugs with applying different function builders to the same closure.
353 lines
7.9 KiB
Swift
353 lines
7.9 KiB
Swift
// RUN: %target-run-simple-swift | %FileCheck %s
|
|
// REQUIRES: executable_test
|
|
|
|
enum Either<T,U> {
|
|
case first(T)
|
|
case second(U)
|
|
}
|
|
|
|
@_functionBuilder
|
|
struct TupleBuilder {
|
|
static func buildBlock<T1, T2>(_ t1: T1, _ t2: T2) -> (T1, T2) {
|
|
return (t1, t2)
|
|
}
|
|
|
|
static func buildBlock<T1, T2, T3>(_ t1: T1, _ t2: T2, _ t3: T3)
|
|
-> (T1, T2, T3) {
|
|
return (t1, t2, t3)
|
|
}
|
|
|
|
static func buildBlock<T1, T2, T3, T4>(_ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4)
|
|
-> (T1, T2, T3, T4) {
|
|
return (t1, t2, t3, t4)
|
|
}
|
|
|
|
static func buildBlock<T1, T2, T3, T4, T5>(
|
|
_ t1: T1, _ t2: T2, _ t3: T3, _ t4: T4, _ t5: T5
|
|
) -> (T1, T2, T3, T4, T5) {
|
|
return (t1, t2, t3, t4, t5)
|
|
}
|
|
|
|
static func buildDo<T>(_ value: T) -> T { return value }
|
|
static func buildIf<T>(_ value: T?) -> T? { return value }
|
|
|
|
static func buildEither<T,U>(first value: T) -> Either<T,U> {
|
|
return .first(value)
|
|
}
|
|
static func buildEither<T,U>(second value: U) -> Either<T,U> {
|
|
return .second(value)
|
|
}
|
|
}
|
|
|
|
func tuplify<T>(_ cond: Bool, @TupleBuilder body: (Bool) -> T) {
|
|
print(body(cond))
|
|
}
|
|
|
|
// CHECK: (17, 3.14159, "Hello, DSL", (["nested", "do"], 6), Optional((2.71828, ["if", "stmt"])))
|
|
let name = "dsl"
|
|
tuplify(true) {
|
|
17
|
|
3.14159
|
|
"Hello, \(name.map { $0.uppercased() }.joined())"
|
|
do {
|
|
["nested", "do"]
|
|
1 + 2 + 3
|
|
}
|
|
if $0 {
|
|
2.71828
|
|
["if", "stmt"]
|
|
}
|
|
}
|
|
|
|
// CHECK: ("Empty optional", nil)
|
|
tuplify(false) {
|
|
"Empty optional"
|
|
if $0 {
|
|
2.71828
|
|
["if", "stmt"]
|
|
}
|
|
}
|
|
|
|
// CHECK: ("chain0", main.Either<(Swift.String, Swift.Double), (Swift.Double, Swift.String)>.second(2.8, "capable"))
|
|
tuplify(false) {
|
|
"chain0"
|
|
if $0 {
|
|
"marginal"
|
|
2.9
|
|
} else {
|
|
2.8
|
|
"capable"
|
|
}
|
|
}
|
|
|
|
// CHECK: ("chain1", nil)
|
|
tuplify(false) {
|
|
"chain1"
|
|
if $0 {
|
|
"marginal"
|
|
2.9
|
|
} else if $0 {
|
|
2.8
|
|
"capable"
|
|
}
|
|
}
|
|
|
|
// CHECK: ("chain2", Optional(main.Either<(Swift.String, Swift.Double), (Swift.Double, Swift.String)>.first("marginal", 2.9)))
|
|
tuplify(true) {
|
|
"chain2"
|
|
if $0 {
|
|
"marginal"
|
|
2.9
|
|
} else if $0 {
|
|
2.8
|
|
"capable"
|
|
}
|
|
}
|
|
|
|
// CHECK: ("chain3", main.Either<main.Either<(Swift.String, Swift.Double), (Swift.Double, Swift.String)>, main.Either<(Swift.Double, Swift.Double), (Swift.String, Swift.String)>>.first(main.Either<(Swift.String, Swift.Double), (Swift.Double, Swift.String)>.first("marginal", 2.9)))
|
|
tuplify(true) {
|
|
"chain3"
|
|
if $0 {
|
|
"marginal"
|
|
2.9
|
|
} else if $0 {
|
|
2.8
|
|
"capable"
|
|
} else if $0 {
|
|
2.8
|
|
1.0
|
|
} else {
|
|
"wild"
|
|
"broken"
|
|
}
|
|
}
|
|
|
|
// CHECK: ("chain4", main.Either<main.Either<main.Either<(Swift.String, Swift.Int), (Swift.String, Swift.Int)>, main.Either<(Swift.String, Swift.Int), (Swift.String, Swift.Int)>>, main.Either<main.Either<(Swift.String, Swift.Int), (Swift.String, Swift.Int)>, (Swift.String, Swift.Int)>>.first
|
|
tuplify(true) {
|
|
"chain4"
|
|
if $0 {
|
|
"0"
|
|
0
|
|
} else if $0 {
|
|
"1"
|
|
1
|
|
} else if $0 {
|
|
"2"
|
|
2
|
|
} else if $0 {
|
|
"3"
|
|
3
|
|
} else if $0 {
|
|
"4"
|
|
4
|
|
} else if $0 {
|
|
"5"
|
|
5
|
|
} else {
|
|
"6"
|
|
6
|
|
}
|
|
}
|
|
|
|
// rdar://50710698
|
|
// CHECK: ("chain5", 8, 9)
|
|
tuplify(true) {
|
|
"chain5"
|
|
#if false
|
|
6
|
|
$0
|
|
#else
|
|
8
|
|
9
|
|
#endif
|
|
}
|
|
|
|
// CHECK: ("getterBuilder", 0, 4, 12)
|
|
@TupleBuilder
|
|
var globalBuilder: (String, Int, Int, Int) {
|
|
"getterBuilder"
|
|
0
|
|
4
|
|
12
|
|
}
|
|
print(globalBuilder)
|
|
|
|
// CHECK: ("funcBuilder", 13, 45.0)
|
|
@TupleBuilder
|
|
func funcBuilder(d: Double) -> (String, Int, Double) {
|
|
"funcBuilder"
|
|
13
|
|
d
|
|
}
|
|
print(funcBuilder(d: 45))
|
|
|
|
struct MemberBuilders {
|
|
@TupleBuilder
|
|
func methodBuilder(_ i: Int) -> (String, Int) {
|
|
"methodBuilder"
|
|
i
|
|
}
|
|
|
|
@TupleBuilder
|
|
static func staticMethodBuilder(_ i: Int) -> (String, Int) {
|
|
"staticMethodBuilder"
|
|
i + 14
|
|
}
|
|
|
|
@TupleBuilder
|
|
var propertyBuilder: (String, Int) {
|
|
"propertyBuilder"
|
|
12
|
|
}
|
|
}
|
|
|
|
// CHECK: ("staticMethodBuilder", 27)
|
|
print(MemberBuilders.staticMethodBuilder(13))
|
|
|
|
let mbuilders = MemberBuilders()
|
|
|
|
// CHECK: ("methodBuilder", 13)
|
|
print(mbuilders.methodBuilder(13))
|
|
|
|
// CHECK: ("propertyBuilder", 12)
|
|
print(mbuilders.propertyBuilder)
|
|
|
|
struct Tagged<Tag, Entity> {
|
|
let tag: Tag
|
|
let entity: Entity
|
|
}
|
|
|
|
protocol Taggable {
|
|
}
|
|
|
|
extension Taggable {
|
|
func tag<Tag>(_ tag: Tag) -> Tagged<Tag, Self> {
|
|
return Tagged(tag: tag, entity: self)
|
|
}
|
|
}
|
|
|
|
extension Int: Taggable { }
|
|
extension String: Taggable { }
|
|
extension Double: Taggable { }
|
|
|
|
@_functionBuilder
|
|
struct TaggedBuilder<Tag> {
|
|
static func buildBlock() -> () { }
|
|
|
|
static func buildBlock<T1>(_ t1: Tagged<Tag, T1>) -> Tagged<Tag, T1> {
|
|
return t1
|
|
}
|
|
|
|
static func buildBlock<T1, T2>(_ t1: Tagged<Tag, T1>, _ t2: Tagged<Tag, T2>) -> (Tagged<Tag, T1>, Tagged<Tag, T2>) {
|
|
return (t1, t2)
|
|
}
|
|
|
|
static func buildBlock<T1, T2, T3>(_ t1: Tagged<Tag, T1>, _ t2: Tagged<Tag, T2>, _ t3: Tagged<Tag, T3>)
|
|
-> (Tagged<Tag, T1>, Tagged<Tag, T2>, Tagged<Tag, T3>) {
|
|
return (t1, t2, t3)
|
|
}
|
|
|
|
static func buildBlock<T1, T2, T3, T4>(_ t1: Tagged<Tag, T1>, _ t2: Tagged<Tag, T2>, _ t3: Tagged<Tag, T3>, _ t4: Tagged<Tag, T4>)
|
|
-> (Tagged<Tag, T1>, Tagged<Tag, T2>, Tagged<Tag, T3>, Tagged<Tag, T4>) {
|
|
return (t1, t2, t3, t4)
|
|
}
|
|
|
|
static func buildBlock<T1, T2, T3, T4, T5>(
|
|
_ t1: Tagged<Tag, T1>, _ t2: Tagged<Tag, T2>, _ t3: Tagged<Tag, T3>, _ t4: Tagged<Tag, T4>, _ t5: Tagged<Tag, T5>
|
|
) -> (Tagged<Tag, T1>, Tagged<Tag, T2>, Tagged<Tag, T3>, Tagged<Tag, T4>, Tagged<Tag, T5>) {
|
|
return (t1, t2, t3, t4, t5)
|
|
}
|
|
|
|
static func buildIf<T>(_ value: Tagged<Tag, T>?) -> Tagged<Tag, T>? { return value }
|
|
}
|
|
|
|
enum Color {
|
|
case red, green, blue
|
|
}
|
|
|
|
func acceptColorTagged<Result>(@TaggedBuilder<Color> body: () -> Result) {
|
|
print(body())
|
|
}
|
|
|
|
struct TagAccepter<Tag> {
|
|
static func acceptTagged<Result>(@TaggedBuilder<Tag> body: () -> Result) {
|
|
print(body())
|
|
}
|
|
}
|
|
|
|
func testAcceptColorTagged(b: Bool, i: Int, s: String, d: Double) {
|
|
// CHECK: Tagged<
|
|
acceptColorTagged {
|
|
i.tag(.red)
|
|
s.tag(.green)
|
|
d.tag(.blue)
|
|
}
|
|
|
|
// CHECK: Tagged<
|
|
TagAccepter<Color>.acceptTagged {
|
|
i.tag(.red)
|
|
s.tag(.green)
|
|
d.tag(.blue)
|
|
}
|
|
|
|
// CHECK: Tagged<
|
|
TagAccepter<Color>.acceptTagged { () -> Tagged<Color, Int> in
|
|
if b {
|
|
return i.tag(Color.green)
|
|
} else {
|
|
return i.tag(Color.blue)
|
|
}
|
|
}
|
|
}
|
|
|
|
testAcceptColorTagged(b: true, i: 17, s: "Hello", d: 3.14159)
|
|
|
|
// rdar://53325810
|
|
|
|
// Test that we don't have problems with expression pre-checking when
|
|
// type-checking an overloaded function-builder call. In particular,
|
|
// we need to make sure that expressions in the closure are pre-checked
|
|
// before we build constraints for them. Note that top-level expressions
|
|
// that need to be rewritten by expression prechecking (such as the operator
|
|
// sequences in the boolean conditions and statements below) won't be
|
|
// rewritten in the original closure body if we just precheck the
|
|
// expressions produced by the function-builder transformation.
|
|
struct ForEach1<Data : RandomAccessCollection, Content> {
|
|
var data: Data
|
|
var content: (Data.Element) -> Content
|
|
|
|
func show() {
|
|
print(content(data.first!))
|
|
print(content(data.last!))
|
|
}
|
|
}
|
|
extension ForEach1 where Data.Element: StringProtocol {
|
|
// Checking this overload shouldn't trigger inappropriate caching that
|
|
// affects checking the next overload.
|
|
init(_ data: Data,
|
|
@TupleBuilder content: @escaping (Data.Element) -> Content) {
|
|
self.init(data: data, content: content)
|
|
}
|
|
}
|
|
extension ForEach1 where Data == Range<Int> {
|
|
// This is the overload we actually want.
|
|
init(_ data: Data,
|
|
@TupleBuilder content: @escaping (Int) -> Content) {
|
|
self.init(data: data, content: content)
|
|
}
|
|
}
|
|
let testForEach1 = ForEach1(-10 ..< 10) { i in
|
|
"testForEach1"
|
|
if i < 0 {
|
|
"begin"
|
|
i < -5
|
|
} else {
|
|
i > 5
|
|
"end"
|
|
}
|
|
}
|
|
testForEach1.show()
|
|
|
|
// CHECK: ("testForEach1", main.Either<(Swift.String, Swift.Bool), (Swift.Bool, Swift.String)>.first("begin", true))
|
|
// CHECK: ("testForEach1", main.Either<(Swift.String, Swift.Bool), (Swift.Bool, Swift.String)>.second(true, "end"))
|