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-10668] Lazy variables referencing each other in closures no longer compiles with Swift 5 #53067

Open
sindresorhus opened this issue May 11, 2019 · 5 comments
Assignees
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. compiler The Swift compiler in itself regression swift 5.0

Comments

@sindresorhus
Copy link

Previous ID SR-10668
Radar None
Original Reporter @sindresorhus
Type Bug
Environment

Swift 5

Xcode 10.2.1

macOS 10.14.4

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug, 5.0Regression
Assignee @slavapestov
Priority Medium

md5: 53b86198e420bf1e3f5944efa4cfaeea

Issue Description:

When upgrading from Swift 4.2 to Swift 5, I encountered a case of code that no longer compiles.

The following compiled fine in Swift 4.2 (Xcode 10.1):

func run<T, U>(_ value: T, _ block: (T) -> U) -> U {
    return block(value)
}

class Fixture {}

class Foo {
    lazy var a = run(Fixture()) { _ in
        print(b)
    }


    lazy var b = run(Fixture()) { _ in
        print(a)
    }
}

But with Swift 5 (Xcode 10.2), I get this error:

error: value of type 'Foo' has no member 'a'

However, if I remove the `run()` usage from the `a` variable, it compiles fine:

func run<T, U>(_ value: T, _ block: (T) -> U) -> U {
    return block(value)
}

class Fixture {}

class Foo {
    lazy var a: Fixture = {
        print(b)
        return Fixture()
    }()


    lazy var b = run(Fixture()) { _ in
        print(a)
    }
}

I couldn't find anything in the release notes about this, so I'm gonna assume it's a regression in Swift 5.

I use the `run()` utility a lot in my code and it would be too bad if that pattern is no longer supported.

@belkadan
Copy link
Contributor

@slavapestov probably has the context to know what's going wrong.

@slavapestov
Copy link
Member

This doesn't work in Swift 4.2 either:

lazy.swift:14:9: error: value of type 'Foo' has no member 'a'
                print(a)
                      ^
lazy.swift:8:11: note: did you mean 'a'?
        lazy var a = run(Fixture()) { _ in
                 ^

Is the example complete? I don't see any way we could infer the type of the result of 'a' or 'b' here.

@sindresorhus
Copy link
Author

My reduced example was faulty. I've made a better test case.

The following code compiles with Swift 4.1, but not Swift 5 or Swift 4.2.1:

@discardableResult
func with<T>(_ value: T, update: (inout T) throws -> Void) rethrows -> T {
    var copy = value
    try update(&copy)
    return copy
}

final class Fixture {
    var onAction: (() -> Void)?
}

final class Fixture2 {}

final class Foo {
    lazy var a = with(Fixture()) {
        print("")

        $0.onAction = {
            print(self.b)
        }
    }

    lazy var b = with(Fixture2()) { _ in
        print(self.a)
    }
}

I've confirmed it compiles by using the Swift 4.1 version here: https://www.jdoodle.com/execute-swift-online

@slavapestov
Copy link
Member

Ah, I see. One of the closures is a multiple-statement closure.

The difference is subtle. In 4.1, we would first compute the type of the property without applying the solution. This would tell us the type of the lazy property. Then we would type check it again, and this time apply a solution, which type checks multiple-statement closure bodies. At this point, it is safe to refer to self.a or self.b, which now have types.

In 4.2, we type check the initializer expression in one shot, including multiple-statement closures, before we assign an inferred type to the property. So the circular reference is rejected because at the time we see the 'self.a' reference inside 'self.b', we still haven't assigned a type to 'self.a' yet.

@rjmccall You worked on this stuff most recently – do you have any suggestions? I wonder if we can delay type checking multiple-statement closure bodies in general to resolve cycles such as this.

@sindresorhus Going back to the 4.1 behavior is non-trivial and I can't promise when or if it will happen at this point. However, as a workaround, you can explicitly declare the type of the lazy property in case of circularity like this.

@rjmccall
Copy link
Member

I'm fine with this having become ill-formed without a type annotation, but if we feel compelled to make it work, I guess delaying closure bodies in some cases is probably acceptable.

@swift-ci swift-ci transferred this issue from apple/swift-issues Apr 25, 2022
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 regression swift 5.0
Projects
None yet
Development

No branches or pull requests

5 participants