Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SR-9743] withoutActuallyEscaping(_: do:) doesn't work when the first argument is defined inline and is more than one expression #52174

Closed
swift-ci opened this issue Jan 24, 2019 · 5 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself

Comments

@swift-ci
Copy link
Collaborator

Previous ID SR-9743
Radar rdar://problem/56835205
Original Reporter CTMacUser (JIRA User)
Type Bug
Status Resolved
Resolution Cannot Reproduce

Attachment: Download

Environment

macOS Mojave 10.14.2

Xcode 10.1 with its default Swift

Additional Detail from JIRA
Votes 1
Component/s Compiler
Labels Bug
Assignee CTMacUser (JIRA)
Priority Medium

md5: 35ddc4fec6eaa6e4d5385e385721d82f

Issue Description:

Let's start with a type like:

struct Map10ElementsIterator<Base: IteratorProtocol, Element>: IteratorProtocol {
var base: Base
let transform: (AnySequence<Base.Element>) -> Element

mutating func next() -> Element? { /* ... */ }
}

The next() function calls transform with a sequence that is extracted from base, so that inner sequence's own next is defined within the outer. The inner incrementing code can be defined as a local function:

mutating func next() -> Element? {
guard let first = base.next() else { return nil }

var remaining = 10

func generate() -> Base.Element? {
guard remaining > 0 else { return nil }
defer { remaining -= 1 }
guard remaining < 10 else { return first }

return base.next()
}

return withoutActuallyEscaping(generate, do: { transform(AnySequence(AnyIterator($0))) })
}

The problems occur when we try to define the incrementing code directly as a closure:

mutating func next() -> Element? {
guard let first = base.next() else { return nil }

var remaining = 10
return withoutActuallyEscaping({
guard remaining > 0 else { return nil }
defer { remaining -= 1 }
guard remaining < 10 else { return first }

return self.base.next() // Error here
}) { transform(AnySequence(AnyIterator($0))) }
}

The error is highlighted at the "self" token as "Closure cannot implicitly capture a mutating self parameter". But this can be worked around with a helper function shielding the call to withoutActuallyEscaping:

mutating func next() -> Element? {
func helper(_ body: () -> Base.Element?) -> Element {
return withoutActuallyEscaping(body) {
transform(AnySequence(AnyIterator($0)))
}
}

guard let first = base.next() else { return nil }

var remaining = 10
return helper {
guard remaining > 0 else { return nil }
defer { remaining -= 1 }
guard remaining < 10 else { return first }

return self.base.next()
}
}

There seems to be a dependence on the length of the closure, because this:

public mutating func next() -> Element? {
switch remainingSplits {
case .some(0):
guard let first = base.next() else { return nil }

let firstCollection = CollectionOfOne(first)
var firstIterator = firstCollection.makeIterator()
return withoutActuallyEscaping({ firstIterator.next() ?? self.base.next() }, do: { transform(AnySequence(AnyIterator($0))) })
case .some(let limit):
remainingSplits = .some(limit - 1)
fallthrough
case .none:
return nil
}
}

From a similar type works just find at the end of the .some(0) case. It is a single expression, so the bug may be triggered on the inlined closure's code length.

@swift-ci
Copy link
Collaborator Author

Comment by Daryle Walker (JIRA)

This is based off the Swift forum thread at "Can a mutating function be used while not actually escaping? "

@belkadan
Copy link
Contributor

belkadan commented Nov 2, 2019

@swift-ci create

@rjmccall
Copy link
Member

I don't see a test case that should work but doesn't in either the report here or the original thread. Jordan mentions that he tried something that should've worked, but his code snippet isn't even particularly close to compiling, so I don't know how to reverse it to something that doesn't work for the right reasons. So this all completely unusable, sorry.

@bobergj
Copy link

bobergj commented Nov 15, 2019

I think I can shed some light on this.
With the Swift 5 compiler, compiling the following code would fail.
With the Swift 5.1 compiler (Apple Swift version 5.1.2 (swiftlang-1100.0.278 clang-1100.0.33.9) it compiles successfully.

func doSomethingWithEscapingClosure(_ body: @escaping () -> Void) -> Void {
    body()
}

struct MyStruct {
    
    mutating func myFunc() {
        
        let inlineClosure = { () -> Void in
           // Swift 5: "Error: Closure cannot implicitly capture a mutating self parameter"
           // Swift 5.1: OK!
            self.foo() 
        }

        withoutActuallyEscaping(inlineClosure) { inlineClosurePromiseWontEscape -> Void in
            doSomethingWithEscapingClosure {
                inlineClosurePromiseWontEscape()
            }
        }
    }

    mutating func foo() {

    }
}

Interestingly, pasting this in a Xcode 11.2.1 (11B500) playground, gives a "error: escaping closure captures mutating 'self' parameter" (see attached EscapingClosureError.playground.zip), if someone wants to file a radar for that..

@rjmccall
Copy link
Member

Okay. It's a known problem that we don't infer escaping-ness on local funcs very well.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself
Projects
None yet
Development

No branches or pull requests

4 participants