Skip to main content

`DispatchQueue.sync` ambiguity: an annotation wasn't enough, extraction was

19. DispatchQueue.sync ambiguity: an annotation wasn't enough, extraction was

What happened, round 1: with entries 15–18 resolved, MPVKit progressed further into real type-checking and hit:

MPVGLView.swift:97:21: error: ambiguous use of 'sync(execute:)'
renderQueue.sync {
^
Dispatch.DispatchQueue:74:17: note: found this candidate in module 'Dispatch'
public func sync<T>(execute work: () throws -> T) rethrows -> T
Dispatch.DispatchQueue:3:17: note: found this candidate in module 'Dispatch'
public func sync(execute block: () -> Void)

Initial (incomplete) diagnosis: DispatchQueue declares two overloads of sync — a generic, rethrows version, and a plain () -> Void version. The closure passed to renderQueue.sync { ... } in attachRenderContext() contained nested calls to withUnsafeMutablePointer(to:_:), itself generic and rethrows. The first fix attempt added an explicit closure signature — renderQueue.sync { () -> Void in ... } — reasoning that telling the compiler the outer closure's type explicitly would resolve which sync overload was intended.

Round 2 — the same error, in the same place, after that fix shipped: a later CI run showed the identical "ambiguous use of 'sync(execute:)'" error, at the same line, with the () -> Void in annotation visibly already present in the failing line the compiler quoted. This conclusively demonstrated that the annotation alone was not sufficient — the ambiguity wasn't coming from Swift being unable to infer the outer closure's own signature, but from the nested generic/rethrows calls inside it confusing overload resolution in a way an outer annotation doesn't reach.

Actual fix: extracted the entire nested withUnsafeMutablePointer(to:_:) pyramid out of the sync closure entirely, into a new private, non-generic method (createRenderContext(core:) -> MPVError?). The sync closure in attachRenderContext() now contains only a single, flat statement — creationError = self.createRenderContext(core: core) — with no nested generic calls anywhere in its body. This is what actually resolved the ambiguity: removing the nested generics from the closure passed to sync, not describing that closure's own type more precisely.

Lesson: when an "ambiguous use of X" error persists after adding an explicit type annotation at the call site the error points to, the annotation may be treating a symptom rather than the cause — worth checking whether nested generic/rethrows calls deeper inside that same closure body are what's actually defeating overload resolution. Extracting the nested generic structure into its own ordinary (non-generic-call-containing) function is a more reliable fix than trying to out-annotate the ambiguity from the outside, and is worth trying first once an annotation demonstrably didn't work — as confirmed here by the same error reappearing, unchanged, in the very next CI run after the annotation was believed to have fixed it.