Skip to content

Commit 73577ef

Browse files
committed
Update contents to highlight SE-0335 and SE-0352
1 parent 544f309 commit 73577ef

File tree

6 files changed

+224
-20
lines changed

6 files changed

+224
-20
lines changed

ACKNOWLEDGMENTS.md

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
11
Initially I was challenged with the idea of creating AnyView from Any by https://github.com/glukianets at the end of 2k22.
22

3-
It was fun, I successfully passed the challenge, and now I challenge u, my reader, to implement similar thing without use of `_openExistential` (it is possible tho even more tricky) 😎
3+
It was fun, I successfully passed the challenge, and now I challenge u, my reader, to implement similar thing without use of `_openExistential` (it is possible using tho even more tricky) 😎
4+
5+
> [!TIP]
6+
>
7+
> - Imagine it's Swift 5.0
8+
>
9+
> - You can't use SE-0335 ( `any View`)
10+
>
11+
> - You should use `unsafeBitCast`

Makefile

+90-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
CONFIG = Debug
2-
31
DERIVED_DATA_PATH = ~/.derivedData/$(CONFIG)
42

53
PLATFORM_IOS = iOS Simulator,id=$(call udid_for,iOS,iPhone \d\+ Pro [^M])
@@ -9,57 +7,135 @@ PLATFORM_TVOS = tvOS Simulator,id=$(call udid_for,tvOS,TV)
97
PLATFORM_VISIONOS = visionOS Simulator,id=$(call udid_for,visionOS,Vision)
108
PLATFORM_WATCHOS = watchOS Simulator,id=$(call udid_for,watchOS,Watch)
119

12-
PLATFORM = IOS
13-
DESTINATION = platform="$(PLATFORM_$(PLATFORM))"
10+
ifeq ($(PLATFORM),iOS)
11+
DESTINATION = "$(PLATFORM_IOS)"
12+
else ifeq ($(PLATFORM),macOS)
13+
DESTINATION = "$(PLATFORM_MACOS)"
14+
else ifeq ($(PLATFORM),tvOS)
15+
DESTINATION = "$(PLATFORM_TVOS)"
16+
else ifeq ($(PLATFORM),watchOS)
17+
DESTINATION = "$(PLATFORM_WATCHOS)"
18+
else ifeq ($(PLATFORM),visionOS)
19+
DESTINATION = "$(PLATFORM_VISIONOS)"
20+
else ifeq ($(PLATFORM),macCatalyst)
21+
DESTINATION = "$(PLATFORM_MAC_CATALYST)"
22+
else
23+
DESTINATION = __unsupported__
24+
endif
1425

1526
PLATFORM_ID = $(shell echo "$(DESTINATION)" | sed -E "s/.+,id=(.+)/\1/")
1627

17-
WORKSPACE = Package.xcworkspace
18-
SCHEME = swift-existential-container
28+
SCHEME = Unspecified
29+
WORKSPACE = ".swiftpm/xcode/package.xcworkspace"
1930

20-
XCODEBUILD_ARGUMENT = test
31+
ifeq ($(BEAUTIFY),true)
32+
XCBEAUTIFY = xcbeautify
33+
XCBEAUTIFY_COMMAND = xcbeautify
34+
else ifeq ($(BEAUTIFY),quiet)
35+
XCBEAUTIFY = xcbeautify
36+
XCBEAUTIFY_COMMAND = xcbeautify --quiet
37+
else
38+
XCBEAUTIFY = __do_not_beautify__
39+
endif
2140

2241
XCODEBUILD_FLAGS = \
2342
-configuration $(CONFIG) \
2443
-derivedDataPath $(DERIVED_DATA_PATH) \
25-
-destination $(DESTINATION) \
44+
-destination=$(DESTINATION) \
2645
-scheme "$(SCHEME)" \
2746
-skipMacroValidation \
2847
-workspace $(WORKSPACE)
2948

30-
XCODEBUILD_COMMAND = xcodebuild $(XCODEBUILD_ARGUMENT) $(XCODEBUILD_FLAGS)
49+
XCODEBUILD_COMMAND = xcodebuild $(COMMAND) $(XCODEBUILD_FLAGS)
3150

32-
ifneq ($(strip $(shell which xcbeautify)),)
33-
XCODEBUILD = set -o pipefail && $(XCODEBUILD_COMMAND) | xcbeautify --quiet
51+
ifneq ($(strip $(shell which $(XCBEAUTIFY))),)
52+
XCODEBUILD = set -o pipefail && $(XCODEBUILD_COMMAND) | $(XCBEAUTIFY_COMMAND)
3453
else
3554
XCODEBUILD = $(XCODEBUILD_COMMAND)
3655
endif
3756

3857
TEST_RUNNER_CI = $(CI)
3958

4059
warm-simulator:
60+
@echo "Running warm-simulator for $(PLATFORM)"
4161
@test "$(PLATFORM_ID)" != "" \
4262
&& xcrun simctl boot $(PLATFORM_ID) \
4363
&& open -a Simulator --args -CurrentDeviceUDID $(PLATFORM_ID) \
4464
|| exit 0
4565

4666
xcodebuild: warm-simulator
67+
@echo "Running xcodebuild for $(PLATFORM)"
68+
@echo " Workspace: $(WORKSPACE)"
69+
@echo " Scheme: $(SCHEME)"
70+
@echo " Config: $(CONFIG)"
71+
@echo " Destination: $(DESTINATION)"
72+
@echo " DerivedData: $(DERIVED_DATA_PATH)"
4773
$(XCODEBUILD)
4874

4975
# Workaround for debugging Swift Testing tests: https://github.com/cpisciotta/xcbeautify/issues/313
5076
xcodebuild-raw: warm-simulator
77+
@echo "Running xcodebuild-raw for $(PLATFORM)"
78+
@echo " Workspace: $(WORKSPACE)"
79+
@echo " Scheme: $(SCHEME)"
80+
@echo " Config: $(CONFIG)"
81+
@echo " Destination: $(DESTINATION)"
82+
@echo " DerivedData: $(DERIVED_DATA_PATH)"
5183
$(XCODEBUILD_COMMAND)
5284

5385
build-for-library-evolution:
86+
@echo "Running build-for-library-evolution for $(SCHEME)"
5487
swift build \
5588
-q \
5689
-c release \
57-
--target "$(SCHEME)" \
90+
--target ${SCHEME} \
5891
-Xswiftc -emit-module-interface \
5992
-Xswiftc -enable-library-evolution
6093

61-
.PHONY: build-for-library-evolution format warm-simulator xcodebuild xcodebuild-raw
94+
benchmark:
95+
@echo "Running benchmark for $(SCHEME)"
96+
swift run --configuration release $(SCHEME)
97+
98+
swift-format:
99+
@echo "Running swift-format"
100+
find . \
101+
-path '*/Documentation.docc' -prune -o \
102+
-name '*.swift' \
103+
-not -path '*/.*' -print0 \
104+
| xargs -0 swift format --ignore-unparsable-files --in-place
105+
106+
DOC_WARNINGS = $(shell \
107+
xcodebuild clean docbuild \
108+
-scheme "$(SCHEME)" \
109+
-destination platform="$(DESTINATION)" \
110+
-quiet 2>&1 \
111+
| grep "couldn't be resolved to known documentation" \
112+
| sed 's|$(PWD)|.|g' \
113+
| tr '\n' '\1' \
114+
)
115+
116+
test-docs:
117+
@echo "Running test-docs for $(SCHEME) [$(PLATFORM)]"
118+
@test "$(DOC_WARNINGS)" = "" \
119+
|| (echo "xcodebuild docbuild failed:\n\n$(DOC_WARNINGS)" | tr '\1' '\n' \
120+
&& exit 1)
121+
122+
github-build-docs:
123+
@echo "Running github-build-docs for $(SCHEME)"
124+
@chmod +x '.scripts/github-build-docs'
125+
SCHEME=$(SCHEME) ./.scripts/github-build-docs
126+
127+
.PHONY: build-for-library-evolution format warm-simulator xcodebuild xcodebuild-raw test-docs
62128

63129
define udid_for
64-
$(shell xcrun simctl list devices available '$(1)' | grep '$(2)' | sort -r | head -1 | awk -F '[()]' '{ print $$(NF-3) }')
130+
$(shell \
131+
xcrun simctl list devices available '$(1)' \
132+
| grep '$(2)' \
133+
| sort -r \
134+
| head -1 \
135+
| awk -F '[()]' '{ print $$(NF-3) }' \
136+
)
65137
endef
138+
139+
# simple action for testing if Makefile is valid
140+
ping:
141+
@echo "pong 🏓"

README.md

+37-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# swift-existential-container
22

3-
[![CI](https://github.com/CaptureContext/swift-existential-container/actions/workflows/ci.yml/badge.svg)](https://github.com/CaptureContext/swift-existential-container/actions/workflows/ci.yml) [![SwiftPM 5.6](https://img.shields.io/badge/swiftpm-6.0-ED523F.svg?style=flat)](https://swift.org/download/) ![Platforms](https://img.shields.io/badge/platforms-all-ED523F.svg?style=flat) [![@capture_context](https://img.shields.io/badge/contact-@capture__context-1DA1F2.svg?style=flat&logo=twitter)](https://twitter.com/capture_context)
3+
[![CI](https://github.com/CaptureContext/swift-existential-container/actions/workflows/ci.yml/badge.svg)](https://github.com/CaptureContext/swift-existential-container/actions/workflows/ci.yml) [![SwiftPM 6.0](https://img.shields.io/badge/swiftpm-6.0-ED523F.svg?style=flat)](https://swift.org/download/) ![Platforms](https://img.shields.io/badge/platforms-all-ED523F.svg?style=flat) [![@capture_context](https://img.shields.io/badge/contact-@capture__context-1DA1F2.svg?style=flat&logo=twitter)](https://twitter.com/capture_context)
44

5-
Package for opening existentials with ease.
5+
> [!NOTE]
6+
> _You don't need this package or explicit `_openExistential` now, because [SE-0352](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0352-implicit-open-existentials.md) introduced implicit open existentials and any of examples can be acheived with plain generic functions that now can open existentials implicitly. Example with AnyView can be simplified even more with [SE-0335](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0335-existential-any.md)_ 💁‍♂️
67
78
## Usage
89

@@ -89,6 +90,38 @@ extension AnyView {
8990
}
9091
```
9192

93+
### [SE-0352](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0352-implicit-open-existentials.md)
94+
95+
```swift
96+
extension AnyView {
97+
@MainActor
98+
init?(any: Any) {
99+
func open<T: View>(_ view: T) -> AnyView { .init(view) }
100+
guard let anyView = any as? (any View) else { return nil }
101+
self = open(anyView)
102+
}
103+
}
104+
```
105+
106+
### [SE-0335](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0335-existential-any.md)
107+
108+
```swift
109+
extension View {
110+
fileprivate func eraseToAnyView() -> AnyView { AnyView(self) }
111+
}
112+
```
113+
114+
It's enough, but the call site will look like this `(anyValue as? (any View))?.eraseToAnyView()` which is not very ergonomic and you may still want an AnyView extension
115+
116+
```swift
117+
extension AnyView {
118+
@MainActor
119+
init?(any: Any) {
120+
guard let anyView = any as? (any View) else { return nil }
121+
self = anyView.eraseToAnyView()
122+
}
123+
}
124+
```
92125

93126
### APIs
94127

@@ -132,7 +165,7 @@ If you use SwiftPM for your project structure, add ExistentialContainer to your
132165
```swift
133166
.package(
134167
url: "[email protected]:capturecontext/swift-existential-container.git",
135-
.upToNextMinor(from: "1.0.0")
168+
.upToNextMajor(from: "1.0.1")
136169
)
137170
```
138171

@@ -141,7 +174,7 @@ or via HTTPS
141174
```swift
142175
.package(
143176
url: "https://github.com:capturecontext/swift-existential-container.git",
144-
.upToNextMinor("1.0.0")
177+
.upToNextMajor("1.0.1")
145178
)
146179
```
147180

Tests/ExistentialContainerTests/ExistentialContainerTests.swift

+43-1
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,24 @@ struct ExistentialContainerTests {
1414
let anyText: Any = text
1515
#expect(AnyView(any: anyText) != nil)
1616
}
17-
#endif
1817

18+
@MainActor
19+
@Test
20+
func testSwiftUIAnyViewSE0352() async throws {
21+
let text = Text("")
22+
let anyText: Any = text
23+
#expect(AnyView(anySE0352: anyText) != nil)
24+
}
25+
26+
@MainActor
27+
@Test
28+
func testSwiftUIAnyViewSE0335() async throws {
29+
let text = Text("")
30+
let anyText: Any = text
31+
let anyView: AnyView? = (anyText as? (any View))?.__eraseToAnyViewSE0335()
32+
#expect(anyView != nil)
33+
}
34+
#endif
1935

2036
@Test
2137
func testMutuallyDependantTypes() async throws {
@@ -29,6 +45,32 @@ struct ExistentialContainerTests {
2945

3046
#expect(component.model === model)
3147
}
48+
49+
@Test
50+
func testMutuallyDependantTypesSE0352() async throws {
51+
let component = SomeComponent()
52+
let model = SomeComponent.Model()
53+
54+
let anyComponent: any UIComponent = component
55+
let anyModel: any UIComponentModel = model
56+
57+
setAnyModelSE0352(anyModel, to: anyComponent)
58+
59+
#expect(component.model === model)
60+
}
61+
62+
@Test
63+
func testMutuallyDependantTypesSE0335() async throws {
64+
let component = SomeComponent()
65+
let model = SomeComponent.Model()
66+
67+
let anyComponent: any UIComponent = component
68+
let anyModel: any UIComponentModel = model
69+
70+
anyComponent.setAnyModelSE0335(anyModel)
71+
72+
#expect(component.model === model)
73+
}
3274
}
3375

3476
private final class SomeComponent: UIComponent {

Tests/ExistentialContainerTests/Helpers/AnyView+.swift

+25
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,29 @@ extension AnyView {
1818
}
1919
}
2020

21+
// MARK: - SE-0352
22+
23+
extension AnyView {
24+
@MainActor
25+
init?(anySE0352 any: Any) {
26+
func open<T: View>(_ view: T) -> AnyView { .init(view) }
27+
guard let anyView = any as? (any View) else { return nil }
28+
self = open(anyView)
29+
}
30+
}
31+
32+
// MARK: - SE-0335
33+
34+
extension AnyView {
35+
@MainActor
36+
init?(anySE0335 any: Any) {
37+
guard let anyView = any as? (any View) else { return nil }
38+
self = anyView.__eraseToAnyViewSE0335()
39+
}
40+
}
41+
42+
private extension View {
43+
func __eraseToAnyViewSE0335() -> AnyView { .init(self) }
44+
}
45+
2146
#endif

Tests/ExistentialContainerTests/Helpers/UIComponent+.swift

+20
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,23 @@ extension ExistentialBox<(any UIComponent)> {
1515
ExistentialBox<UIComponentConforming>.open(content) { $0.setAnyModel(model) }
1616
}
1717
}
18+
19+
// MARK: - SE-0352
20+
21+
func setAnyModelSE0352<
22+
Component: UIComponent,
23+
Model: UIComponentModel
24+
>(
25+
_ model: Model,
26+
to component: Component
27+
) {
28+
(model as? Component.Model).map(component.setModel)
29+
}
30+
31+
// MARK: - SE-0335
32+
33+
extension UIComponent {
34+
func setAnyModelSE0335(_ model: any UIComponentModel) {
35+
(model as? Model).map(setModel)
36+
}
37+
}

0 commit comments

Comments
 (0)