[GSoC 2025] Re-implement property wrappers with macros

Hola,Namaste and Hello to fellow community members

I am Arihant Marwaha a software developer who is currently in his third year of pursuing a bachelor's in computer science. After giving it much thought and finally stepping up to the challenge I have decided to contribute to this community very frequently and apply for GSOC 2025 with Swift.

I am a beginner to open-source and would appreciate all the help I can get to understand how everything works and what needs to be done by me to contribute to the code base and make a genuine impact. I might be new to this setting of workflow but by strength lies in flexibility and ability to adapt to new concepts quickly.

I'm interested in working on Re-implement property wrappers with macros
as this is the topic I can see myself not only being comfortable to understand and contribute to.

I was able to read the codebase for the addressed problem statement and will be keen to work on fixing the vulnerabilities and make improvements as per the requirements and also give out new ideas.

I will be looking forward to your input and see how I can refine my proposal in the coming days!

Thanks,
Arihant

3 Likes

@ktoso I don't see any discussion towards my interseted topic within the forum.
So should I be diverting my focus towards some other project or should I keep looking deeper into the codebase for this project to find new ideas that can make impact as modifying the general case scenario of propertywrappers in general will be a huge task ahead but will be worth optimzing as it contains huge potential to change the way we code in swift at a base level.

could you also be willing to provide me a link to the codebase for the proposed solution ?

It's been less than a day since you posted your message, please give mentors some time to reply.

In your initial post you phrase it a bit confusingly, there are no "vulnerabilities to fix" in this project.

I would suggest you spend more time looking into the project and come up with a project plan description of what you'd work on, and maybe get your hands dirty a little bit with trying to build the project and see if you need any help with some specific questions. This period is really for you got check out what the project would be about and you have to come up with a detailed project plan about the work.

If you have specific questions please feel free to reach out to the project mentor @xedin on the forums (here), so far it's a bit too vague to provide much details, but maybe Pavel can give you some hints.

1 Like

@ktoso
I apologize for the confusion in my initial message regarding the project. As someone new to this environment, I misunderstood certain aspects and mistakenly referred to non-existent vulnerabilities. I appreciate your guidance and will thoroughly review the project to develop a detailed plan. I will reach out with specific questions as they arise and look forward to your feedback.

Thank you for your understanding.

Best regards,
Arihant

Hello, I'm glad to here that you are interested in the project and contributing in general! I'd suggest you start by reading a proposal for property wrappers and look at the code both in Sema and SILGen to understand what is going on there. A good starting point could be to look at issues labeled as "property wrappers" and try to tackle one of them to get familiar with the code base. We also have a few how-to guides for first time contributors that should help you build the compiler.

3 Likes

External property wrappers don't play well with variadic parameters #77824

Currently, Swift does not support property wrappers on variadic parameters (Int...) , which leads to either compiler crashes or unclear error messages .

Problem Example:

@propertyWrapper
struct Wrapper<Value> {
    var wrappedValue: Value
}

func foo(@Wrapper x: Int...) {}  // Compiler error
foo(x: 1, 2, 3)

/*

This produces:

Cannot convert value of type 'Int' to expected argument type 'Int...'
Additionally, trying to use $x in function calls results in a compiler crash.

*/

Proposed Solution: Macros for Variadic Property Wrappers

Instead of modifying the compiler directly, we can re-implement property wrappers using macros. The idea is to transform variadic parameters (Int...) into a wrapped array (Wrapper<[Int]>) at compile time.

This solution aligns with Swift's declarative and compile-time transformation principles , making it a practical and scalable fix . Would love to hear feedback from @xedin , @ktoso and the community on the feasibility and potential refinements of this approach! :rocket:

Sorry but this issue has nothing to do with macros or re-implementing property wrappers, regardless of how property wrapper itself is implemented there needs to be a compiler change at the use site to either reject+diagnose this use or support it.

1 Like

Hey @xedin I have been trying to explore macros and property wrappers on a small scale to see how do they actullay work and make packages that remove boilerplate code to increase productivity.

here were a few macro definations made by me that i would like you to review and comment if they are relevant to the project or not :

/// Macro that transforms a property declaration to use our custom property wrapper implementation
@attached(accessor)
public macro Wrapped<T>(_ wrapperType: Any.Type) = #externalMacro(module: "PropertyWrapperMacros", type: "WrappedMacro")

/// Macro that transforms a type into a property wrapper alternative
@attached(member)
public macro PropertyWrapperAlternative() = #externalMacro(module: "PropertyWrapperMacros", type: "PropertyWrapperAlternativeMacro")

reimplemented the property wrapper as follows :

@PropertyWrapperAlternative
struct Uppercase {
    private var storage: String = ""
    
    func get() -> String {
        return storage
    }
    
    mutating func set(_ newValue: String) {
        storage = newValue.uppercased()
    }
    
    init(_ initialValue: String) {
        set(initialValue)
    }
}

example useage of how it would look like if we redefined the property wrappers at a compiler level :

struct User {
    // Original property wrapper
    @OriginalUppercase var name: String
    
    // Our reimplemented version using macros
    @Wrapped(Uppercase.self) var email: String
}

the code may seem a litle more complex in terms of syntax but if more efficient in terms of compiler processing.

actual logic for the macros :

// This is what the macro expansion would look like behind the scenes
struct ExpandedUser {
    // Original property wrapper
    @OriginalUppercase var name: String
    
    // Expanded version of our macro
    private var _email: Uppercase
    
    var email: String {
        get {
            return _email.get()
        }
        set {
            _email.set(newValue)
        }
    }
    
    init(name: String, email: String) {
        self.name = name
        self._email = Uppercase(email)
    }
}

behind the scenes for the provided example :

import SwiftSyntax
import SwiftSyntaxMacros

struct WrappedMacro: AccessorMacro {
    static func expansion(
        of node: AttributeSyntax,
        providingAccessorsOf declaration: VarDeclSyntax,
        in context: MacroExpansionContext
    ) throws -> [AccessorDeclSyntax] {
      
        guard let argument = node.arguments?.as(LabeledExprListSyntax.self)?.first?.expression,
              let typeName = argument.as(MemberAccessExprSyntax.self)?.base?.as(IdentifierExprSyntax.self)?.identifier.text else {
            throw MacroError("Expected a type as argument")
        }
        
        return [
            """
            get {
                return _\(declaration.identifier.text).get()
            }
            """,
            """
            set {
                _\(declaration.identifier.text).set(newValue)
            }
            """
        ]
    }
}

struct PropertyWrapperAlternativeMacro: MemberMacro {
    static func expansion(
        of node: AttributeSyntax,
        attachedTo declaration: DeclSyntax,
        in context: MacroExpansionContext
    ) throws -> [DeclSyntax] {
       
        return [
            """
            protocol PropertyWrapperProtocol {
                associatedtype Value
                func get() -> Value
                mutating func set(_ newValue: Value)
            }
            """
        ]
    }
}

while creating this aproach i was struggling a little bit with adding required protocols and methods to make this type behave like a property wrapper and need more a bit more help on throwing errors to handle unexpected behaviour and make unit test cases to do so.

I understand there is lot of work to be done in terms of SIL code and implement the project and any sort of directions would be apriciated @ktoso

also while looking for good first issues reagarding label:"property wrappers" there weren't any issues that were open , and the ones that were open were not good first issues.

My implementation of " External property wrappers don't play well with variadic parameters #77824" wasn't related to how we may re-implement property wrappers but was just a geniune effort because when we form macros the way we handle vardic variables also becomes a probelm that needs to be addressed in my perosnal opinion.

What we are trying to archive with the project is enable removal of most if not all of the custom logic from the compiler that deals with property wrapper transformations, this means that the existing Swift syntax itself should be supported without requiring any changes.

A couple of relevant considerations:

  • Macros should have the same transformation capabilities that property wrappers do today i.e. injecting code into a function body when property wrapper is applied to a parameter. This needs to be audited into a capability table between property wrappers and macros to make sure that everything is covered.
  • Logic related to@propertyWrapper attribute has to be changed - it effectively has to turn a type declaration it is applied to into a macro or use a macro under the hood to generate underlying storage property and inject a getter and a setter into a property its applied to. There is some design work that needs to happen here and a strong proposal should address that.
  • Property wrappers have to start using init accessors for initialization. This means that assign_by_wrapper SIL instruction needs to be replaced with initial accessor one. Proposal needs to mention this and the reasons why that is the case.

Re: issues - usually its helpful to start with a bug of a small change to gain more understanding of the codebase, attempting to fix one of issues related to property wrappers or macros helps by giving you hands on experience with the codebase and relevant features.

2 Likes

Thanks for the feedback first of all @xedin !

Logic related to@propertyWrapper attribute has to be changed

Does this approach lead us to make fundamental changes to the foundational code of both macros and property wrappers all together or just property wrappers?

and to create more generalised syntax to make the process more automated for property wrappers ?

btw can you also give me the file address to this said "assign_by_wrapper" implementation within the codebase so that i can review it by hand.

Sorry, I don't know what you mean exactly, the project would most likely invoke compiler side changes to both macros and property wrappers.

btw can you also give me the file address to this said "assign_by_wrapper" implementation within the codebase so that i can review it by hand.

The instruction is declared in here, it's used in a few places, mostly in SILGenLValue.cpp and DefiniteInitialization.cpp.

Sorry, I don't know what you mean exactly, the project would most likely invoke compiler side changes to both macros and property wrappers

.
Sorry for framing my question in a bad way but you answered what I wanted to know by this.

Also I just needed specific file loacations which need to be changed in the compiler side and see where they are declared, i find it a bit difficult sometimes to navigate through such a large codebase that's all.

@xedin Apologies for being absent on the forum for the past few days, but this time was not wasted but rather used understadning the codebase in depth to familiarise myself with the swift compiler.

In the past few days I was able to understand a lot but also get lost in the depths of swift all together. When I begin to work on anything i ask myself a few questions and i was able to come up with a few and answer them but a whole lot of them un-answered and I hope you will help me find them.

I was able to make a detailed proposal as to how I want to approach the project and I look forward to your feeback and comments on the document so that I can make a final submition:

I was able to make some conclusions as to what and how can I approach this project :

  • Compiler code does have the ability to make people cry (just kidding, or am I)
  • Property wrappers are deeply coded into the system and work flawlessly but have a lot of room for improvement
  • need to make changes to the macros implementation so that it can Initialise accesors dynamically
  • need to change a lot of SIL code implementation
  • Formation of test cases for the proposal
  • find the location of all the implementation of macros, property wrappers , attribute handelling , expansion

Not going to lie this is the hardest thing I have ever done in my life but that's where I find the thrill . The amount of documentation I had to read and concepts I had able to tackle has just made me gain respect for all the people who have been doing this for a long long time.

Here is a link to some of my notes that i made in the last few days and see what i had in mind and will keep adding to it as time goes on. https://drive.google.com/drive/folders/1ACksefIR_IaDj7XeitPECVZB8QkRJnP7?usp=share_link

I am really looking forward to your feedback and comments on my proposal for the project. :rocket:

Thanks !
Arihant

I was able to make amends to my detailed proposal and extend the weekly phases and better explaination to it.

also made some test builds to suggest how we can make changes to the complier SIL genration and AST.

I will be making a pull request in the coming week to show my progress.Just need a little bit more amends to finalise what I have been working on.

I hope I can get some feedback soon as the date for final submiton is closing and I am very dedicated to making this project a reality as it was also not implemented last year for gsoc and I belive it has a lot value and impact.

2 Likes

Overall looks good to me, I think the proposal would benefit from more "how" in the "Proposed Solution" since I don't think that attached screenshots are enough.

1 Like

Thank you for the feedback.

I have been working on creating a detailed implementation proposal to be added since you replied to my message.

Apologies of the late reply but I assure that I have been working for hours without sleep to get the required changes. I will be adding the enhanced and version of the"Proposed solution" by highlighting compiler side changes and specific pathnames to the file directories to show where and why I want to change certain code(removal and changes).

proposed changes to both the complier (AST, SIL) and swift side of the build will be shown in detail.

@xedin
I have made proposed changes to the document from pages 5 to 16 by addding a detailed proposed implementation.

point number 10 [page 16] of subsection 2. Implementation plan (complier level):
has been kept in the discussion keeping in mind the future proposed changes to the SourceKit LSP in the coming days and how we may handle our own implementations in accordance to the future.

code after point 3 of the subsection 2 of complier implementation has not been written by me yet but a basic overview as to what needs to be done , as it would require great amount to time to study and then test changes to the macro expantions and other points. seeing the deadline I am providing a basic crux and hope for it to be enough for now.

if any changes or deletion is required, your feedback is much appriciated.

side note :- i struggle with testing a little and thus the testing stratergy has been kept to a minimum as per my understanding of the said subject.

Thanks,
Arihant

Hello Arihant,

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 }
    }
}
2 Likes

hey @omochimetaru ,

I really appreciate you taking the time to go through my proposal and providing such a detailed breakdown of the challenges involved in re-implementing property wrappers using macros. Your insights, especially regarding the interplay between macro expansion and type inference, have given me a lot to think about.

You're absolutely right—without explicit type information being available to macros at expansion time, reproducing the current property wrapper behavior purely through macros isn't feasible with the existing capabilities. Your examples, particularly Patterns 3 and 4, highlight the fundamental issue: inference relies on expansion, but expansion also relies on inference. This cyclic dependency makes a purely macro-based approach difficult without deeper compiler integration.

A dedicated “property wrapper macro” as you suggested might help, but it would require a way to let macros access inferred types at expansion time—or at least define explicit type constraints that the compiler could use pre-expansion. I wonder if this could be solved by allowing macros to interact with the type checker in a more structured way, perhaps through an additional annotation or a staged expansion process.

As suggested by you, I have tried to give details about my approch to macros modification in the sub point 4 of the macros extention. I was in the process of studying macros and it's current implementation at compiler level before this and thus just didn't expand on the said topic before in the proposal. I only wanted to include things that currently understand. Slowly and surely I think I am getting the hang of it but would love to hear more from you.

I have made a final few changes to the document on pages 15 to extend the document.

Would love to hear your thoughts on this! Thanks again for your support and for cheering me on—it really means a lot!

Best,
Arihant

@xedin i just had a very low IQ question but i have to ask it , when we are re implementing the the current implementation of property wrappers do we keep the old functionality in the background and then just give the devs a macro alternative to do the same thing but with less complexity and then remove it slowly but surely in upcoming builds or do we completely remove it from day 1.