Repository navigation
fix(swift): read the binding that is in scope at the call - #2947
Merged
Merged
Conversation
A receiver's type came from every same-name binding of the enclosing
function, wherever in the function it was declared. When an inner
binding's type could not be inferred, the receiver counted as unknown
even where only an outer, declared binding was in scope, and the call
took the label match. In
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
if flag {
let semaphore = identity(DispatchSemaphore(value: 0))
semaphore.signal()
}
semaphore.wait()
the outer wait() got an edge to an unrelated project wait() (#2436).
Each Swift binding now records where it is in scope: from the end of
its declaration to the end of its block, where a parameter's block is
its function. Only the bindings in scope at the call count, and of
those the innermost block's, so a local shadows a same-name local of an
enclosing block or a parameter. `var request = try request.make()`
reads the parameter on its right-hand side and the new variable after
it. Bindings of the same block that disagree still select nothing, and
fields and module-level bindings are unchanged.
pipeline_swift_in_scope_binding_issue2436 (sequential and parallel)
covers the example, the shadowed-parameter idiom of Alamofire's
encoders and two sibling blocks; it fails without the change.
Signed-off-by: Martin Vogel <[email protected]>
This was referenced Nov 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #2436
This is the first of the follow-ups we promised when #2436 was merged. Thank you, @DavidHLP, for the shadowing example in that thread. It was exactly the case this change needed, and your trace to the binding lookup was spot on.
What was wrong
A Swift receiver's type came from every binding with the same name in the enclosing function, wherever it was declared. When an inner binding's type could not be inferred, the receiver counted as unknown even at a call where only the outer, declared binding is in scope, and the call fell back to the label match:
This came from our own commit 8372793 in #2436. Treating "one binding known, one unknown" as a conflict would fix the example but drop correct edges on Alamofire's
var urlRequest = try urlRequest.asURLRequest()idiom, so this change picks the binding that is actually in scope at the call instead.What changed
let/var) records where it is in scope: from the end of its declaration to the end of its block. A parameter's block is its function.var request = try request.make()reads the parameter on its right-hand side and the new variable after it.Test
pipeline_swift_in_scope_binding_issue2436(sequential and parallel) covers the example from #2436 (no edge toDecoy.wait()), the shadowed-parameter idiom with ananyparameter (the new variable's call keeps its edge), and two sibling blocks that each type their ownstore. It fails without the change.Measured
main 0d835c5 vs this branch; Alamofire bda9ed57, swift-nio eabcc14f, helm 9b88efd6; every run indexed from scratch.
We reviewed every changed pair, not a sample, since the deltas are small:
urlRequest.asURLRequest()three times,url.asURL()once) on anany URLRequestConvertible/any URLConvertibleparameter. main label-matched them to one arbitrary conformer (Session's privateParameterlessRequestConvertible,String.asURL). The calls on the new variable, such asurlRequest.headers.update(...), keep their edges. The 1 added edge (configuration.headersinside the closure that initializes a same-nameconfiguration) is right.channelFuture.whenSuccessonEventLoopFuture.Isolatedinstead ofEventLoopFuture, andupgraders.map). The other 4 were right only because a same-name binding from another closure or test method happened to have the same type. Of the 44 added pairs, 32 are right: the 11whenSuccesscalls now onEventLoopFuture,socket.close()afterlet socket = try Socket(socket: socket), andConditionLock,ByteBufferandEventLoopcalls. The other 12 come from fallbacks this change does not touch: the label match for a constructed type the project does not declare (DispatchSemaphore(...)thenwait(), as noted in fix(swift): preserve declaration identity and compatible call edges #2436), for closure parameters and pattern bindings that are not recorded as bindings, and through property chains.Not in this change: an
any P/some Pparameter type still names no type, so a call on such a parameter gets no typed edge.