Skip to content

Commit cbd5c0d

Browse files
Build On Swift 6.2 (#2)
### Description Updates certain callsites and asynchronous captures to build without warnings or errors using Swift 6.2. This is a source-breaking change, as it adds a few new requirements for API users. Specifically it marks some callbacks as either `@Sendable` or `@MainActor` which shouldn't be too large a change for most cases. ### Related Issues * CodeEditApp/CodeEdit#2116 ### Checklist - [x] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md) - [x] The issues this PR addresses are related to each other - [x] My changes generate no new warnings - [x] My code builds and runs on my machine - [x] My changes are all related to the related issue above - [x] I documented my code ### Screenshots N/A
1 parent c42d5b2 commit cbd5c0d

File tree

5 files changed

+74
-40
lines changed

5 files changed

+74
-40
lines changed

Sources/WelcomeWindow/Model/NSDocumentController+Extensions.swift

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ extension NSDocumentController {
2525
public func createFileDocumentWithDialog(
2626
configuration: DocumentSaveDialogConfiguration = .init(),
2727
onDialogPresented: @escaping () -> Void = {},
28-
onCompletion: @escaping () -> Void = {},
28+
onCompletion: @MainActor @escaping () -> Void = {},
2929
onCancel: @escaping () -> Void = {}
3030
) {
3131
_createDocument(
@@ -53,7 +53,7 @@ extension NSDocumentController {
5353
public func createFolderDocumentWithDialog(
5454
configuration: DocumentSaveDialogConfiguration,
5555
onDialogPresented: @escaping () -> Void = {},
56-
onCompletion: @escaping () -> Void = {},
56+
onCompletion: @MainActor @escaping () -> Void = {},
5757
onCancel: @escaping () -> Void = {}
5858
) {
5959
_createDocument(
@@ -100,7 +100,7 @@ extension NSDocumentController {
100100
mode: SaveMode,
101101
configuration: DocumentSaveDialogConfiguration,
102102
onDialogPresented: @escaping () -> Void,
103-
onCompletion: @escaping () -> Void,
103+
onCompletion: @MainActor @escaping () -> Void,
104104
onCancel: @escaping () -> Void
105105
) {
106106
// 1 ────────────────────────────────────────────────────────────────
@@ -177,8 +177,8 @@ extension NSDocumentController {
177177
public func openDocumentWithDialog(
178178
configuration: DocumentOpenDialogConfiguration = DocumentOpenDialogConfiguration(),
179179
onDialogPresented: @escaping () -> Void = {},
180-
onCompletion: @escaping () -> Void = {},
181-
onCancel: @escaping () -> Void = {}
180+
onCompletion: @MainActor @escaping () -> Void = {},
181+
onCancel: @MainActor @escaping () -> Void = {}
182182
) {
183183
let panel = NSOpenPanel()
184184
panel.title = configuration.title
@@ -189,12 +189,14 @@ extension NSDocumentController {
189189
panel.level = .modalPanel
190190

191191
panel.begin { result in
192-
guard result == .OK, let selectedURL = panel.url else {
193-
onCancel()
194-
return
195-
}
192+
DispatchQueue.mainIfNot {
193+
guard result == .OK, let selectedURL = panel.url else {
194+
onCancel()
195+
return
196+
}
196197

197-
self.openDocument(at: selectedURL, onCompletion: onCompletion, onError: { _ in onCancel() })
198+
self.openDocument(at: selectedURL, onCompletion: onCompletion, onError: { _ in onCancel() })
199+
}
198200
}
199201
onDialogPresented()
200202
}
@@ -208,25 +210,27 @@ extension NSDocumentController {
208210
@MainActor
209211
public func openDocument(
210212
at url: URL,
211-
onCompletion: @escaping () -> Void = {},
212-
onError: @escaping (Error) -> Void = { _ in }
213+
onCompletion: @MainActor @escaping () -> Void = {},
214+
onError: @MainActor @escaping (Error) -> Void = { _ in }
213215
) {
214216
let accessGranted = RecentsStore.beginAccessing(url)
215217
openDocument(withContentsOf: url, display: true) { _, _, error in
216-
if let error {
217-
if accessGranted {
218-
RecentsStore.endAccessing(url)
219-
}
220-
DispatchQueue.main.async {
221-
NSAlert(error: error).runModal()
222-
}
223-
onError(error)
224-
} else {
225-
RecentsStore.documentOpened(at: url)
226-
DispatchQueue.main.async {
227-
NSApp.activate(ignoringOtherApps: true)
218+
DispatchQueue.mainIfNot {
219+
if let error {
220+
if accessGranted {
221+
RecentsStore.endAccessing(url)
222+
}
223+
DispatchQueue.main.async {
224+
NSAlert(error: error).runModal()
225+
}
226+
onError(error)
227+
} else {
228+
RecentsStore.documentOpened(at: url)
229+
DispatchQueue.main.async {
230+
NSApp.activate(ignoringOtherApps: true)
231+
}
232+
onCompletion()
228233
}
229-
onCompletion()
230234
}
231235
}
232236
}

Sources/WelcomeWindow/Model/RecentsStore.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public enum RecentsStore {
3939
}
4040
}
4141

42-
private static let logger = Logger(
42+
private nonisolated static let logger = Logger(
4343
subsystem: Bundle.main.bundleIdentifier ?? "com.example.app",
4444
category: "RecentsStore"
4545
)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// DispatchQueue+asyncIfNot.swift
3+
// WelcomeWindow
4+
//
5+
// Created by Khan Winter on 8/28/25.
6+
//
7+
8+
import Foundation
9+
10+
extension DispatchQueue {
11+
/// Dispatch an operation to the main queue if it's not already on it.
12+
/// - Parameter operation: The operation to enqueue.
13+
static func mainIfNot(_ operation: @MainActor @escaping () -> Void) {
14+
if Thread.isMainThread {
15+
MainActor.assumeIsolated {
16+
operation()
17+
}
18+
} else {
19+
DispatchQueue.main.async(execute: operation)
20+
}
21+
}
22+
}

Sources/WelcomeWindow/Views/WelcomeWindow.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public struct WelcomeWindow<RecentsView: View, SubtitleView: View>: Scene {
1313

1414
private let buildActions: (_ dismissWindow: @escaping () -> Void) -> WelcomeActions
1515
private let customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)?
16-
private let onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)?
16+
private let onDrop: (@Sendable (_ url: URL, _ dismiss: @escaping () -> Void) -> Void)?
1717
private let subtitleView: (() -> SubtitleView)?
1818
private let openHandler: WelcomeOpenHandler?
1919

@@ -34,7 +34,7 @@ public struct WelcomeWindow<RecentsView: View, SubtitleView: View>: Scene {
3434
@ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
3535
customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? = nil,
3636
subtitleView: (() -> SubtitleView)? = nil,
37-
onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? = nil,
37+
onDrop: (@Sendable (_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? = nil,
3838
openHandler: WelcomeOpenHandler? = nil
3939
) {
4040
self.iconImage = iconImage
@@ -99,7 +99,7 @@ extension WelcomeWindow where RecentsView == EmptyView, SubtitleView == EmptyVie
9999
iconImage: Image? = nil,
100100
title: String? = nil,
101101
@ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
102-
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil,
102+
onDrop: (@Sendable (_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil,
103103
openHandler: WelcomeOpenHandler? = nil
104104
) {
105105
self.init(
@@ -125,7 +125,7 @@ extension WelcomeWindow where RecentsView == EmptyView {
125125
title: String? = nil,
126126
subtitleView: @escaping () -> SubtitleView,
127127
@ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
128-
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil,
128+
onDrop: (@Sendable (_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil,
129129
openHandler: WelcomeOpenHandler? = nil
130130
) {
131131
self.init(
@@ -151,7 +151,7 @@ extension WelcomeWindow where SubtitleView == EmptyView {
151151
title: String? = nil,
152152
@ActionsBuilder actions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
153153
customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? = nil,
154-
onDrop: ((_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil,
154+
onDrop: (@Sendable (_ url: URL, _ dismissWindow: @escaping () -> Void) -> Void)? = nil,
155155
openHandler: WelcomeOpenHandler? = nil
156156
) {
157157
self.init(

Sources/WelcomeWindow/Views/WelcomeWindowView.swift

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public struct WelcomeWindowView<RecentsView: View, SubtitleView: View>: View {
2222
@State private var selection: Set<URL> = []
2323

2424
private let buildActions: (_ dismissWindow: @escaping () -> Void) -> WelcomeActions
25-
private let onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)?
25+
private let onDrop: (@Sendable (_ url: URL, _ dismiss: @escaping () -> Void) -> Void)?
2626
private let customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)?
2727
private let subtitleView: (() -> SubtitleView)?
2828
private let openHandler: WelcomeOpenHandler?
@@ -35,7 +35,7 @@ public struct WelcomeWindowView<RecentsView: View, SubtitleView: View>: View {
3535
title: String? = nil,
3636
subtitleView: (() -> SubtitleView)? = nil,
3737
buildActions: @escaping (_ dismissWindow: @escaping () -> Void) -> WelcomeActions,
38-
onDrop: ((_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? = nil,
38+
onDrop: (@Sendable (_ url: URL, _ dismiss: @escaping () -> Void) -> Void)? = nil,
3939
customRecentsList: ((_ dismissWindow: @escaping () -> Void) -> RecentsView)? = nil,
4040
openHandler: WelcomeOpenHandler? = nil
4141
) {
@@ -60,12 +60,20 @@ public struct WelcomeWindowView<RecentsView: View, SubtitleView: View>: View {
6060
}
6161
}
6262

63-
public var body: some View {
64-
let dismiss = dismissWindow.callAsFunction
65-
let actions = buildActions(dismiss)
66-
let effectiveOpen = openHandler ?? defaultOpenHandler
63+
var dismiss: () -> Void {
64+
dismissWindow.callAsFunction
65+
}
66+
67+
var actions: WelcomeActions {
68+
buildActions(dismiss)
69+
}
6770

68-
return HStack(spacing: 0) {
71+
var effectiveOpen: (@MainActor ([URL], @escaping () -> Void) -> Void) {
72+
openHandler ?? defaultOpenHandler
73+
}
74+
75+
public var body: some View {
76+
HStack(spacing: 0) {
6977
WelcomeView(
7078
iconImage: iconImage,
7179
title: title,
@@ -114,11 +122,11 @@ public struct WelcomeWindowView<RecentsView: View, SubtitleView: View>: View {
114122
}
115123
.onDrop(of: [.fileURL], isTargeted: .constant(true)) { providers in
116124
NSApp.activate(ignoringOtherApps: true)
117-
providers.forEach {
125+
providers.forEach { [onDrop, dismissWindow] in
118126
_ = $0.loadDataRepresentation(for: .fileURL) { data, _ in
119127
if let data, let url = URL(dataRepresentation: data, relativeTo: nil) {
120128
Task { @MainActor in
121-
onDrop?(url, dismiss)
129+
onDrop?(url, dismissWindow.callAsFunction)
122130
}
123131
}
124132
}

0 commit comments

Comments
 (0)