Skip to content

Commit bc807e3

Browse files
feat!: Enable strict concurrency
1 parent a348434 commit bc807e3

30 files changed

+543
-412
lines changed

MIGRATION.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ Also, all resolver closures must remove the `eventLoopGroup` argument, and all t
1212

1313
The documentation here will be very helpful in the conversion: https://www.swift.org/documentation/server/guides/libraries/concurrency-adoption-guidelines.html
1414

15+
### Swift Concurrency checking
16+
17+
With the conversion from NIO to Swift Concurrency, types used across async boundaries should conform to `Sendable` to avoid errors and warnings. This includes the Swift types and functions that back the GraphQL schema. For more details on the conversion, see the [Sendable documentation](https://developer.apple.com/documentation/swift/sendable).
18+
1519
### `ConcurrentDispatchFieldExecutionStrategy`
1620

1721
This was changed to `ConcurrentFieldExecutionStrategy`, and takes no parameters.
@@ -22,7 +26,7 @@ The `EventStream` abstraction used to provide pre-concurrency subscription suppo
2226

2327
### SubscriptionResult removal
2428

25-
The `SubscriptionResult` type was removed, and `graphqlSubscribe` now returns a true Swift `Result` type.
29+
The `SubscriptionResult` type was removed, and `graphqlSubscribe` now returns `Result<AsyncThrowingStream<GraphQLResult, Error>, GraphQLErrors>`.
2630

2731
### Instrumentation removal
2832

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ let package = Package(
2525
.copy("LanguageTests/schema-kitchen-sink.graphql"),
2626
]
2727
),
28-
]
28+
],
29+
swiftLanguageVersions: [.v5, .version("6")]
2930
)

Sources/GraphQL/Error/GraphQLError.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ extension GraphQLError: Hashable {
169169

170170
// MARK: IndexPath
171171

172-
public struct IndexPath: Codable {
172+
public struct IndexPath: Codable, Sendable {
173173
public let elements: [IndexPathValue]
174174

175175
public init(_ elements: [IndexPathElement] = []) {
@@ -197,7 +197,7 @@ extension IndexPath: ExpressibleByArrayLiteral {
197197
}
198198
}
199199

200-
public enum IndexPathValue: Codable, Equatable {
200+
public enum IndexPathValue: Codable, Equatable, Sendable {
201201
case index(Int)
202202
case key(String)
203203

Sources/GraphQL/Execution/Execute.swift

Lines changed: 48 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ import OrderedCollections
2727
* Namely, schema of the type system that is currently executing,
2828
* and the fragments defined in the query document
2929
*/
30-
public final class ExecutionContext {
30+
public final class ExecutionContext: @unchecked Sendable {
3131
let queryStrategy: QueryFieldExecutionStrategy
3232
let mutationStrategy: MutationFieldExecutionStrategy
3333
let subscriptionStrategy: SubscriptionFieldExecutionStrategy
3434
public let schema: GraphQLSchema
3535
public let fragments: [String: FragmentDefinition]
36-
public let rootValue: Any
37-
public let context: Any
36+
public let rootValue: any Sendable
37+
public let context: any Sendable
3838
public let operation: OperationDefinition
3939
public let variableValues: [String: Map]
4040

@@ -55,8 +55,8 @@ public final class ExecutionContext {
5555
subscriptionStrategy: SubscriptionFieldExecutionStrategy,
5656
schema: GraphQLSchema,
5757
fragments: [String: FragmentDefinition],
58-
rootValue: Any,
59-
context: Any,
58+
rootValue: any Sendable,
59+
context: any Sendable,
6060
operation: OperationDefinition,
6161
variableValues: [String: Map],
6262
errors: [GraphQLError]
@@ -86,10 +86,10 @@ public protocol FieldExecutionStrategy: Sendable {
8686
func executeFields(
8787
exeContext: ExecutionContext,
8888
parentType: GraphQLObjectType,
89-
sourceValue: Any,
89+
sourceValue: any Sendable,
9090
path: IndexPath,
9191
fields: OrderedDictionary<String, [Field]>
92-
) async throws -> OrderedDictionary<String, Any>
92+
) async throws -> OrderedDictionary<String, any Sendable>
9393
}
9494

9595
public protocol MutationFieldExecutionStrategy: FieldExecutionStrategy {}
@@ -107,11 +107,11 @@ public struct SerialFieldExecutionStrategy: QueryFieldExecutionStrategy,
107107
public func executeFields(
108108
exeContext: ExecutionContext,
109109
parentType: GraphQLObjectType,
110-
sourceValue: Any,
110+
sourceValue: any Sendable,
111111
path: IndexPath,
112112
fields: OrderedDictionary<String, [Field]>
113-
) async throws -> OrderedDictionary<String, Any> {
114-
var results = OrderedDictionary<String, Any>()
113+
) async throws -> OrderedDictionary<String, any Sendable> {
114+
var results = OrderedDictionary<String, any Sendable>()
115115
for field in fields {
116116
let fieldASTs = field.value
117117
let fieldPath = path.appending(field.key)
@@ -138,13 +138,14 @@ public struct ConcurrentFieldExecutionStrategy: QueryFieldExecutionStrategy,
138138
public func executeFields(
139139
exeContext: ExecutionContext,
140140
parentType: GraphQLObjectType,
141-
sourceValue: Any,
141+
sourceValue: any Sendable,
142142
path: IndexPath,
143143
fields: OrderedDictionary<String, [Field]>
144-
) async throws -> OrderedDictionary<String, Any> {
145-
return try await withThrowingTaskGroup(of: (String, Any?).self) { group in
144+
) async throws -> OrderedDictionary<String, any Sendable> {
145+
return try await withThrowingTaskGroup(of: (String, (any Sendable)?).self) { group in
146146
// preserve field order by assigning to null and filtering later
147-
var results: OrderedDictionary<String, Any?> = fields.mapValues { _ -> Any? in nil }
147+
var results: OrderedDictionary<String, (any Sendable)?> = fields
148+
.mapValues { _ -> Any? in nil }
148149
for field in fields {
149150
group.addTask {
150151
let fieldASTs = field.value
@@ -179,8 +180,8 @@ func execute(
179180
subscriptionStrategy: SubscriptionFieldExecutionStrategy,
180181
schema: GraphQLSchema,
181182
documentAST: Document,
182-
rootValue: Any,
183-
context: Any,
183+
rootValue: any Sendable,
184+
context: any Sendable,
184185
variableValues: [String: Map] = [:],
185186
operationName: String? = nil
186187
) async throws -> GraphQLResult {
@@ -246,8 +247,8 @@ func buildExecutionContext(
246247
subscriptionStrategy: SubscriptionFieldExecutionStrategy,
247248
schema: GraphQLSchema,
248249
documentAST: Document,
249-
rootValue: Any,
250-
context: Any,
250+
rootValue: any Sendable,
251+
context: any Sendable,
251252
rawVariableValues: [String: Map],
252253
operationName: String?
253254
) throws -> ExecutionContext {
@@ -313,8 +314,8 @@ func buildExecutionContext(
313314
func executeOperation(
314315
exeContext: ExecutionContext,
315316
operation: OperationDefinition,
316-
rootValue: Any
317-
) async throws -> OrderedDictionary<String, Any> {
317+
rootValue: any Sendable
318+
) async throws -> OrderedDictionary<String, any Sendable> {
318319
let type = try getOperationRootType(schema: exeContext.schema, operation: operation)
319320
var inputFields: OrderedDictionary<String, [Field]> = [:]
320321
var visitedFragmentNames: [String: Bool] = [:]
@@ -574,10 +575,10 @@ func getFieldEntryKey(node: Field) -> String {
574575
public func resolveField(
575576
exeContext: ExecutionContext,
576577
parentType: GraphQLObjectType,
577-
source: Any,
578+
source: any Sendable,
578579
fieldASTs: [Field],
579580
path: IndexPath
580-
) async throws -> Any? {
581+
) async throws -> (any Sendable)? {
581582
let fieldAST = fieldASTs[0]
582583
let fieldName = fieldAST.name.value
583584

@@ -643,11 +644,11 @@ public func resolveField(
643644
// function. Returns the result of `resolve` or the abrupt-return Error object.
644645
func resolveOrError(
645646
resolve: GraphQLFieldResolve,
646-
source: Any,
647+
source: any Sendable,
647648
args: Map,
648-
context: Any,
649+
context: any Sendable,
649650
info: GraphQLResolveInfo
650-
) async -> Result<Any?, Error> {
651+
) async -> Result<(any Sendable)?, Error> {
651652
do {
652653
let result = try await resolve(source, args, context, info)
653654
return .success(result)
@@ -664,8 +665,8 @@ func completeValueCatchingError(
664665
fieldASTs: [Field],
665666
info: GraphQLResolveInfo,
666667
path: IndexPath,
667-
result: Result<Any?, Error>
668-
) async throws -> Any? {
668+
result: Result<(any Sendable)?, Error>
669+
) async throws -> (any Sendable)? {
669670
// If the field type is non-nullable, then it is resolved without any
670671
// protection from errors, however it still properly locates the error.
671672
if let returnType = returnType as? GraphQLNonNull {
@@ -708,8 +709,8 @@ func completeValueWithLocatedError(
708709
fieldASTs: [Field],
709710
info: GraphQLResolveInfo,
710711
path: IndexPath,
711-
result: Result<Any?, Error>
712-
) async throws -> Any? {
712+
result: Result<(any Sendable)?, Error>
713+
) async throws -> (any Sendable)? {
713714
do {
714715
return try await completeValue(
715716
exeContext: exeContext,
@@ -755,8 +756,8 @@ func completeValue(
755756
fieldASTs: [Field],
756757
info: GraphQLResolveInfo,
757758
path: IndexPath,
758-
result: Result<Any?, Error>
759-
) async throws -> Any? {
759+
result: Result<(any Sendable)?, Error>
760+
) async throws -> (any Sendable)? {
760761
switch result {
761762
case let .failure(error):
762763
throw error
@@ -846,9 +847,9 @@ func completeListValue(
846847
fieldASTs: [Field],
847848
info: GraphQLResolveInfo,
848849
path: IndexPath,
849-
result: Any
850-
) async throws -> [Any?] {
851-
guard let result = result as? [Any?] else {
850+
result: any Sendable
851+
) async throws -> [(any Sendable)?] {
852+
guard let result = result as? [(any Sendable)?] else {
852853
throw GraphQLError(
853854
message:
854855
"Expected array, but did not find one for field " +
@@ -858,9 +859,9 @@ func completeListValue(
858859

859860
let itemType = returnType.ofType
860861

861-
return try await withThrowingTaskGroup(of: (Int, Any?).self) { group in
862+
return try await withThrowingTaskGroup(of: (Int, (any Sendable)?).self) { group in
862863
// To preserve order, match size to result, and filter out nils at the end.
863-
var results: [Any?] = result.map { _ in nil }
864+
var results: [(any Sendable)?] = result.map { _ in nil }
864865
for (index, item) in result.enumerated() {
865866
group.addTask {
866867
// No need to modify the info object containing the path,
@@ -889,7 +890,7 @@ func completeListValue(
889890
* Complete a Scalar or Enum by serializing to a valid value, returning
890891
* .null if serialization is not possible.
891892
*/
892-
func completeLeafValue(returnType: GraphQLLeafType, result: Any?) throws -> Map {
893+
func completeLeafValue(returnType: GraphQLLeafType, result: (any Sendable)?) throws -> Map {
893894
guard let result = result else {
894895
return .null
895896
}
@@ -910,8 +911,8 @@ func completeAbstractValue(
910911
fieldASTs: [Field],
911912
info: GraphQLResolveInfo,
912913
path: IndexPath,
913-
result: Any
914-
) async throws -> Any? {
914+
result: any Sendable
915+
) async throws -> (any Sendable)? {
915916
var resolveRes = try returnType.resolveType?(result, info)
916917
.typeResolveResult
917918

@@ -976,8 +977,8 @@ func completeObjectValue(
976977
fieldASTs: [Field],
977978
info: GraphQLResolveInfo,
978979
path: IndexPath,
979-
result: Any
980-
) async throws -> Any? {
980+
result: any Sendable
981+
) async throws -> (any Sendable)? {
981982
// If there is an isTypeOf predicate func, call it with the
982983
// current result. If isTypeOf returns false, then raise an error rather
983984
// than continuing execution.
@@ -1023,7 +1024,7 @@ func completeObjectValue(
10231024
* isTypeOf for the object being coerced, returning the first type that matches.
10241025
*/
10251026
func defaultResolveType(
1026-
value: Any,
1027+
value: any Sendable,
10271028
info: GraphQLResolveInfo,
10281029
abstractType: GraphQLAbstractType
10291030
) throws -> TypeResolveResult? {
@@ -1045,11 +1046,11 @@ func defaultResolveType(
10451046
* and returns it as the result.
10461047
*/
10471048
func defaultResolve(
1048-
source: Any,
1049+
source: any Sendable,
10491050
args _: Map,
1050-
context _: Any,
1051+
context _: any Sendable,
10511052
info: GraphQLResolveInfo
1052-
) async throws -> Any? {
1053+
) async throws -> (any Sendable)? {
10531054
guard let source = unwrap(source) else {
10541055
return nil
10551056
}
@@ -1058,11 +1059,11 @@ func defaultResolve(
10581059
let value = subscriptable[info.fieldName]
10591060
return value
10601061
}
1061-
if let subscriptable = source as? [String: Any] {
1062+
if let subscriptable = source as? [String: any Sendable] {
10621063
let value = subscriptable[info.fieldName]
10631064
return value
10641065
}
1065-
if let subscriptable = source as? OrderedDictionary<String, Any> {
1066+
if let subscriptable = source as? OrderedDictionary<String, any Sendable> {
10661067
let value = subscriptable[info.fieldName]
10671068
return value
10681069
}

Sources/GraphQL/GraphQL.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,11 @@ public func graphql(
8686
queryStrategy: QueryFieldExecutionStrategy = SerialFieldExecutionStrategy(),
8787
mutationStrategy: MutationFieldExecutionStrategy = SerialFieldExecutionStrategy(),
8888
subscriptionStrategy: SubscriptionFieldExecutionStrategy = SerialFieldExecutionStrategy(),
89-
validationRules: [(ValidationContext) -> Visitor] = [],
89+
validationRules: [@Sendable (ValidationContext) -> Visitor] = [],
9090
schema: GraphQLSchema,
9191
request: String,
92-
rootValue: Any = (),
93-
context: Any = (),
92+
rootValue: (any Sendable) = (),
93+
context: (any Sendable) = (),
9494
variableValues: [String: Map] = [:],
9595
operationName: String? = nil
9696
) async throws -> GraphQLResult {
@@ -150,8 +150,8 @@ public func graphql<Retrieval: PersistedQueryRetrieval>(
150150
subscriptionStrategy: SubscriptionFieldExecutionStrategy = SerialFieldExecutionStrategy(),
151151
queryRetrieval: Retrieval,
152152
queryId: Retrieval.Id,
153-
rootValue: Any = (),
154-
context: Any = (),
153+
rootValue: (any Sendable) = (),
154+
context: (any Sendable) = (),
155155
variableValues: [String: Map] = [:],
156156
operationName: String? = nil
157157
) async throws -> GraphQLResult {
@@ -216,11 +216,11 @@ public func graphqlSubscribe(
216216
queryStrategy: QueryFieldExecutionStrategy = SerialFieldExecutionStrategy(),
217217
mutationStrategy: MutationFieldExecutionStrategy = SerialFieldExecutionStrategy(),
218218
subscriptionStrategy: SubscriptionFieldExecutionStrategy = SerialFieldExecutionStrategy(),
219-
validationRules: [(ValidationContext) -> Visitor] = [],
219+
validationRules: [@Sendable (ValidationContext) -> Visitor] = [],
220220
schema: GraphQLSchema,
221221
request: String,
222-
rootValue: Any = (),
223-
context: Any = (),
222+
rootValue: (any Sendable) = (),
223+
context: (any Sendable) = (),
224224
variableValues: [String: Map] = [:],
225225
operationName: String? = nil
226226
) async throws -> Result<AsyncThrowingStream<GraphQLResult, Error>, GraphQLErrors> {

0 commit comments

Comments
 (0)