public struct A {
@_alwaysEmitIntoClient
public internal(set) var a: Int
@inlinable
public mutating func update() {
a = 1
}
}
but gives warning "Setter for property 'a' is internal and cannot be referenced from an '@inlinable' function; this will be an error in a future Swift language mode" in swift 6.4
Is this a new behaviour of @_alwaysEmitIntoClient? How should I update codes like this?
I just checked, and @export(implementation) doesn't change the diagnostic.
This feels like a bug, because if I refactor it like so and get rid of @_alwaysEmitIntoClient:
public struct A {
@usableFromInline
internal var _a: Int
public internal(set) var a: Int {
get { _a }
@usableFromInline set { _a = newValue }
}
@inlinable
public mutating func update() {
a = 1 // <-- warning here
}
}
I get the same diagnostic on the marked line. But IIUC, the code above should certainly work, since I'm explicitly marking the computed setter as @usableFromInline, right?
This was an intentional bug fix, because the old behavior was incorrect. Private declarations cannot be referenced from an inlinable context.
For example, try generating a module interface from your program using an older compiler that accepted that code. The resulting module interface won’t parse, because the private setter is not part of the interface, so the function body will not type check on account of attempting to assign to a read-only property.
The reason it is a warning and not an error is to save you from having to fix the problem right away; we’ve noticed this pattern comes up in our source compatibility testing. I highly recommend fixing it though, and there’s a good chance it will become an error at some point in the future, perhaps in a future language mode if not unconditionally.
I'm confused about this. @allevato's example should work: an @inlinable public function should be able to reference a@usableFromInline setter which then sets @usableFromInline internal storage, no?
@usableFromInline has no effect on a private decl. Only internal decls can be @usableFromInline.
To understand why, you can once again consider the case of a module interface. Because two source files can both declare a private decl with the same name, if we were to print both in the module interface, we would get a module interface that fails to type check.
I was wondering if this code could be fixed with @export, but I'm getting error with same diagnostic as warning in OP, so I'll ask here just to understand better.
Should following code compile without errors?
public struct C {
@export(interface)
public internal(set) var a: Int
@export(implementation)
public mutating func update() {
a = 1
}
}
It's written with understanding that @export(interface) on property should make setter symbol visible, while @export(implementation) on method should make it inlinable, but it fails on line with a = 1.
Have you tried using @export(implementation) instead, which is the finalized spelling for @_alwaysEmitIntoClient? Would that fix the issue?
I just checked, and @export(implementation) doesn't change the diagnostic.
I was wondering if this code could be fixed with @export, but I'm getting error with same diagnostic as warning in OP, so I'll ask here just to understand better.
To save folks the trouble of further exploring this, although the spelling is different there's nothing fundamentally different about @export(implementation) and @_alwaysEmitIntoClient - they are both implemented as the same attribute in the compiler.
but there is something fundamentally different about @export(implementation) and @export(interface), and my example uses @export(implementation)on method instead of @inlinable and @export(interface)on property instead of @usableFromInline
I'm thinking that the reason why my snippet doesn't work is the same as why OP code doesn't work (internal(set) suppressing exporting symbol of setter), but I'm not 100% sure.
If my thought process is right, I'll just post that example in github issue to make sure both cases are taken care of. If I'm wrong, I'll learn something.
You're right that SE-0497 says that @export(interface) ought to have the same effect on declaration interface accessibility as @usableFromInline. However, I'm finding that's not the case in Swift 6.4 with a much more basic test that doesn't involve internal(set) at all:
@usableFromInline internal func usableFromInline() { }
@_alwaysEmitIntoClient internal func alwaysEmitIntoClient() { }
@export(implementation) internal func exportImplementation() { }
@export(interface) internal func exportInterface() { }
@inlinable public func inlinable() {
usableFromInline() // OK
alwaysEmitIntoClient() // OK
exportImplementation() // OK
exportInterface() // error: global function 'exportInterface()' is internal and cannot be referenced from an '@inlinable' function
}
@Douglas_Gregor are we missing something or is there a gap in the implementation of the @export(interface)?
Even if the example above did work as SE-0497 implies it ought to, though, I assume the interaction with internal(set) would still be broken, just like it is with @usableFromInline.