I’m not a student, but I’ve made a small contribution to Swift compiler development in the past.
I’ve also been interested in this topic ever since Swift macros were introduced three years ago, so I’m cheering you on.
In order to replace Property Wrappers with macros,
we need to shift the type inference (semantic analysis) that the compiler has traditionally performed for Property Wrappers
into the macro system. However, there are many cases where this is not currently possible with the existing macro capabilities.
Personally, I believe that improving the collaboration between macros and type inference
is the most difficult and important challenge in this area.
This topic is also mentioned in the Swift Macros Vision Document:
However, your proposal doesn’t seem to address these challenges in much detail.
Below, I’ve included some concrete examples—I think it would be a good idea to consider how your proposal would address them.
For what it’s worth, I personally don’t think this can be achieved just by adding type information support to macros.
I believe we’ll need a dedicated “property wrapper macro” that allows the user to explicitly declare type information on the usage side.
Pattern 1
@propertyWrapper
struct Simple<T> {
var wrappedValue: T
}
enum Ext {
static func foo() -> Bool { true }
}
struct Pattern1 {
@Simple var w = Ext.foo()
}
struct Pattern1_expand {
var _w = Ext.foo()
// The macro `@Simple` cannot know the `Bool` here.
// The macro must receive the type of expression `Ext.foo`, which the compiler has inferred to be `Bool`.
var w: Bool {
get { _w }
set { _w = newValue }
}
}
Pattern 2
struct Foo {
static func foo() -> Foo { .init() }
}
@propertyWrapper
struct InterLeft {
var wrappedValue: Foo
}
struct Pattern2 {
@InterLeft var w = .foo()
}
struct Pattern2_expand {
// The compiler cannot infer the expression .foo() until after the macro has been expanded.
// This means the order of type inference and macro expansion is reversed compared to pattern 1.
var _w: Foo = .foo()
var w: Foo {
get { _w }
set { _w = newValue }
}
}
Pattern 3
protocol PILProto {}
struct PILValue : PILProto {}
extension PILProto where Self == PILValue {
static var value: PILValue { PILValue() }
}
@propertyWrapper
struct ProtocolInferLeft<T: PILProto> {
var wrappedValue: T
}
struct Pattern3 {
@ProtocolInferLeft var w = .value
}
struct Pattern3_expand {
// The macro must receive the type `PILValue` of the expression `.value`, as inferred by the compiler.
// For the compiler to perform that inference, it needs to resolve the expression `var _w<T: PILProto>: T = .value`.
// However, resolving that requires expanding the macro first.
// Whether inference comes first or expansion comes first, it doesn’t work.
// And of course, to begin with, the expression `var _w<T: PILProto>: T = .value` is not even valid in current Swift.
var _w: PILValue = .value
var w: PILValue {
get { _w }
set { _w = newValue }
}
}
Pattern 4
protocol MPProto {
associatedtype AT
}
struct MPValue: MPProto {
typealias AT = Int
}
@propertyWrapper
struct MetaParameter<M: MPProto> {
init(wrappedValue: M.AT, _ meta: M) {
self.wrappedValue = wrappedValue
}
var wrappedValue: M.AT
}
struct Pattern4 {
@MetaParameter(MPValue()) var w = .init()
}
struct Pattern4_expand {
// The macro must receive the type `Int` of the expression `.init()`, as inferred by the compiler.
// However, the compiler cannot generate an expression it can resolve.
// Unlike pattern 3, the expression `MPValue()` is also needed, which would require a highly complex and currently unknown syntax, such as:
// `var _w<M: MPProto = #type(of: MPValue())>: M.AT = .init()`
var _w: Int?
var w: Int {
get { _w! }
set { _w = newValue }
}
}