swift: Extensions for subtype of a class marked @Observable generate compile error

Description I cannot add a subtype (class, enum, struct) to a class marked with @Observable macro. If I add an extension to this subtype, I get compile error:

Circular reference resolving attached macro ‘Observable’

Steps to reproduce

@Observable
class MyObservableClass {
    var property1 = ""
    var property2 = MyClass()
}

extension MyObservableClass {
    class MyClass {
        var property = ""
    }
}

extension MyObservableClass.MyClass {
    var test: String { "test \(property)" }
}

Expected behavior It must be compilable because the following version is compilible:

@Observable
class MyObservableClass {
    var property1 = ""
    var property2 = MyClass()
}

extension MyObservableClass {
    class MyClass {
        var property = ""
        var test: String { "test \(property)" }
    }
}

Environment Xcode 15.0 beta (15A5160n) Swift 5.9 Target: iOS 17.0

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 34
  • Comments: 25 (11 by maintainers)

Most upvoted comments

We shipped a few macros this week and have had about a dozen folks report this bug to us. Would be nice to have the issue triaged.

It’d be nice to at least have some acknowledgement of the problem from the core team. While we understand that there are plenty of bugs out there that need attention and triage, this one has quite a bit of activity and reach, and was opened pretty promptly the week of WWDC23.

Checked the latest snapshot this morning and sadly this is still a problem!

Hopefully this gets fixed before the Xcode 15.3 + Swift 5.10 beta cycle completes.

Also experiencing this issue on Xcode 15.3, Swift 5.10

A class of these circularity issues were fixed by https://github.com/apple/swift/pull/67689, which is included in Xcode 15.1 Beta 3 where some folks are seeing their specific test cases resolved. However, the circular reference while resolving macro 'X' errors can be manifestations of slightly different circularity paths in the type checker, which is why other people are still seeing the issue.

With the amount of macros being utilized now by many libraries, including Apple’s, this problem becomes much more prominent, and also very cryptic to even understand that the harmless extension is causing it. Hope it will get addressed.

+1 to this issue being a problem that definitely comes up quite a bit in every day work.

Also, regarding this:

One workaround is to place the extension(s) in a separate file.

This can work most of the time, but sometimes it can be difficult. For example a macro applied to a generic type that needs to conditionally conform to Equatable:

@SomeMacro
struct Wrapper<T> { var value: T }

extension Model: Equatable where T: Equatable {}  // 🛑 Circular reference

If this extension is moved into a separate file then you lose the automatic synthesis of Equatable, and so that is unfortunate.

~It seems that this issue has been fixed in Xcode 15.1 Beta 3.~

Add a new case:

enum Namespace {
    @SomeMacro
    struct SomeStruct { }
}

extension Namespace.SomeStruct { }  // 🛑 Circular reference

One workaround is to place the extension(s) in a separate file.

@JessyCatterwaul Yes. It’s always the same pattern. TypeA with a Macro extension type B nested in type A extension for type Bcompile error

Please give some love to this issue, Swift core team! ❤️🙏

Just ran into this as well; here’s a perhaps even more minimal example:

@Observable
final class Outer {
    struct Inner {}
}

extension Outer.Inner {}

@Model has the same problem. Do all macros exhibit it?

@hborla Awesome! Would you know if we planned to patch this on the 5.10 branch?

Hello, any progress here?