diff --git a/ElementX/Sources/Services/Audio/AudioConverterProtocol.swift b/ElementX/Sources/Services/Audio/AudioConverterProtocol.swift index 9343d5f2b..b7d7b87ae 100644 --- a/ElementX/Sources/Services/Audio/AudioConverterProtocol.swift +++ b/ElementX/Sources/Services/Audio/AudioConverterProtocol.swift @@ -8,7 +8,7 @@ import Foundation -protocol AudioConverterProtocol { +protocol AudioConverterProtocol: Sendable { func convertToOpusOgg(sourceURL: URL, destinationURL: URL) throws func convertToMPEG4AAC(sourceURL: URL, destinationURL: URL) throws } diff --git a/ElementX/Sources/Services/BugReport/BugReportService.swift b/ElementX/Sources/Services/BugReport/BugReportService.swift index fe2a19914..883b94981 100644 --- a/ElementX/Sources/Services/BugReport/BugReportService.swift +++ b/ElementX/Sources/Services/BugReport/BugReportService.swift @@ -49,7 +49,7 @@ class BugReportService: NSObject, BugReportServiceProtocol { // MARK: - BugReportServiceProtocol var crashedLastRun: Bool { - SentrySDK.crashedLastRun + SentrySDK.lastRunStatus == .didCrash } // swiftlint:disable:next cyclomatic_complexity diff --git a/ElementX/Sources/Services/Room/JoinRule.swift b/ElementX/Sources/Services/Room/JoinRule.swift index 712718b49..9cc36adf9 100644 --- a/ElementX/Sources/Services/Room/JoinRule.swift +++ b/ElementX/Sources/Services/Room/JoinRule.swift @@ -9,7 +9,7 @@ import Foundation import MatrixRustSDK /// The rule used for users wishing to join a room. -public enum JoinRule: Equatable, Hashable { +public enum JoinRule: Equatable, Hashable, Sendable { /// Anyone can join the room without any prior action. case `public` /// A user who wishes to join the room must first receive an invite. @@ -52,7 +52,7 @@ public enum JoinRule: Equatable, Hashable { } /// An allow rule which defines a condition that allows joining a room. -public enum AllowRule: Equatable, Hashable { +public enum AllowRule: Equatable, Hashable, Sendable { /// Only a member of the `room_id` Room can join the one this rule is used in. case roomMembership(roomID: String) /// A custom allow rule implementation, containing its JSON representation as a `String`. diff --git a/ElementX/Sources/Services/Timeline/TimelineController/TimelineControllerProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineController/TimelineControllerProtocol.swift index 22ef7c532..f6818256e 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/TimelineControllerProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/TimelineControllerProtocol.swift @@ -42,7 +42,7 @@ enum TimelineControllerError: Error { /// It, for example, permits switching from a live timeline to an event focused one, building view specific /// timeline items, grouping together state events, donating intents to the larger system etc. @MainActor -protocol TimelineControllerProtocol { +protocol TimelineControllerProtocol: Sendable { var roomID: String { get } var timelineKind: TimelineKind { get } diff --git a/UnitTests/Sources/AttributedStringBuilderTests.swift b/UnitTests/Sources/AttributedStringBuilderTests.swift index 9a59782d9..b67fb7fe0 100644 --- a/UnitTests/Sources/AttributedStringBuilderTests.swift +++ b/UnitTests/Sources/AttributedStringBuilderTests.swift @@ -149,7 +149,7 @@ struct AttributedStringBuilderTests { let attributedString = try #require(attributedStringBuilder.fromHTML(htmlString), "Could not build the attributed string") - #expect(attributedString.uiKit.attachment == nil, + #expect(attributedString.suppressedAttachment == nil, "iFrame attachments should be removed as they're not included in the allowedHTMLTags array.") } @@ -305,7 +305,7 @@ struct AttributedStringBuilderTests { foundBlockquoteAndLink = true } - #expect(foundBlockquoteAndLink != nil, "Couldn't find blockquote or link") + #expect(foundBlockquoteAndLink == true, "Couldn't find blockquote or link") } @Test @@ -358,11 +358,11 @@ struct AttributedStringBuilderTests { func userPermalinkMentionAtachment() { let string = "https://matrix.to/#/@test:matrix.org" let attributedStringFromHTML = attributedStringBuilder.fromHTML(string) - #expect(attributedStringFromHTML?.attachment != nil) + #expect(attributedStringFromHTML?.suppressedAttachment != nil) #expect(attributedStringFromHTML?.userID == "@test:matrix.org") #expect(attributedStringFromHTML?.link?.absoluteString == string) let attributedStringFromPlain = attributedStringBuilder.fromPlain(string) - #expect(attributedStringFromPlain?.attachment != nil) + #expect(attributedStringFromPlain?.suppressedAttachment != nil) #expect(attributedStringFromPlain?.userID == "@test:matrix.org") #expect(attributedStringFromPlain?.link?.absoluteString == string) } @@ -371,11 +371,11 @@ struct AttributedStringBuilderTests { func userIDMentionAtachment() { let string = "@test:matrix.org" let attributedStringFromHTML = attributedStringBuilder.fromHTML(string) - #expect(attributedStringFromHTML?.attachment != nil) + #expect(attributedStringFromHTML?.suppressedAttachment != nil) #expect(attributedStringFromHTML?.userID == "@test:matrix.org") #expect(attributedStringFromHTML?.link?.absoluteString == "https://matrix.to/#/@test:matrix.org") let attributedStringFromPlain = attributedStringBuilder.fromPlain(string) - #expect(attributedStringFromPlain?.attachment != nil) + #expect(attributedStringFromPlain?.suppressedAttachment != nil) #expect(attributedStringFromPlain?.userID == "@test:matrix.org") #expect(attributedStringFromPlain?.link?.absoluteString == "https://matrix.to/#/@test:matrix.org") } @@ -384,11 +384,11 @@ struct AttributedStringBuilderTests { func roomIDPermalinkMentionAttachment() { let string = "https://matrix.to/#/!test:matrix.org" let attributedStringFromHTML = attributedStringBuilder.fromHTML(string) - #expect(attributedStringFromHTML?.attachment != nil) + #expect(attributedStringFromHTML?.suppressedAttachment != nil) #expect(attributedStringFromHTML?.roomID == "!test:matrix.org") #expect(attributedStringFromHTML?.link?.absoluteString == string) let attributedStringFromPlain = attributedStringBuilder.fromPlain(string) - #expect(attributedStringFromPlain?.attachment != nil) + #expect(attributedStringFromPlain?.suppressedAttachment != nil) #expect(attributedStringFromHTML?.roomID == "!test:matrix.org") #expect(attributedStringFromPlain?.link?.absoluteString == string) } @@ -397,11 +397,11 @@ struct AttributedStringBuilderTests { func roomAliasPermalinkMentionAttachment() { let string = "https://matrix.to/#/#test:matrix.org" let attributedStringFromHTML = attributedStringBuilder.fromHTML(string) - #expect(attributedStringFromHTML?.attachment != nil) + #expect(attributedStringFromHTML?.suppressedAttachment != nil) #expect(attributedStringFromHTML?.roomAlias == "#test:matrix.org") #expect(attributedStringFromHTML?.link?.absoluteString == "https://matrix.to/#/%23test:matrix.org") let attributedStringFromPlain = attributedStringBuilder.fromPlain(string) - #expect(attributedStringFromPlain?.attachment != nil) + #expect(attributedStringFromPlain?.suppressedAttachment != nil) #expect(attributedStringFromHTML?.roomAlias == "#test:matrix.org") #expect(attributedStringFromPlain?.link?.absoluteString == "https://matrix.to/#/%23test:matrix.org") } @@ -410,11 +410,11 @@ struct AttributedStringBuilderTests { func roomAliasMentionAttachment() { let string = "#test:matrix.org" let attributedStringFromHTML = attributedStringBuilder.fromHTML(string) - #expect(attributedStringFromHTML?.attachment != nil) + #expect(attributedStringFromHTML?.suppressedAttachment != nil) #expect(attributedStringFromHTML?.roomAlias == "#test:matrix.org") #expect(attributedStringFromHTML?.link?.absoluteString == "https://matrix.to/#/%23test:matrix.org") let attributedStringFromPlain = attributedStringBuilder.fromPlain(string) - #expect(attributedStringFromPlain?.attachment != nil) + #expect(attributedStringFromPlain?.suppressedAttachment != nil) #expect(attributedStringFromHTML?.roomAlias == "#test:matrix.org") #expect(attributedStringFromPlain?.link?.absoluteString == "https://matrix.to/#/%23test:matrix.org") } @@ -423,11 +423,11 @@ struct AttributedStringBuilderTests { func eventRoomIDPermalinkMentionAttachment() { let string = "https://matrix.to/#/!test:matrix.org/$test" let attributedStringFromHTML = attributedStringBuilder.fromHTML(string) - #expect(attributedStringFromHTML?.attachment != nil) + #expect(attributedStringFromHTML?.suppressedAttachment != nil) #expect(attributedStringFromHTML?.eventOnRoomID == .some(.init(roomID: "!test:matrix.org", eventID: "$test"))) #expect(attributedStringFromHTML?.link?.absoluteString == string) let attributedStringFromPlain = attributedStringBuilder.fromPlain(string) - #expect(attributedStringFromPlain?.attachment != nil) + #expect(attributedStringFromPlain?.suppressedAttachment != nil) #expect(attributedStringFromPlain?.eventOnRoomID == .some(.init(roomID: "!test:matrix.org", eventID: "$test"))) #expect(attributedStringFromPlain?.link?.absoluteString == string) } @@ -436,11 +436,11 @@ struct AttributedStringBuilderTests { func eventRoomAliasPermalinkMentionAttachment() { let string = "https://matrix.to/#/#test:matrix.org/$test" let attributedStringFromHTML = attributedStringBuilder.fromHTML(string) - #expect(attributedStringFromHTML?.attachment != nil) + #expect(attributedStringFromHTML?.suppressedAttachment != nil) #expect(attributedStringFromHTML?.eventOnRoomAlias == .some(.init(alias: "#test:matrix.org", eventID: "$test"))) #expect(attributedStringFromHTML?.link?.absoluteString == "https://matrix.to/#/%23test:matrix.org/$test") let attributedStringFromPlain = attributedStringBuilder.fromPlain(string) - #expect(attributedStringFromPlain?.attachment != nil) + #expect(attributedStringFromPlain?.suppressedAttachment != nil) #expect(attributedStringFromPlain?.eventOnRoomAlias == .some(.init(alias: "#test:matrix.org", eventID: "$test"))) #expect(attributedStringFromPlain?.link?.absoluteString == "https://matrix.to/#/%23test:matrix.org/$test") } @@ -536,7 +536,7 @@ struct AttributedStringBuilderTests { #expect(attributedString?.runs.count == 1) - #expect(attributedString?.attachment == nil) + #expect(attributedString?.suppressedAttachment == nil) } @Test @@ -546,7 +546,7 @@ struct AttributedStringBuilderTests { #expect(attributedString?.runs.count == 1) - #expect(attributedString?.attachment == nil) + #expect(attributedString?.suppressedAttachment == nil) } @Test @@ -556,7 +556,7 @@ struct AttributedStringBuilderTests { #expect(attributedString?.runs.count == 1) - #expect(attributedString?.attachment == nil) + #expect(attributedString?.suppressedAttachment == nil) } @Test @@ -569,7 +569,7 @@ struct AttributedStringBuilderTests { var foundAttachments = 0 var foundLink: URL? for run in attributedStringFromHTML.runs { - if run.attachment != nil { + if run.suppressedAttachment != nil { foundAttachments += 1 } @@ -585,7 +585,7 @@ struct AttributedStringBuilderTests { foundAttachments = 0 foundLink = nil for run in attributedStringFromPlain.runs { - if run.attachment != nil { + if run.suppressedAttachment != nil { foundAttachments += 1 } @@ -607,7 +607,7 @@ struct AttributedStringBuilderTests { var foundAttachments = 0 var foundLink: URL? for run in attributedStringFromHTML.runs { - if run.attachment != nil { + if run.suppressedAttachment != nil { foundAttachments += 1 } @@ -623,7 +623,7 @@ struct AttributedStringBuilderTests { foundAttachments = 0 foundLink = nil for run in attributedStringFromPlain.runs { - if run.attachment != nil { + if run.suppressedAttachment != nil { foundAttachments += 1 } @@ -983,7 +983,7 @@ struct AttributedStringBuilderTests { #expect(String(attributedString2.characters) == "This is visible. And this text and link are visible too.") - try #require(attributedString2.runs.first { $0.link != nil }?.link, "Couldn't find the link") + _ = try #require(attributedString2.runs.first { $0.link != nil }?.link, "Couldn't find the link") } // MARK: - Private @@ -1006,10 +1006,46 @@ struct AttributedStringBuilderTests { #expect(attributedString.runs.count == expectedRuns) - for run in attributedString.runs where run.attachment != nil { + for run in attributedString.runs where run.suppressedAttachment != nil { return } Issue.record("Couldn't find expected value.") } } + +// warning suppression +// `NSTextAttachment` is marked `@_nonSendable(_assumed)` by Apple in the UIKit SDK, and +// `ScopedAttributeContainer` (returned by `AttributedString.uiKit` and used by dynamic member +// lookup on `AttributedString` and `AttributedString.Runs.Element`) is marked `Sendable`. +// The compiler warns at every access point where a `@_nonSendable` value is read through a +// `Sendable` container — regardless of Swift version or strict concurrency mode. There is no +// compiler flag that suppresses this cleanly. +// +// Attempted fixes: +// - `@MainActor` on the test class: does not help; the warning is not about actor isolation +// - `@preconcurrency import UIKit`: does not help; the warning originates from the SDK type attribute +// - Varying `-strict-concurrency` (targeted/complete) or `-swift-version` (5/6): all produce +// the same warning — confirmed via standalone `swiftc` repro +// +// The warning is safe to ignore here because no concurrency boundary is actually crossed — +// the value is accessed synchronously within a single context and never escapes. +// +// These two extension properties consolidate 20+ warning sites across this file down to just +// these two definitions taking the hit. This pattern is test-only; app code reads attachments +// via `NSAttributedString.attribute(_:at:effectiveRange:)` which does not trigger the warning. + +// swiftformat:disable redundantSelf +// self is not redundant here because these are NOT properties directly on the type, but on a +// child through dynamicMember which requires the `self` for access +private extension AttributedString { + var suppressedAttachment: NSTextAttachment? { + self.attachment + } +} + +private extension AttributedString.Runs.Element { + var suppressedAttachment: NSTextAttachment? { + self.attachment + } +}