This release makes the macro tell you, at the declaration, about the shapes it cannot copy — an init accessor, an opaque type, a property that expands a parameter pack, one declared inside #if, one named after its own type, and a class that is not final — each of which previously surfaced, if at all, as an error inside generated code or as a silently wrong copy. The generated method also now caps its access level at the least visible property it copies, the rule Swift applies to a struct's memberwise initializer, so a copy can never vary state from somewhere that state is hidden.
Added
- An error on a property with an
initaccessor. Such a property is computed and was skipped as one, but it stands in for the storage it initializes in the memberwise initializer, so the generated call passed that storage and left the property out — "extra argument" and "missing argument" at once, inside generated code. Copying such a type means routing through the accessor, which only its@storageRestrictionsdescribes, so it is reported instead. Macros that generateinitaccessors,@Observableamong them, are unaffected: each macro sees the declaration as written rather than what another expands it into. - An error on a stored property named after the type it belongs to, such as a
Moneyinstruct Money. The generated method takes a parameter of that name and the call that builds the copy sits in its body, soMoney(Money: …)readMoneyas the parameter and failed with "cannot call value of non-function type", pointing inside generated code. Renaming the property is the one fix that leaves every supported declaration working: building the copy withSelf(…)instead would need arequiredinitializer on a class the macro has just advised to befinal. - An error on a stored property with an opaque type, such as
var value: some Equatable. Written in a parameter,some Pdeclares a fresh generic parameter for the caller to choose rather than naming the property's own type, so the generated code did not type-check. Anany Pexistential is a type in its own right and is copied as before. - An error on a stored property whose type expands a parameter pack, such as
let values: (repeat each T). A copy is built by passing each property to an initializer, and Swift does not compile such a call for a value that expands a pack — with a memberwise initializer or one written by hand alike — so a type that stores one needs acopyingwritten by hand. Types generic over a pack are otherwise copied as usual. - An error on a stored property declared inside
#if. A macro is handed the declaration before the directives are resolved, so such a property was invisible and the generated call simply left it out: on the configurations where its branch is active the expansion failed to compile, or — when the initializer defaults that parameter — silently reset the property on every copy. Every clause is checked,#elseif,#else, and nested directives included. A member inside#ifthat a copy never carries anyway, such as a computed,static, orlazyproperty or aletwith an initial value, is still skipped silently. - A warning on a
classthat is notfinal, with a Fix-It that marks it.copyingbuilds the copy by calling the annotated type's own initializer and returns it as that type, so a subclass inherited a method that rebuilt only the superclass: its own stored properties were dropped and the dynamic type changed, with nothing in the language to catch it. The Fix-It writesfinalafter any modifier the class already carries and demotes anopenclass topublic final, sinceopenandfinalcontradict each other. Astructand anactorcannot be subclassed and are never flagged. It is a warning rather than an error because a class nothing subclasses copies itself correctly.
Changed
- The generated
copyingmethod now caps its access level at the least visible property it copies, the rule Swift applies to a struct's memberwise initializer. It previously took the annotated type's level alone, so apublictype with aninternalorprivateproperty got apublicmethod — which either handed out what the property's access level withholds, letting any module build a copy that varies it, or did not compile at all, because apublicmethod cannot take a parameter of a less visible type. The type's own level counts as before:openyieldspublic, and aprivatetype yieldsfileprivate. Aprivateproperty is the one reversal — it keeps the methodprivate, which reaches the type declaration and its extensions in the same file, exactly as far as the property does. A modifier that constrains only the setter, such asprivate(set), does not lower the cap, sincecopyingreads the property rather than assigning through it, and neither do the members the method does not copy. The initializer the Fix-It writes carries the same capped level, and a type declaredinternalexplicitly now yields a method with no modifier spelled out — the same level, written the way a declaration carries it by default. To keep apubliccopying method, declare the copied propertiespublic.
Fixed
- Correct the documentation of
letconstants with an initial value. They were described as fixed, which holds for a literal but not for an expression such asUUID()orDate(): a copy is a new instance and runs that expression again, so it does not share the original's value. The documentation now says so and shows how to preserve the value instead, by dropping the initial value and supplying the default through an initializer. - Stop accepting a variadic parameter for a copied property's argument label. An initializer such as
init(id: Int, tags: String...)passed the check for atags: [String]property, and the generated call then failed to compile — Swift has no array-to-variadic splat — with no warning to say what was missing. A variadic is now only ever an extra parameter the call leaves out, and the macro names the initializer it needs. - Match initializer labels to escaped property names. A property declared
var `default`: Stringnever matchedinit(default value: String), the way Swift spells that label, so the macro warned about an initializer that was right there — and its Fix-It inserted a secondinit(default:), an invalid redeclaration. Labels are now compared as identifiers, and the warning spells the signature the way Swift writes a compound name,init(default:). Generated code and the Fix-It's initializer keep the backticks the language requires, while the documentation the macro writes names each parameter as its label, without them. - Spell a parameter pack in the generated return type as an expansion. A type such as
struct Bundle<each T>produced-> Bundle<T>, which Swift rejects with "pack reference 'T' can only appear in pack expansion", so the expansion never compiled; the return type now readsBundle<repeat each T>. A plain generic parameter, constrained or not, keeps its bare name as before. - No more false "missing initializer" warning when the initializer is declared inside
#if. The initializer was invisible for the same reason a conditional property was, so the macro reported one as missing and offered a Fix-It that wrote a second initializer with the signature the first one already had. Any conditionally compiled initializer now silences the check, since a macro cannot know which branch a build takes. - Spell the
copyingparameter of an implicitly unwrapped optional property, such asvar label: UILabel!, as a plain optional. Swift only accepts!at the top level of a property's or a parameter's type, so the(UILabel!)?parameter generated before did not compile. It denotes the same type as(UILabel?)?, so the parameter takes the same arguments and the property stays implicitly unwrapped in the copy. The initializer the Fix-It writes is a top-level position and keeps the declaredUILabel!. - Accept bindings that share one type annotation, such as
var x, y: Int. Swift attaches the annotation to the last binding only, so every preceding one was reported as missing a type and the expansion failed on code that compiles. A binding now takes the annotation of a later binding in the same declaration, stopping at the first one with an initial value — which types itself by inference, and which Swift rejects sharing an annotation with anyway (var x, y: Int = 0).
Full Changelog: 1.4.0...1.5.0