[GSoC 2026] Reimplementing property wrappers with macros

Hi everyone! My name is Rob, and I’m currently a Computer Science undergrad in my final year at Northeastern University. I’ve been using Swift for 6 years and have been interested in programming languages/compilers for a few years now; it would be a great experience to contribute to the language during GSoC!

The project that I’m interested in is reimplementing property wrappers with macros. I’ve been looking at the feature gap that currently exists between the capabilities of property wrappers and the capabilities of macros. There are a few features missing from macros to allow for a full reimplementation of property wrappers, but in this post I’ll only focus on one to get feedback from the community/possible mentors.

On a syntactic level, @propertyWrapper on an arbitrary struct X introduces the struct’s name as an attribute, meaning @X is valid in the scope of X. The same “expansion” logic is applied to each invocation of @X on a declaration var foo = bar: introduce backing storage of type X, and transform our original foo into getters and setters for our backing storage (and/or projected value if it exists in the nominal declaration). To generalize this, we need some way for a macro attached to a nominal declaration to introduce this declaration’s name as a valid attribute. All expansion logic would come from this macro definition, but we can use the struct/class/enum name as an attribute.

To accomplish this, we can introduce a new macro role called attribute consumer (if you have a better name, let me know!) When a new attribute consumer macro is defined, it has two different “facets”: a) the definition logic, which defines expansion at use sites, and b) its own usage as an attribute, which introduces the nominal declaration it is attached to as an attribute.

For example, imagine if I have an attribute consumer macro Foo, and a struct Bar:

@Foo
struct Bar { 
    // properties, funcs, etc. in Bar
    … 
}

As a client of the Foo macro, I can use @Bar as if it were a macro. In other words, I can have:

@Bar var x = 10

In this example, the Foo macro is the attribute consumer, and the struct Bar is the attribute producer. When it is time to expand the invocation of @Bar, we will use the logic of the consumer macro Foo.

To tie it back to property wrappers:

@attached(attributeConsumer, of: "PropertyWrapperUsageSite")
macro propertyWrapper() = #externalMacro(module: "Foo", type: "PropertyWrapperMacro")

@attached(peer)
macro propertyWrapperUsageSite() = #externalMacro(module: "Foo", type: "PropertyWrapperUsageSite")

// ...
// in module Foo

// HERE is where validation logic on struct/enum/class goes
struct PropertyWrapperMacro: AttributeConsumerMacro {
    static func expansion(
        ...
    ) -> SyntaxProtocol {
        // here is where we do TypeCheckPropertyWrapper.cpp logic
        // (and emit diagnostics into context if needed)
    }
}

// HERE is where expansion logic goes
struct PropertyWrapperUsageSite<Context: MacroExpansionContext>: PeerMacro {
    static func expansion(
        of node: AttributeSyntax,
        providingPeersOf declaration: some DeclSyntaxProtocol,
        in context: Context
    ) throws -> [DeclSyntax] {
        // here is where we can place the expansion context,
        // getting necessary facts from our property wrapped
        // nominal declaration
        // (ex: do I need to introduce a projected value?)
    }
}

Hopefully we’d be able to utilize the parentDeclSyntax field in the HostToPluginMessage.expandAttachedMacro command in order to pass the consumer declaration to producers. It seems like the compiler decides per macro role how this field is used when passed onto the SwiftSyntax side for expansion, so we can (hopefully) provide the necessary context to producers in this way during implementation.

Now, consider the following example:

@propertyWrapper
struct State<Value> { 
    ...
}

class Foo {
    @State var count: Int = 0
    …
}

The property wrapper macro will, conceptually, do the following:

  1. Perform semantic analysis on the State declaration to ensure that it has all of the expected properties/behaviors of property wrappers enforced in TypeCheckPropertyWrapper.cpp (this is the logic in PropertyWrapperMacro)
  2. Introduce @State as a valid attribute into the scope/visibility that encloses struct State
  3. Expand the use site on the declaration of count to introduce backing storage, getters and setters, etc. (this is the logic in the PropertyWrapperUsageSite macro)

which is the exact syntactic role that property wrappers currently play in Swift.

You can think of attribute consumers as “macros that introduce (families of) attributes,” or “macros that produce (limited) macros” (I considered macros that produced macros but there were a few problems with the idea: how would arbitrarily introduced macros be dynamically registered?). The lowering logic for each nominal declaration with the same attribute consumer attached is identical, with the uniqueness of each attribute being dictated by the content of the attribute producer declaration.

Currently, property wrapper logic happens as a result of type checking a declaration; I’ll focus on VarDecls in my explanation here. When a var decl is encountered, the constraint generator will make an AttachedPropertyWrappersRequest where, for each attribute attached to the decl, we’ll a) get the nominal declaration using a CustomAttrNominalRequest (if it exists), and b) verify that this nominal declaration actually has an @propertyWrapper attribute. If these conditions are met, then we’ve resolved this var decl’s attached attribute to a property wrapper and we can evaluate/expand it as such.

To implement this new type of macro within the compiler, I’m thinking we can resolve unknown custom attributes the same way: when we are resolving custom attributes, we can evaluate a CustomAttrNominalRequest and check to see if one of its attributes is an invocation of an attribute consumer macro. If it is, we can expand it by delegating to the expansion plugin. This way, we can reuse some of the logic currently implemented for property wrappers while generalizing it to a whole class of new macros. (Please keep in mind that I am only considering VarDecl in the last two paragraphs and that consideration needs to be made for other applications of property wrappers, but in the interest of keeping this post shorter, I’m leaving that for a full proposal.)

There are other considerations for expanding current capabilities of macros to fully replace ad-hoc property wrapper logic (type inference for macros and function parameter macros to name a couple), but I wanted to gauge feedback for this part of the idea to see if it would be viable.

Edit: In the original post I had the following syntax/setup for attribute consumers:

Original API
// member extension describes validation logic in TypeCheckPropertyWrappers.cpp
@attached(member)
@attached(attributeConsumer)
macro propertyWrapper() = #externalMacro(module: “Foo”, type: “PropertyWrapperMacro”)

// ...
// somewhere in Foo module
struct PropertyWrapperMacro: AttributeConsumerMacro {
    func expansion(
        usageContext: AttributeConsumerContext, // .varDecl, .letDecl, .functionParam, etc.
        …
    ) -> SyntaxProtocol {

        switch usageContext {
        // logic for the expansion at the use site of our property wrapper goes here
        }
    }
}

extension PropertyWrapperMacro: MemberMacro {
    func expansion(…) -> … {
        // logic for the validation that exists for property wrappers in TypeCheckPropertyWrappers.cpp
    }
}

We’ll need an enum AttributeConsumerContext that will describe the context in which the attribute is being used. This will allow our AttributeConsumerMacro.expansion implementation to dispatch on the usage context and produce the correct syntax needed. The API for this will probably be changed over time: for example, how can we provide the correct syntax needed in each scenario? An idea might be to provide associated values on the AttributeConsumerContext: for example, if our attribute is attached to a function parameter, we can pass in a .functionParam(FunctionDeclSyntax) that will allow us to expand inside of the function body.

My reason for a different API than the one in the OP would be

  1. The dispatch mechanism for switching on the expansion is confusing: why does logic on the property wrapped struct need to happen alongside boilerplate for usage sites?
  2. Perhaps we’d be able to get some better performance on expansion/caching for incremental builds; the expansion and validation logic would ALWAYS happen on both the execution of the attached @propertyWrapper and all expansion sites. We should have some way to separate the two instead of coupling them.
  3. Having two separate macros really drives home what is happening: we have both our "actual" macro (@propertyWrapper) AND our "pseudo" macro (the synthesized attribute). When we are doing validation logic on our property wrapped struct/enum/class, we can dispatch to the attribute consumer macro. When we are looking for our expansion logic, we resolve the attribute to our nominal declaration but with added context that we are expanding; since we marked our attribute consumer as a consumer of the usage sites, we are able to dispatch to the correct logic. Some work could possibly be done on the fact that we need to introduce a second macro, but we could desugar each @State (for example) into @propertyWrapperUsageSite(for: State.self) and then use our PropertyWrapperUsageSite macro.
6 Likes

Hello, Rob and thank you for the interest in the project! I think you are on the right track with that you have, there are two sides one for @propertyWrapper attribute and one for use sites of a property wrapper. The project was listed for a few previous years as well, I'd suggest you take a look at the posts from 2025 as they go into depth regarding some of the challenges and specifically whether macros are currently applicable in all of the situations were property wrapper transform is currently performed by the compiler (i.e. closure parameters). Feel free to message me directly with any questions you might have!

2 Likes

Thank you for reaching out @xedin! I'm excited to keep digging into property wrappers (and I'll definitely message you with questions!)

I've been following your pointer on how closure parameters are handled and reading through SE-293. Additionally, I've been stepping through this small example I made:

@propertyWrapper
struct A {
    var wrappedValue: Int
}

let c = { (@A x) in
    let i = x
    let j = _x
    return (i, j)
}

and looking at the SIL the compiler generates along with what is happening as we move through the type checker. I think I've got it down to what would be an equivalent surface syntax (modulo names and usages of @_silgen_name:

struct A {
    var wrappedValue: Int
}

let c: (Int) -> (Int, A) = { x in 
  func body(_ _x: A) -> (Int, A) {
    var x: Int {
      get {
        _x.wrappedValue
      }
    }

    let i = x
    let j = _x
    return (i, j)
  }

  func property_wrapper_backing_initializer(_ x: Int) -> A {
    A(wrappedValue: x)
  }

  return body(property_wrapper_backing_initializer(x))
}

It gives me hope that this produces exactly the SIL that the property wrapped example currently does! Granted, this is a simple example and I'm a human who doesn't have to algorithmically infer types, but it's cool to see that (hypothetically) I'm on the right track.

However, I do have one concern centering around names. Property wrappers influence name mangling, meaning we need some way of ensuring that names are kept consistent with the current implementation when lowering property wrappers through macros. I'm thinking this means that we need some way of introducing a function that is local to the closure body (like we have in the example; body and property_wrapper_backing_initializer are scoped to c) but still able to be named by @_silgen_name (as an aside, usage of the attribute for ABI capability reasons seems to be within the accepted use, but I'd just want to confirm that's true). This means that we'd also need to translate some of the name mangling generation logic to our macro? Or we'd need to query the compiler through context to give us a mangled name for a syntax node? Of course, we'd also need to add macros to function/closure parameters that allows injection into the function body (as well as at the call site for function parameter property wrappers).

As far as type inference goes, there seemed to be a good idea last year about a compiler intrinsic #inferredType (or maybe a method on context?) that would act as a "type hole" for macros to produce and allow the solver to plug back in when the macro is fully expanded. When the syntax returned is converted into an actual tree, we could collect all usage points of #inferredType as pointers to AST nodes that the solver needs to go back and solve for us. The compiler should have the same type information pre- and post- property wrapper expansion so hopefully this would work, but I still need to look at the property wrapper specific type check requests in this context to see what actually happens.

While playing around with this, I found a fun issue with globally scoped closures with property wrapped parameters that I'll look into as I work!

Perhaps instead of @_silgen_name @abi attribute could be used to spell out the declaration and hence produce a mangling that would match the old implementation.

I don't think such inference is always possible unfortunately and how do properly do this is an open design question.

1 Like

I wrote a first draft of my proposal, please read it and let me know if you have any feedback (I really appreciate anyone who takes the time to read it!) I marked a few non-goals explicitly and changed the scope of the project to focus specifically on reimplementing property wrappers only for stored properties and left local variables and parameters as potential future work. An explicit goal of the project is to ensure that the new macro features are generically applicable and useful even outside of property wrappers, but this can change if it'd be better to focus on property wrappers first and then generalize the implementation for all types of metadata payloads.


Abstract

This project proposes replacing the current ad-hoc implementation of property wrappers for stored properties in nominal declarations with one that utilizes macros. This will remove some specific compiler complexity and create new features that can be used by macro authors to write more powerful transformations. We introduce the concept of a linked macro, which will allow macro writers to inspect nominal declarations and produce metadata for expansion site macros. By implementing this new macro type, we generalize the “producer” and “consumer” nature of property wrappers as a new language tool with property wrappers as the first implementation.

Introduction

Property wrappers are a Swift feature that generalizes common abstractions around access to properties through some mediator. For example, the lazy keyword can be implemented using property wrappers to delay initialization of a variable until it is accessed for the first time. The introduction of property wrappers democratized the ability to direct initialization and access through types that can perform use-case specific logic.

At its core, property wrappers are a syntactic transformation. When a nominal declaration is marked with @propertyWrapper, the compiler introduces the type's name as an attribute in the type’s visibility. When the synthesized attribute is encountered in the program, requests will be made to resolve some information about the property wrapper’s type, dictating how expansion should happen. New backing storage (and potentially projected storage if the property wrapper is API-level) is introduced in the scope of the usage of the attribute, and the declaration with the property wrapper attached is transformed into getters and setters that route to the backing storage.

When property wrappers were proposed in SE-0258 (and further expanded upon in SE-0293), macros were not yet introduced. Macros allow programmers to expand declarations and expressions during compile time. Specifically, this takes place when the compiler encounters a freestanding #foo or a custom @foo attribute; when type checking takes place, macros are expanded before constraint solving. Macros generalize the expansion that takes place when a property wrapper is used, and, with some specific additional features from this proposal, they can completely replace the ad-hoc compiler logic for property wrappers.

Deliverables

Linked Macros

The major addition to the macro system is a new linked macro role for attached macros:

public protocol LinkedMacro: AttachedMacro {
  static func expansion(
    of node: AttributeSyntax,
    providingLinkedMetadataFor declaration: some DeclGroupSyntax,
    in context: some MacroExpansionContext
  ) throws -> LinkedExpansion
}

public struct LinkedExpansion {
  public var decls: [DeclSyntax]
  public var metadata: LinkedMetadata
}

The linked macro role generalizes the “two macro” behavior of property wrappers: we inspect the property wrapper type and use that information to dictate how expansion happens at usage sites. For linked macros, this metadata is stored in a new representation that is internal to the compiler. The compiler will expose a small set of valid types for metadata properties, and the linked macro can store values of these types that will then be returned upon requests from consumers of the linked macro. These macros can return both new declarations and the linked metadata.

public struct LinkedMetadata {
  public func get<T: LinkedMetadataValue>(_ key: LinkedMetadataKey<T>) -> T?
}

public struct LinkedMetadataKey<T: LinkedMetadataValue> {
  public let name: String
  public init(_ name: String) { self.name = name }
}

public struct LinkedMetadataBuilder {
  public mutating func set<T: LinkedMetadataValue>(_ key: LinkedMetadataKey<T>, _ value: T)
  public func build() -> LinkedMetadata
}

The linked metadata is type safe, meaning calls to builder.set can be statically verified to ensure that values that are stored to specific keys have the correct type. Additionally, the compiler will mark specific LinkedMetadata key-value pairs as belonging to a specific linked macro when LinkedMetadataBuilder.set is used. Each metadata payload is scoped to a specific linked macro and the identity of the nominal declaration it is attached to. This also allows us to avoid collisions when multiple macro authors use the same name for two different linked metadata keys.

To share a concrete example, for a property wrapper macro, you could have the following API:

public enum PropertyWrapperMetadata {
  public enum Keys {
    public static let hasWrappedValue =
      LinkedMetadataKey<Bool>("hasWrappedValue")
    public static let wrappedValueType =
      LinkedMetadataKey<TypeHandle>("wrappedValueType")
    public static let initWrappedValue =
      LinkedMetadataKey<DeclHandle>("initWrappedValue")
    // … other keys as needed …
  }
}

Then, we could use the linked metadata API like so:

var builder = context.makeLinkedMetadataBuilder()

builder.set(PropertyWrapperMetadata.Keys.hasWrappedValue, true)
builder.set(PropertyWrapperMetadata.Keys.wrappedValueType, wrappedValueTypeHandle)
builder.set(PropertyWrapperMetadata.Keys.initWrappedValue, initDeclHandle)

return LinkedExpansion(
  decls: [],
  metadata: builder.build()
)

An important part of the API is to ensure that we do not need complex serialization logic and we respect the safety features that come with current macro implementations, like sandboxed plugins. Because of this, we do not want to allow macros to store arbitrary structs within the compiler, and we would like to provide a safe way for macros to give metadata back that still allows for type safety and expressiveness. Therefore, we have the following handles and metadata types:

public protocol LinkedMetadataValue {}

public struct TypeHandle: LinkedMetadataValue, Hashable, Sendable {}
public struct DeclHandle: LinkedMetadataValue, Hashable, Sendable {}

extension Bool: LinkedMetadataValue {}
extension Int: LinkedMetadataValue {}
extension Optional: LinkedMetadataValue where Wrapped: LinkedMetadataValue {}
extension Array: LinkedMetadataValue where Element: LinkedMetadataValue {}

Metadata can store trivial types (like Int, Bool, etc.), but to provide macro authors with an expressive type API, we need to handle more complex types while still allowing for safe interaction between the plugin and the host. These handles are given to the macro writer through an API exposed through context:

extension MacroExpansionContext {
  func typeHandle(ofNominalDecl: NominalDeclSyntax) throws -> TypeHandle
  func declHandle(ofVarDecl: VariableDeclSyntax, named: String) throws -> DeclHandle
  func declHandle(ofInitDecl: InitializerDeclSyntax) throws -> DeclHandle
  func typeHandle(ofValueDecl: DeclHandle) throws -> TypeHandle
  func typeHandle(from type: TypeSyntax) throws -> TypeHandle
  func areTypesEqual(_ lhs: TypeHandle, _ rhs: TypeHandle) -> Bool
}

It is important that these APIs do not expose or influence internal compiler state, as we want to keep this separate from constraint generation. This will not introduce any new macro expansion phase, influence the constraint solver, or affect any solver state. These handles need to be wrappers around the compiler’s internal representation of these types and declarations so consumers can query for the semantics they need. Concretely, these handles cannot be mutated by the macro; they are opaque, read-only views that the compiler exposes safely.

For example, a macro that implements the validation logic for a property wrapper will need to ensure that the wrappedValue label in an initializer matches the type of the property. To do this, we resolve the init syntax and the wrappedValue declaration to a pair of DeclHandles, resolve their types using typeHandle(ofValueDecl:), and check for their equality using areTypesEqual. This allows us to use type equality logic from the compiler without needing to implement it on our own. Additionally, we can now send compiler metadata back from our plugin in a way that the compiler can safely store. We only allow trivial types and these handles (along with optionals and arrays) to be stored in metadata.

When the linked macro logic happens, the macro will return the metadata back to the compiler, which will then store this metadata to be used in request evaluator queries that are keyed by the linked macro identity and attached nominal declaration identity. The macro writer can then use these metadata structs during expansion. We will expose an API on the context parameter so the consumers of these linked macros can ask for the metadata:

extension MacroExpansionContext {
  func linkedMetadata(for attribute: AttributeSyntax) -> LinkedMetadata?
}

This function will resolve the attribute to the linked producer macro and attached declaration that our current expansion corresponds to. When the compiler sends this linked metadata to the plugin, it will convert its own internal representation of the metadata into a Swift LinkedMetadata struct.

Tying back to property wrappers, the usage and consumer macros will be declared as expected:

@attached(linked, to: PropertyWrapperUsageSite)
macro propertyWrapper() = #externalMacro(module: “PropertyWrapperImpl”, type: “PropertyWrapperTypeMacro”)

@attached(peer)
// … and other roles, as needed …
macro propertyWrapperUsageSite() = #externalMacro(module: “PropertyWrapperImpl”, type: “PropertyWrapperUsageSiteMacro”)

When the macros are first registered, the compiler will note that @propertyWrapper links to @propertyWrapperUsageSite. When it finds @Foo, it will resolve the attribute to struct Foo marked with @propertyWrapper. It will delegate to the plugin that expands PropertyWrapperTypeMacro and produce its LinkedMetadata, which will be sent back to the compiler to store in an internal key-value structure. Then, it will delegate to the PropertyWrapperUsageSiteMacro to perform the correct expansion, exposing the correct LinkedMetadata through context APIs.

SIL Name Backwards Compatibility

Property wrappers affect how declarations are lowered to SIL and the names they are eventually given. We will use tests and inspect SIL outputs to ensure that we are properly lowering our property wrappers to the correct name format and our new SIL output remains compatible with what is currently expected.

Property Wrapper Re-implementation for Type Properties

With the above changes to the macro system, we should be able to reimplement property wrappers for type properties. This is a good place to begin the reimplementation process, as properties require type annotations, meaning we wouldn’t need to use heavy type inference logic in order to properly expand property wrapper usage sites. By only worrying about this single usage case, we can begin to incrementally replace parts of the ad-hoc logic without completely moving over to a full macro reimplementation.

To make the plan concrete, this project will only focus on re-implementation for stored type properties, not local variables or parameters. These other usage sites require extremely complex compiler infrastructure changes that will be explored below.

Tests

A full testing suite needs to be produced to ensure that property wrappers are compiled the exact same way they currently are. We will introduce more tests for each of the current property wrapper usage locations: parameters, local declarations, and properties. Although we are only reimplementing property wrappers for one specific use, it is important to document current expected behavior across all usage sites for potential future continuation of reimplementation. We will also test that facilitation between linking macros is handled correctly by the compiler, as this will be a new feature that macros are currently not capable of. Finally, we will have backwards compatibility tests that ensure that the current SIL naming scheme is honored with our new implementation.

Documentation

This project will add novel infrastructure to the compiler to accommodate for new macro features, so it will be important for documentation to show how these features are used. It is important for possible future use cases and improvements of these new macro features that compiler authors know how they are implemented.

Possible Challenges

Property wrappers are heavily integrated with Swift and see heavy usage in important APIs like SwiftUI. Therefore, it is important that changes happen incrementally and are heavily tested to ensure feature parity and correctness. Additionally, property wrappers span across the entire compiler pipeline; ensuring that all current features are accounted for in our new implementation prevents later possible complications.

Non-Goals

Function and Closure Parameter Macros

Currently, attached macros are not supported on function or closure parameters. We would need to introduce a new attached macro role, ParameterMacro, that would allow users to attach attributes to function and closure parameters and add to the body of the function of which they are a parameter of. Additionally, these macros will need to be able to dictate syntactic transformations that take place at the call site. Using an example from SE-0293:

struct History<Value> { ... }

@propertyWrapper
struct Traceable<Value> {
  init(wrappedValue value: Value)
  init(projectedValue: History<Value>)

  var wrappedValue: Value
  var projectedValue: History<Value>
}

when we call this function with property wrapped parameters:

func log<Value>(@Traceable value: Value) { ... }

let history: History<Int> = ...

log(value: 10)
log($value: history)

we need to inspect the label of the parameter and inject newly synthesized arguments into the call, as below:

log(value: Traceable(wrappedValue: 10))
log(value: Traceable(projectedValue: history))

This new ParameterMacro role would have two expansion functions: one for the declaration site (where we attach the property wrapper on the parameter), and one for the call site. The macro author would be able to inspect the type of the parameter to indicate which context they are in, and perform the necessary logic: for example, the above argument synthesization at the call site only happens for functions; closures must indicate whether they take in a projected value or a wrapped value in the closure signature and/or the label name (by prepending a $).

Implementing function and closure macros would change how function calls are currently processed and type checked. If we allow macros to expand both within the function body and at call sites, we now need to have the compiler check for any necessary expansion for most/all function calls. This is a massive infrastructure change, and this feature for macros could be its own project. Therefore, property wrapper reimplementation for parameters should be a future goal.

Constraint Generation API

Property wrappers are heavily integrated with the type system, with type information flowing in and out of the expansion sites. When the constraint solver is attempting to resolve types, it will query the nominal declaration and use the types of synthesized local declarations (like the projected value and wrapped backing storage) in its attempts. If we were to naively expand property wrappers using our current macro capabilities, we would be unable to resolve types as we’d have no information flowing back out of property wrappers.

On expansion, we need to provide a way for macros to inform the compiler of any new constraints it can deduce from their own internal semantics. However, this is a massive change that would require careful auditing of the type checker flow and how property wrappers currently influence generation and solving of constraints. This feature would change very high traffic parts of the compilation pipeline and could be a project in and of itself. It would be best to mark this as a non-goal for this project and work on it in the future. Additionally, it would be best to move the reimplementation of type inference heavy property wrapper usages, like local variables and parameters, to a later project.

Timeline

Community Bonding (May 1 – May 24)

  • Finalize API design for LinkedMacro, LinkedMetadata system, and TypeHandle/DeclHandle with mentor
  • Identify request evaluator integration points, caching, and invalidation strategy (nominal declaration changes)
  • Define feature flag for macro-based property wrapper path, allowing for incremental transition
  • Audit existing property wrapper tests and categorize by usage site, producing tests for areas that are currently lacking (if necessary)
  • Draft linked macro and metadata test files
  • Produce reviewed technical design document and plan for testing

Week 1 (May 25 – May 31)

  • Implement @attached(linked, to: ...) role parsing and registration
  • Represent producer to consumer link relationships in macro registry
  • Add failing tests for linked macro registration and resolution
  • Implement basic diagnostics for linked macros

Week 2 (June 1 – June 7)

  • Implement LinkedMetadata, LinkedMetadataKey<T>, and LinkedMetadataBuilder
  • Implement compiler-side metadata storage and request-evaluator caching
  • Write metadata round-trip tests (producer sets, consumer retrieves)
  • Add tests for allowed metadata types and invalid type usage

Week 3 (June 8 – June 14)

  • Implement context.linkedMetadata(for:) API
  • Implement conversion between internal metadata and plugin representation
  • Write tests for multiple linked producers and metadata visibility
  • Add diagnostics tests for missing or invalid metadata

Week 4 (June 15 – June 21)

  • Implement TypeHandle and DeclHandle types
  • Add context APIs for obtaining handles from syntax and declarations
  • Write tests validating handle identity stability and immutability
  • Add negative tests for misuse

Week 5 (June 22 – June 28)

  • Implement areTypesEqual using canonical type comparison
  • Ensure no solver mutation or new constraint phases are introduced
  • Write equality tests covering generics and cross-module cases
  • Add basic stress tests for repeated equality queries (ensure that no solver state is mutated)

Week 6 (June 29 – July 5)

  • Add AST dump comparison tests
  • Add SIL comparison tests for stored properties
  • Ensure all parity tests pass under legacy implementation with feature flag off

Midterm Evaluation (July 6 – July 10)

  • LinkedMacro infrastructure complete
  • LinkedMetadata system complete
  • TypeHandle/DeclHandle API complete
  • Parity test suite written and passing under legacy path
  • Feature flag integrated into the compiler

Week 7 (July 6 – July 12)

  • Implement PropertyWrapperTypeMacro producer.
  • Extract wrappedValue and init(wrappedValue:) metadata
  • Populate LinkedMetadata for property wrapper types
  • Add diagnostics tests for malformed wrappers

Week 8 (July 13 – July 19)

  • Implement PropertyWrapperUsageSiteMacro for stored properties
  • Expand backing storage, getter, and setter
  • Enable macro path under feature flag
  • Fix failing parity tests incrementally

Week 9 (July 20 – July 26)

  • Ensure SIL name and mangling parity with legacy implementation
  • Add SIL regression tests
  • Validate behavior for access control and generic wrappers

Week 10 (July 27 – August 2)

  • Validate request evaluator invalidation behavior
  • Stress test metadata caching
  • Run full Swift test suite and fix regressions

Week 11 (August 3 – August 9)

  • Add cross-module wrapper tests
  • Validate incremental compilation behavior

Week 12 (August 10 – August 16)

  • Write documentation for LinkedMacro architecture and metadata lifecycle
  • Final full test suite run
  • Remove or gate legacy stored-property wrapper path
  • Prepare final PRs for submission

Final Submission (August 17 – August 24)

  • LinkedMacro role merged
  • LinkedMetadata system merged
  • TypeHandle/DeclHandle API merged
  • Stored-property wrapper reimplementation merged
  • Full regression and parity test suite committed
  • Documentation complete
3 Likes