Skip to content

Commit 6981fb1

Browse files
authored
Merge pull request #25 from RougeWare/bugfix/20-Lazy-property-wrapper-eagerly-evaluated
Bodge guarding against issue #20
2 parents 77d063c + 93bdf54 commit 6981fb1

File tree

6 files changed

+141
-47
lines changed

6 files changed

+141
-47
lines changed

Package.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,7 @@ let package = Package(
3232
.testTarget(
3333
name: "LazyContainersTests",
3434
dependencies: ["LazyContainers"]),
35-
]
35+
],
36+
37+
swiftLanguageVersions: [.v5]
3638
)

Sources/LazyContainers/LazyContainers.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
//
22
// LazyContainers.swift
3-
// Lazy in Swift 5.1
3+
// Lazy in Swift 5.2
44
//
55
// Created by Ben Leggiero on 2018-05-04.
6-
// Version 3.1.0 (last edited 2019-08-30)
6+
// Version 4.0.0 (last edited 2020-08-01)
77
// Copyright Ben Leggiero © 2020
88
// https://github.com/RougeWare/Swift-Lazy-Patterns/blob/master/LICENSE.txt
99
//
@@ -217,6 +217,13 @@ public struct Lazy<Value>: LazyContainer {
217217
/// Same as `init(initializer:)`, but allows you to use property wrapper andor autoclosure semantics
218218
///
219219
/// - Parameter initializer: The closure that will be called the very first time a value is needed
220+
@available(swift, // https://github.com/RougeWare/Swift-Lazy-Patterns/issues/20
221+
introduced: 5.3,
222+
message: """
223+
Due to changes introduced in Swift 5.3, property wrappers can now be passed their initial value lazily, through the language assignment syntax. This initializer requires that behavior to work properly.
224+
For Swift 5.2.x and earlier, I recommend you don't use `Lazy` as a property wrapper, since Swift 5.2.x property wrappers can't guarantee lazy evaluation.
225+
This is a real downer for me, but at least it appears to have been a fixable bug, rather than a problem with the core feature itself.
226+
""")
220227
public init(wrappedValue initializer: @autoclosure @escaping Initializer<Value>) {
221228
self.init(initializer: initializer)
222229
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// GitHubIssue20Tests.swift
3+
// LazyContainersTests
4+
//
5+
// Created by Gabe Shahbazian on 2020-07-29.
6+
//
7+
8+
import XCTest
9+
@testable import LazyContainers
10+
11+
12+
13+
#if swift(>=5.3)
14+
var shouldNotRun = false
15+
16+
class ShouldNotInit {
17+
init() {
18+
shouldNotRun = true
19+
}
20+
}
21+
22+
23+
24+
/// Guards against issue #20
25+
/// https://github.com/RougeWare/Swift-Lazy-Patterns/issues/20
26+
final class GitHubIssue20Tests: XCTestCase {
27+
28+
@Lazy
29+
var lazyShouldNotRun = ShouldNotInit()
30+
31+
func testLazyInitWithPropertyWrapper() {
32+
XCTAssertFalse(shouldNotRun)
33+
}
34+
35+
static var allTests = [
36+
("testLazyInitWithPropertyWrapper", testLazyInitWithPropertyWrapper)
37+
]
38+
}
39+
#endif

Tests/LazyContainersTests/LazyContainersTests.swift

Lines changed: 84 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
//
22
// LazyContainersTests.swift
3+
// LazyContainersTests
34
//
45
// Created by Ben Leggiero on 2019-08-19
5-
// Copyright BH-0-PD © 2019 - https://github.com/BlueHuskyStudios/Licenses/blob/master/Licenses/BH-0-PD.txt
6+
// Copyright Ben Leggiero © 2020
7+
// https://github.com/RougeWare/Swift-Lazy-Patterns/blob/master/LICENSE.txt
68
//
79

810

@@ -12,22 +14,29 @@ import XCTest
1214

1315

1416

15-
var sideEffect: String?
16-
17+
var sideEffectA: String?
1718
func makeLazyA() -> String {
18-
sideEffect = "Side effect A"
19+
sideEffectA = "Side effect A1"
1920
return "lAzy"
2021
}
2122

23+
var sideEffectB: String?
24+
func makeLazyB() -> String {
25+
sideEffectB = "Side effect B"
26+
return "Lazy B (this time with side-effects)"
27+
}
28+
2229

2330

2431
final class LazyContainersTests: XCTestCase {
2532

33+
#if swift(>=5.3)
2634
@Lazy(initializer: makeLazyA)
27-
var lazyInitWithPropertyWrapper: String
35+
var lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect: String
36+
#endif
2837

2938
var lazyInitTraditionally = Lazy<String>() {
30-
sideEffect = "Side effect B"
39+
sideEffectA = "Side effect A2"
3140
return "lazy B"
3241
}
3342

@@ -43,7 +52,8 @@ final class LazyContainersTests: XCTestCase {
4352

4453

4554
override func setUp() {
46-
sideEffect = nil
55+
sideEffectA = nil
56+
sideEffectB = nil
4757
}
4858

4959

@@ -55,43 +65,60 @@ final class LazyContainersTests: XCTestCase {
5565

5666
// MARK: - `Lazy`
5767

58-
func testLazyInitWithPropertyWrapper() {
59-
XCTAssertEqual(sideEffect, nil)
60-
XCTAssertFalse(_lazyInitWithPropertyWrapper.isInitialized)
61-
XCTAssertEqual(sideEffect, nil)
62-
XCTAssertEqual("lAzy", lazyInitWithPropertyWrapper)
63-
XCTAssertEqual(sideEffect, "Side effect A")
64-
XCTAssertTrue(_lazyInitWithPropertyWrapper.isInitialized)
65-
XCTAssertEqual(sideEffect, "Side effect A")
66-
XCTAssertEqual("lAzy", lazyInitWithPropertyWrapper)
67-
XCTAssertEqual(sideEffect, "Side effect A")
68-
XCTAssertTrue(_lazyInitWithPropertyWrapper.isInitialized)
69-
XCTAssertEqual(sideEffect, "Side effect A")
70-
XCTAssertEqual("lAzy", lazyInitWithPropertyWrapper)
71-
XCTAssertEqual(sideEffect, "Side effect A")
72-
XCTAssertTrue(_lazyInitWithPropertyWrapper.isInitialized)
73-
XCTAssertEqual(sideEffect, "Side effect A")
68+
#if swift(>=5.3)
69+
func testLazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect() {
70+
XCTAssertEqual(sideEffectA, nil)
71+
XCTAssertFalse(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
72+
XCTAssertEqual(sideEffectA, nil)
73+
XCTAssertEqual("lAzy", lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect)
74+
XCTAssertEqual(sideEffectA, "Side effect A1")
75+
XCTAssertTrue(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
76+
XCTAssertEqual(sideEffectA, "Side effect A1")
77+
XCTAssertEqual("lAzy", lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect)
78+
XCTAssertEqual(sideEffectA, "Side effect A1")
79+
XCTAssertTrue(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
80+
XCTAssertEqual(sideEffectA, "Side effect A1")
81+
XCTAssertEqual("lAzy", lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect)
82+
XCTAssertEqual(sideEffectA, "Side effect A1")
83+
XCTAssertTrue(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
84+
XCTAssertEqual(sideEffectA, "Side effect A1")
7485

75-
lazyInitWithPropertyWrapper = "MAnual"
86+
lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect = "MAnual"
7687

77-
XCTAssertEqual(sideEffect, "Side effect A")
78-
XCTAssertTrue(_lazyInitWithPropertyWrapper.isInitialized)
79-
XCTAssertEqual(sideEffect, "Side effect A")
80-
XCTAssertEqual("MAnual", lazyInitWithPropertyWrapper)
81-
XCTAssertEqual(sideEffect, "Side effect A")
82-
XCTAssertTrue(_lazyInitWithPropertyWrapper.isInitialized)
83-
XCTAssertEqual(sideEffect, "Side effect A")
84-
XCTAssertEqual("MAnual", lazyInitWithPropertyWrapper)
85-
XCTAssertEqual(sideEffect, "Side effect A")
86-
XCTAssertTrue(_lazyInitWithPropertyWrapper.isInitialized)
87-
XCTAssertEqual(sideEffect, "Side effect A")
88-
XCTAssertEqual("MAnual", lazyInitWithPropertyWrapper)
89-
XCTAssertEqual(sideEffect, "Side effect A")
90-
XCTAssertTrue(_lazyInitWithPropertyWrapper.isInitialized)
91-
XCTAssertEqual(sideEffect, "Side effect A")
88+
XCTAssertEqual(sideEffectA, "Side effect A1")
89+
XCTAssertTrue(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
90+
XCTAssertEqual(sideEffectA, "Side effect A1")
91+
XCTAssertEqual("MAnual", lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect)
92+
XCTAssertEqual(sideEffectA, "Side effect A1")
93+
XCTAssertTrue(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
94+
XCTAssertEqual(sideEffectA, "Side effect A1")
95+
XCTAssertEqual("MAnual", lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect)
96+
XCTAssertEqual(sideEffectA, "Side effect A1")
97+
XCTAssertTrue(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
98+
XCTAssertEqual(sideEffectA, "Side effect A1")
99+
XCTAssertEqual("MAnual", lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect)
100+
XCTAssertEqual(sideEffectA, "Side effect A1")
101+
XCTAssertTrue(_lazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect.isInitialized)
102+
XCTAssertEqual(sideEffectA, "Side effect A1")
92103
}
93104

94105

106+
func testLazyInitWithPropertyWrapperAndSideEffect() {
107+
108+
struct Test {
109+
@Lazy
110+
var lazyInitWithPropertyWrapperAndSideEffect = makeLazyB()
111+
}
112+
113+
114+
let test = Test()
115+
116+
XCTAssertNil(sideEffectB, "@Lazy eagerly evaluated its initial value")
117+
XCTAssertEqual(test.lazyInitWithPropertyWrapperAndSideEffect, "Lazy B (this time with side-effects)")
118+
}
119+
#endif
120+
121+
95122
func testLazyInitTraditionally() {
96123
XCTAssertFalse(lazyInitTraditionally.isInitialized)
97124
XCTAssertEqual("lazy B", lazyInitTraditionally.wrappedValue)
@@ -249,9 +276,16 @@ final class LazyContainersTests: XCTestCase {
249276
XCTAssertEqual("Manual F", functionalLazyInitTraditionally.wrappedValue)
250277
XCTAssertTrue(functionalLazyInitTraditionally.isInitialized)
251278
}
252-
253-
static var allTests = [
254-
("testLazyInitWithPropertyWrapper", testLazyInitWithPropertyWrapper),
279+
280+
#if swift(>=5.3)
281+
static let testsWhichRequireSwift5_3 = [
282+
("testLazyInitWithPropertyWrapperWithCustomInitializerAndSideEffect", testLazyInitWithPropertyWrapperAndCustomInitializerWithSideEffect),
283+
("testLazyInitWithPropertyWrapperAndSideEffect", testLazyInitWithPropertyWrapperAndSideEffect),
284+
]
285+
#endif
286+
287+
288+
static let testsWhichWorkBeforeSwift5_3 = [
255289
("testLazyInitTraditionally", testLazyInitTraditionally),
256290

257291
("testResettableLazyInitWithPropertyWrapper", testResettableLazyInitWithPropertyWrapper),
@@ -260,4 +294,12 @@ final class LazyContainersTests: XCTestCase {
260294
("testFunctionalLazyInitWithPropertyWrapper", testFunctionalLazyInitWithPropertyWrapper),
261295
("testFunctionalLazyInitTraditionally", testFunctionalLazyInitTraditionally),
262296
]
297+
298+
299+
#if swift(>=5.3)
300+
static let allTests = testsWhichRequireSwift5_3 + testsWhichWorkBeforeSwift5_3
301+
#else
302+
@inline(__always)
303+
static let allTests = testsWhichWorkBeforeSwift5_3
304+
#endif
263305
}

Tests/LazyContainersTests/XCTestManifests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import XCTest
44
public func allTests() -> [XCTestCaseEntry] {
55
return [
66
testCase(LazyContainersTests.allTests),
7+
testCase(GitHubIssue20Tests.allTests),
78
]
89
}
910
#endif

Tests/LinuxMain.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import XCTest
22

33
import LazyContainersTests
44

5-
var tests = [XCTestCaseEntry]()
6-
tests += LazyContainersTests.allTests()
5+
let tests: [XCTestCaseEntry] = [
6+
LazyContainersTests.allTests,
7+
GitHubIssue20Tests.allTests,
8+
]
9+
710
XCTMain(tests)

0 commit comments

Comments
 (0)