From 03af5038b646e6e9f49f54e1f82aee3be394a5e6 Mon Sep 17 00:00:00 2001 From: Holly Borla Date: Thu, 4 May 2023 16:56:47 -0700 Subject: [PATCH 01/13] Add proposal for init accessors. --- proposals/NNNN-init-accessors.md | 304 +++++++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 proposals/NNNN-init-accessors.md diff --git a/proposals/NNNN-init-accessors.md b/proposals/NNNN-init-accessors.md new file mode 100644 index 0000000000..0c96d46cbb --- /dev/null +++ b/proposals/NNNN-init-accessors.md @@ -0,0 +1,304 @@ +# Init Accessors + +* Proposal: [SE-NNNN](NNNN-init-accessors.md) +* Authors: [Holly Borla](https://github.com/hborla), [Doug Gregor](https://github.com/douggregor) +* Review Manager: TBD +* Status: **Awaiting implementation** + +## Introduction + +Init accessors generalize the out-of-line initialization feature of property wrappers to allow any computed property on types to opt into definite initialization analysis, and subsume initialization of a stored property with custom initialization code. + +## Motivation + +Swift applies [definite initialization analysis](https://en.wikipedia.org/wiki/Definite_assignment_analysis) to stored properties, stored local variables, and variables with property wrappers. Definite initialization ensures that memory is initialized on all paths before it is accessed. A common pattern in Swift code is to use one property as backing storage for one or more computed properties, and abstractions like [property wrappers](https://github.com/apple/swift-evolution/blob/main/proposals/0258-property-wrappers.md) and now [attached macros](https://github.com/apple/swift-evolution/blob/main/proposals/0389-attached-macros.md) help facilitate this pattern. Under this pattern, the backing storage is an implementation detail, and most code works with the computed property, including initializers. + +Property wrappers support bespoke definite initialization that allows initializing the backing property wrapper storage via the computed property, always re-writing initialization-via-wrapped-property in the form `self.value = value` to initialization of the backing storage in the form of `_value = Wrapper(wrappedValue: value)`: + +```swift +@propertyWrapper +struct Wrapper { + var wrappedValue: T +} + +struct S { + @Wrapper var value: Int + + init(value: Int) { + self.value = value // Re-written to self._x = Wrapper(wrappedValue: value) + } + + init(other: Int) { + self._value = Wrapper(wrappedValue: other) // Okay, initializes storage '_x' directly + } +} +``` + +The ad-hoc nature of property wrapper initializers mixed with an exact definite initialization pattern prevent property wrappers with additional arguments from being initialized out-of-line. Furthermore, property-wrapper-like macros cannot achieve the same initializer usability, because any backing storage variables added must be initialized directly instead of supporting initialization through computed properties. For example, the proposed [`@Observable` macro](https://github.com/apple/swift-evolution/blob/main/proposals/0395-observability.md) applies a property-wrapper-like transform that turns stored properties into computed properties backed by the observation APIs, but it provides no way to write an initializer using the original property names like the programmer expects: + +```swift +@Observable +struct Proposal { + var title: String + var text: String + + init(title: String, text: String) { + self.title = title // error: 'self' used before all stored properties are initialized + self.text = text // error: 'self' used before all stored properties are initialized + } // error: Return from initializer without initializing all stored properties +} +``` + +## Proposed solution + +This proposal adds _`init` accessors_ to opt computed properties on types into virtual initialization that subsumes initialization of a set of zero or more specified stored properties, which allows assigning to computed properties in the body of a type's initializer: + +```swift +struct Angle { + var degrees: Double + var radians: Double { + init(newValue, initializes: degrees) { + degrees = newValue * 180 / .pi + } + + get { degrees * .pi / 180 } + set { degrees = newValue * 180 / .pi } + } + + init(degrees: Double) { + self.degrees = degrees // sets 'self.degrees' directly + } + + init(radians: Double) { + self.radians = radians // calls init accessor with 'radians' + } +} +``` + +The signature of an `init` accessor specifies the property's access dependencies and the set of stored properties that are initialized by the accessor. Access dependencies must be initialized before the computed property's `init` accessor is invoked, and the `init` accessor must initialize the specified stored properties on all control flow paths. + +With this proposal, property wrappers have no bespoke definite initialization support. Instead, the desugaring includes an `init` accessor for wrapped properties. The property wrapper code in the Motivation section will desugar to the following code: + +```swift +@propertyWrapper +struct Wrapper { + var wrappedValue: T +} + +struct S { + private var _value: Wrapper + var value: Int { + init(newValue, initializes: _value) { + self._value = Wrapper(wrappedValue: newValue) + } + + get { _value.wrappedValue } + set { _value.wrappedValue = newValue } + } + + init(value: Int) { + self.value = value // Calls 'init' accessor on 'value' + } +} +``` + +This proposal allows macros to model the following property-wrapper-like patterns including out-of-line initialization of the computed property: +* A wrapped property with attribute arguments +* A wrapped property that is backed by an explicit stored property +* A set of wrapped properties that are backed by a single stored property + +## Detailed design + +### Syntax + +This proposal adds new syntax for `init` accessor blocks, which can be written in the accessor list of a computed property. Init accessors add the following production rules to the grammar: + +``` +init-accessor -> 'init' init-accessor-signature[opt] function-body + +init-accessor-signature -> '(' init-dependency-clause ')' + +init-dependency-clause -> 'newValue' +init-dependency-clause -> 'newValue' ',' init-dependencies + +init-dependencies -> subsumes-list +init-dependencies -> subsumes-list ',' accesses-list +init-dependences -> access-list + +subsumes-list -> 'initializes' ':' identifier-list + +accesses-list -> 'accesses' ':' identifier-list + +identifier-list -> identifier +identifier-list -> identifier ',' identifier-list + +// Not actually sure if `get` and `set` appearing once is baked into the grammar or is a semantic restriction +accessor-block -> init-accessor +``` + +### `init` accessor signatures + +`init` accessor declarations can optionally specify a signature. An `init` accessor signature is composed of the `newValue` parameter, a list of stored properties that are initialized by this accessor specified with the `initializes:` label, and a list of stored properties that are accessed by this accessor specified with the `accesses:` label: + +```swift +struct S { + var readMe: String + + var _x: Int + + var x: Int { + init(newValue, initializes: _x, accesses: readMe) { + print(readMe) + _x = newValue + } + + get { _x } + set { _x = newValue } + } +} +``` + +If the accessor only uses `newValue`, the signature is not required. + +Init accessors can subsume the initialization of a set of stored properties. Subsumed stored properties are specified through the `initializes:` clause of the accessor signature. The body of an `init` accessor is required to initialize the subsumed stored properties on all paths. + +Init accessors can also require a set of stored properties to already be initialized when the body is evaluated, which are specified through the `accesses:` cause of the signature. These stored properties can be accessed in the accessor body; no other properties or methods on `self` are available inside the accessor body. + +### Definite initialization of properties on `self` + +The semantics of an assignment inside of a type's initializer depend on whether or not all of `self` is initialized on all paths at the point of assignment. Before `self` is initialized, assignment to a wrapped property is re-written to initialization of the backing property wrapper storage. After `self` is initialized, assignment to a wrapped property is re-written to a call to the wrapped property's setter. For computed properties with `init` accessors, assignment is re-written to an `init` accessor call before `self` is initialized, and assignment is re-written to a setter call after `self` is initialized. + +With this proposal, all of `self` is initialized if: +* All stored properties are initialized on all paths. +* All computed properties with `init` accessors are virtually initialized on all paths. + +An assignment to a computed property with an `init` accessor before all of `self` is initialized covers the computed property and all stored properties specified in the `initializes` clause: + +```swift +struct S { + var x1: Int + var x2: Int + var computed: Int { + init(newValue, initializes: x1, x2) { ... } + } + + init() { + self.computed = 1 // initializes 'computed', 'x1', and 'x2' + } +} +``` + +An assignment to a stored property before all of `self` is initialized covers the initialization of computed properties with `init` accessors that specify that stored property if the other `initializes:` dependencies are already initialized: + +```swift +struct S { + var x1: Int + var x2: Int + var computed: Int { + init(newValue, initializes: x1, x2) { ... } + } + + init() { + self.computed = 1 // initializes 'computed', 'x1', and 'x2' + } +} +``` + +A stored property is considered initialized if it is assigned a value directly, or if a computed property that subsumes its initialization is assigned a value directly: + +```swift +struct S { + var x: Int + var y: Int + var point: (Int, Int) { + init(newValue, initializes: x, y) { + (self.x, self.y) = newValue + } + get { (x, y) } + set { (x, y) = newValue } + } + + init(x: Int, y: Int) { + self.x = x // Only initializes 'x' + self.y = y // Initializes 'y' and 'point' + } +} +``` + +### Memberwise initializers + +If a struct does not declare its own initializers, it receives an implicit memberwise initializer based on the stored properties of the struct, because the storage is what needs to be initialized. Because `init` provide a preferred mechanism for initializing storage, the memberwise initializer parameter list will include any computed properties that subsume the initialization of stored properties instead of parameters for those stored properties. + +```swift +struct S { + var _x: Int + var x: Int { + init(newValue, initializes: _x) { + _x = newValue + } + + get { _x } + set { _x = newValue } + } + + var y: Int +} + +S(x: 10, y: 100) +``` + +The above struct `S` receives a synthesized initializer: + +```swift +init(x: Int, y: Int) { + self.x = x + self.y = y +} +``` + +A memberwise initializer cannot be synthesized if a stored property that is an `accesses:` dependency of a computed property is ordered after that computed property in the source code: + +```swift +struct S { + var _x: Int + var x: Int { + init(newValue, initializes: _x, reads: y) { + _x = newValue + } + + get { _x } + set { _x = newValue } + } + + var y: Int +} +``` + +The above struct would receive the following memberwise initializer, which is invalid so an error is emitted: + +```swift +init(x: Int, y: Int) { + self.x = x // error + self.y = y +} +``` + +TODO: define whether macro-generated members are ordered before or after their 'attached-to' declaration for peer macros, or before or after the full member list for member macros. + +## Source compatibility + +`init` accessors are an additive capability with new syntax; there is no impact on existing source code. + +## ABI compatibility + +`init` accessors are only called from within a module, so they are not part of the module's ABI. In cases where a type's initializer is `@inlinable`, the body of an `init` accessor must also be inlinable. + +## Implications on adoption + +Because `init` accessors are always called from within the defining module, adopting `init` accessors is an ABI-compatible change. Adding an `init` accessor to an existing property also cannot have any source compatibility impact outside of the defining module; the only possible source incompatibilities are on the generated memberwise initializer (if new entries are added), or on the type's `init` implementation (if new initialization dependencies are added). + +## Future directions + +### `init` accessors for local variables + +`init` accessors for local variables have different implications on definite initialization, because re-writing assignment to `init` or `set` is not based on the initialization state of `self`. Local variable getters and setters can also capture any other local variables in scope, which raises more challenges for diagnosing escaping uses before initialization during the same pass where assignments may be re-written to `init` or `set`. As such, local variables with `init` accessors are a future direction. From 4a2813dbf6040a90aba08778c57915194934f6c5 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 8 May 2023 12:36:18 -0700 Subject: [PATCH 02/13] Init accessors suggestions (#2) * Expand description of accesses and give a "dictionary storage" example * Make the name of the `newValue` parameter configurable Be consistent with set/willSet/didSet in making the parameter name optional and overridable. * A few clarifications regarding init accessor signatures * Clarify the rules for definite initialization Expand the examples and show the case where virtual and stored-property initialization collide. --------- Co-authored-by: Holly Borla --- proposals/NNNN-init-accessors.md | 79 ++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 19 deletions(-) diff --git a/proposals/NNNN-init-accessors.md b/proposals/NNNN-init-accessors.md index 0c96d46cbb..6d4ac2377a 100644 --- a/proposals/NNNN-init-accessors.md +++ b/proposals/NNNN-init-accessors.md @@ -75,7 +75,42 @@ struct Angle { } ``` -The signature of an `init` accessor specifies the property's access dependencies and the set of stored properties that are initialized by the accessor. Access dependencies must be initialized before the computed property's `init` accessor is invoked, and the `init` accessor must initialize the specified stored properties on all control flow paths. +The signature of an `init` accessor specifies up to two sets of stored properties: the access dependencies (via `accesses`) and the initialized properties (via `initializes`). Access dependencies specify the other stored properties that can be accessed from within the `init` accessor (no other uses of `self` are allowed), and therefore must be initialized before the computed property's `init` accessor is invoked. The `init` accessor must initialize each of the initialized stored properties on all control flow paths. The `radians` property in the example above specifies no access dependencies, but initializes the `degrees` property, so it specifies only `initializes: degrees`. + +Access dependencies allow a computed property to be initialized by placing its contents into another stored property: + +```swift +struct ProposalViaDictionary { + private var dictionary: [String: String] = [:] + + var title: String { + init(newValue, accesses: dictionary) { + dictionary["title"] = newValue + } + + get { dictionary["title"]! } + set { dictionary["title"] = newValue } + } + + var text: String { + init(newValue, accesses: dictionary) { + dictionary["text"] = newValue + } + + get { dictionary["text"]! } + set { dictionary["text"] = newValue } + } + + init(title: String, text: String) { + self.title = title // calls init accessor to insert title into the dictionary + self.text = text // calls init accessor to insert text into the dictionary + + // it is an error to omit either initialization above + } +} +``` + +Both `init` accessors document that they access `dictionary`, which allows them to insert the new values into the dictionary with the appropriate key as part of initialization. This allows one to fully abstract away the storage mechanism used in the type. With this proposal, property wrappers have no bespoke definite initialization support. Instead, the desugaring includes an `init` accessor for wrapped properties. The property wrapper code in the Motivation section will desugar to the following code: @@ -116,16 +151,17 @@ This proposal adds new syntax for `init` accessor blocks, which can be written i ``` init-accessor -> 'init' init-accessor-signature[opt] function-body -init-accessor-signature -> '(' init-dependency-clause ')' +init-accessor-signature -> '(' init-dependency-clause [opt] ')' -init-dependency-clause -> 'newValue' -init-dependency-clause -> 'newValue' ',' init-dependencies +init-dependency-clause -> identifier +init-dependency-clause -> identifier ',' init-dependencies +init-dependency-clause -> init-dependencies -init-dependencies -> subsumes-list -init-dependencies -> subsumes-list ',' accesses-list +init-dependencies -> initializes-list +init-dependencies -> initializes-list ',' accesses-list init-dependences -> access-list -subsumes-list -> 'initializes' ':' identifier-list +initializes-list -> 'initializes' ':' identifier-list accesses-list -> 'accesses' ':' identifier-list @@ -136,9 +172,11 @@ identifier-list -> identifier ',' identifier-list accessor-block -> init-accessor ``` +The `identifier` in an `init-dependency-clause`, if provided, is the name of the parameter that contains the initial value. If not provided, a parameter with the name `newValue` is automatically created. + ### `init` accessor signatures -`init` accessor declarations can optionally specify a signature. An `init` accessor signature is composed of the `newValue` parameter, a list of stored properties that are initialized by this accessor specified with the `initializes:` label, and a list of stored properties that are accessed by this accessor specified with the `accesses:` label: +`init` accessor declarations can optionally specify a signature. An `init` accessor signature is composed of a parameter for the initial value, a list of stored properties that are initialized by this accessor specified with the `initializes:` label, and a list of stored properties that are accessed by this accessor specified with the `accesses:` labe, all of which are optional: ```swift struct S { @@ -158,21 +196,21 @@ struct S { } ``` -If the accessor only uses `newValue`, the signature is not required. +If the accessor uses the default parameter name `newValue` and neither initializes nor accesses any stored property, the signature is not required. -Init accessors can subsume the initialization of a set of stored properties. Subsumed stored properties are specified through the `initializes:` clause of the accessor signature. The body of an `init` accessor is required to initialize the subsumed stored properties on all paths. +Init accessors can subsume the initialization of a set of stored properties. Subsumed stored properties are specified through the `initializes:` clause of the accessor signature. The body of an `init` accessor is required to initialize the subsumed stored properties on all control flow paths. -Init accessors can also require a set of stored properties to already be initialized when the body is evaluated, which are specified through the `accesses:` cause of the signature. These stored properties can be accessed in the accessor body; no other properties or methods on `self` are available inside the accessor body. +Init accessors can also require a set of stored properties to already be initialized when the body is evaluated, which are specified through the `accesses:` cause of the signature. These stored properties can be accessed in the accessor body; no other properties or methods on `self` are available inside the accessor body, nor is `self` available as a whole object (i.e., to call methods on it). ### Definite initialization of properties on `self` -The semantics of an assignment inside of a type's initializer depend on whether or not all of `self` is initialized on all paths at the point of assignment. Before `self` is initialized, assignment to a wrapped property is re-written to initialization of the backing property wrapper storage. After `self` is initialized, assignment to a wrapped property is re-written to a call to the wrapped property's setter. For computed properties with `init` accessors, assignment is re-written to an `init` accessor call before `self` is initialized, and assignment is re-written to a setter call after `self` is initialized. +The semantics of an assignment inside of a type's initializer depend on whether or not all of `self` is initialized on all paths at the point of assignment. Before all of `self` is initialized, assignment to a computed property with an `init` accessor is re-written to an `init` accessor call; after `self` has been initialized, assignment to a computed property is re-written to a setter call. With this proposal, all of `self` is initialized if: -* All stored properties are initialized on all paths. +* All stored properties are initialized on all paths, and * All computed properties with `init` accessors are virtually initialized on all paths. -An assignment to a computed property with an `init` accessor before all of `self` is initialized covers the computed property and all stored properties specified in the `initializes` clause: +An assignment to a computed property with an `init` accessor before all of `self` is initialized will virtually initialize the computed property and initialize all of the stored properties specified in its `initializes` clause: ```swift struct S { @@ -183,28 +221,31 @@ struct S { } init() { - self.computed = 1 // initializes 'computed', 'x1', and 'x2' + self.computed = 1 // initializes 'computed', 'x1', and 'x2'; 'self' is now fully initialized } } ``` -An assignment to a stored property before all of `self` is initialized covers the initialization of computed properties with `init` accessors that specify that stored property if the other `initializes:` dependencies are already initialized: +An assignment to a stored property before all of `self` is initialized will initialize that stored property. When all of the stored properties listed in the `initializes:` clause of a computed property with an `init` accessor have been initialized, that computed property is virtually initialized: ```swift struct S { var x1: Int var x2: Int + var x3: Int var computed: Int { init(newValue, initializes: x1, x2) { ... } } init() { - self.computed = 1 // initializes 'computed', 'x1', and 'x2' + self.x1 = 1 // initializes 'x1'; neither 'x2' or 'computed' is initialized + self.x2 = 1 // initializes 'x2' and 'computed' + self.x3 = 1 // initializes 'x3'; 'self' is now fully initialized } } ``` -A stored property is considered initialized if it is assigned a value directly, or if a computed property that subsumes its initialization is assigned a value directly: +An assignment to a computed property where at least one of the stored properties listed in `initializes:` is initialized, but `self` is not initialized, is an error. This prevents double-initialization of the underlying stored properties: ```swift struct S { @@ -220,7 +261,7 @@ struct S { init(x: Int, y: Int) { self.x = x // Only initializes 'x' - self.y = y // Initializes 'y' and 'point' + self.point = (x, y) // error: neither the `init` accessor nor the setter can be called here } } ``` From 72a2cdd24cb9869b5b812949c363a00b7d2f40b2 Mon Sep 17 00:00:00 2001 From: Holly Borla Date: Mon, 8 May 2023 12:40:28 -0700 Subject: [PATCH 03/13] Address outstanding proposal comments. --- proposals/NNNN-init-accessors.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/proposals/NNNN-init-accessors.md b/proposals/NNNN-init-accessors.md index 6d4ac2377a..caf4b35d9e 100644 --- a/proposals/NNNN-init-accessors.md +++ b/proposals/NNNN-init-accessors.md @@ -168,7 +168,6 @@ accesses-list -> 'accesses' ':' identifier-list identifier-list -> identifier identifier-list -> identifier ',' identifier-list -// Not actually sure if `get` and `set` appearing once is baked into the grammar or is a semantic restriction accessor-block -> init-accessor ``` @@ -324,7 +323,7 @@ init(x: Int, y: Int) { } ``` -TODO: define whether macro-generated members are ordered before or after their 'attached-to' declaration for peer macros, or before or after the full member list for member macros. +Note that macro-expanded declarations are ordered after the attached-to declaration for peer macros, and at the end of the member list as written for member macros. ## Source compatibility From ef2e2c6b622f99aad4a7cbb9b258e4badfd35515 Mon Sep 17 00:00:00 2001 From: Holly Borla Date: Tue, 30 May 2023 13:45:44 -0700 Subject: [PATCH 04/13] Update the syntax for init accessor effects based on pitch feedback. --- proposals/NNNN-init-accessors.md | 70 +++++++++++++++++++------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/proposals/NNNN-init-accessors.md b/proposals/NNNN-init-accessors.md index caf4b35d9e..375e08e9af 100644 --- a/proposals/NNNN-init-accessors.md +++ b/proposals/NNNN-init-accessors.md @@ -57,7 +57,7 @@ This proposal adds _`init` accessors_ to opt computed properties on types into v struct Angle { var degrees: Double var radians: Double { - init(newValue, initializes: degrees) { + init(newValue) initializes(degrees) { degrees = newValue * 180 / .pi } @@ -75,16 +75,16 @@ struct Angle { } ``` -The signature of an `init` accessor specifies up to two sets of stored properties: the access dependencies (via `accesses`) and the initialized properties (via `initializes`). Access dependencies specify the other stored properties that can be accessed from within the `init` accessor (no other uses of `self` are allowed), and therefore must be initialized before the computed property's `init` accessor is invoked. The `init` accessor must initialize each of the initialized stored properties on all control flow paths. The `radians` property in the example above specifies no access dependencies, but initializes the `degrees` property, so it specifies only `initializes: degrees`. +The signature of an `init` accessor specifies up to two sets of stored properties: the properties that are accessed (via `accesses`) and the properties that are initialized (via `initializes`) by the accessor. `initializes` and `accesses` are side-effects of the `init` accessor. Access effects specify the other stored properties that can be accessed from within the `init` accessor (no other uses of `self` are allowed), and therefore must be initialized before the computed property's `init` accessor is invoked. The `init` accessor must initialize each of the initialized stored properties on all control flow paths. The `radians` property in the example above specifies no access effect, but initializes the `degrees` property, so it specifies only `initializes: degrees`. -Access dependencies allow a computed property to be initialized by placing its contents into another stored property: +Access effects allow a computed property to be initialized by placing its contents into another stored property: ```swift struct ProposalViaDictionary { private var dictionary: [String: String] = [:] var title: String { - init(newValue, accesses: dictionary) { + init(newValue) accesses(dictionary) { dictionary["title"] = newValue } @@ -93,7 +93,7 @@ struct ProposalViaDictionary { } var text: String { - init(newValue, accesses: dictionary) { + init(newValue) accesses(dictionary) { dictionary["text"] = newValue } @@ -123,7 +123,7 @@ struct Wrapper { struct S { private var _value: Wrapper var value: Int { - init(newValue, initializes: _value) { + init(newValue) initializes(_value) { self._value = Wrapper(wrappedValue: newValue) } @@ -149,21 +149,13 @@ This proposal allows macros to model the following property-wrapper-like pattern This proposal adds new syntax for `init` accessor blocks, which can be written in the accessor list of a computed property. Init accessors add the following production rules to the grammar: ``` -init-accessor -> 'init' init-accessor-signature[opt] function-body +init-accessor -> 'init' init-accessor-parameter[opt] init-effect[opt] access-effect[opt] function-body -init-accessor-signature -> '(' init-dependency-clause [opt] ')' +init-accessor-parameter -> '(' identifier ')' -init-dependency-clause -> identifier -init-dependency-clause -> identifier ',' init-dependencies -init-dependency-clause -> init-dependencies +init-effect -> 'initializes' '(' identifier-list ')' -init-dependencies -> initializes-list -init-dependencies -> initializes-list ',' accesses-list -init-dependences -> access-list - -initializes-list -> 'initializes' ':' identifier-list - -accesses-list -> 'accesses' ':' identifier-list +access-effect -> 'accesses' '(' identifier-list ')' identifier-list -> identifier identifier-list -> identifier ',' identifier-list @@ -171,11 +163,11 @@ identifier-list -> identifier ',' identifier-list accessor-block -> init-accessor ``` -The `identifier` in an `init-dependency-clause`, if provided, is the name of the parameter that contains the initial value. If not provided, a parameter with the name `newValue` is automatically created. +The `identifier` in an `init-accessor-parameter`, if provided, is the name of the parameter that contains the initial value. If not provided, a parameter with the name `newValue` is automatically created. ### `init` accessor signatures -`init` accessor declarations can optionally specify a signature. An `init` accessor signature is composed of a parameter for the initial value, a list of stored properties that are initialized by this accessor specified with the `initializes:` label, and a list of stored properties that are accessed by this accessor specified with the `accesses:` labe, all of which are optional: +`init` accessor declarations can optionally specify a signature. An `init` accessor signature is composed of a parameter for the initial value, a list of stored properties that are initialized by this accessor specified with the contextual `initializes` keyword, and a list of stored properties that are accessed by this accessor specified with the contextual `accesses` keyword, all of which are optional: ```swift struct S { @@ -184,7 +176,7 @@ struct S { var _x: Int var x: Int { - init(newValue, initializes: _x, accesses: readMe) { + init(newValue) initializes(_x) accesses(readMe) { print(readMe) _x = newValue } @@ -216,7 +208,7 @@ struct S { var x1: Int var x2: Int var computed: Int { - init(newValue, initializes: x1, x2) { ... } + init(newValue) initializes(x1, x2) { ... } } init() { @@ -233,7 +225,7 @@ struct S { var x2: Int var x3: Int var computed: Int { - init(newValue, initializes: x1, x2) { ... } + init(newValue) initializes(x1, x2) { ... } } init() { @@ -251,7 +243,7 @@ struct S { var x: Int var y: Int var point: (Int, Int) { - init(newValue, initializes: x, y) { + init(newValue) initializes(x, y) { (self.x, self.y) = newValue } get { (x, y) } @@ -273,7 +265,7 @@ If a struct does not declare its own initializers, it receives an implicit membe struct S { var _x: Int var x: Int { - init(newValue, initializes: _x) { + init(newValue) initializes(_x) { _x = newValue } @@ -296,13 +288,13 @@ init(x: Int, y: Int) { } ``` -A memberwise initializer cannot be synthesized if a stored property that is an `accesses:` dependency of a computed property is ordered after that computed property in the source code: +A memberwise initializer cannot be synthesized if a stored property that is an `accesses` effect of a computed property is ordered after that computed property in the source code: ```swift struct S { var _x: Int var x: Int { - init(newValue, initializes: _x, reads: y) { + init(newValue) initializes(_x) accesses(y) { _x = newValue } @@ -335,7 +327,29 @@ Note that macro-expanded declarations are ordered after the attached-to declarat ## Implications on adoption -Because `init` accessors are always called from within the defining module, adopting `init` accessors is an ABI-compatible change. Adding an `init` accessor to an existing property also cannot have any source compatibility impact outside of the defining module; the only possible source incompatibilities are on the generated memberwise initializer (if new entries are added), or on the type's `init` implementation (if new initialization dependencies are added). +Because `init` accessors are always called from within the defining module, adopting `init` accessors is an ABI-compatible change. Adding an `init` accessor to an existing property also cannot have any source compatibility impact outside of the defining module; the only possible source incompatibilities are on the generated memberwise initializer (if new entries are added), or on the type's `init` implementation (if new initialization effects are added). + +## Alternatives considered + +A previous version of this proposal specified init accessor effects in the parameter list using special labels: + +```swift +struct S { + var _x: Int + var x: Int { + init(newValue, initializes: _x, accesses: y) { + _x = newValue + } + + get { _x } + set { _x = newValue } + } + + var y: Int +} +``` + +This syntax choice is misleading because the effects look like function parameters, while `initializes` behaves more like the output of an init accessor, and `accesses` are not explicitly provided at the call-site. Conceptually, `initializes` and `accesses` are side effects of an `init` accessor, so the proposal was revised to place these modifiers in the effects clause. ## Future directions From a4d53f80e01a613ccb74f88af809cebdbba88b18 Mon Sep 17 00:00:00 2001 From: Holly Borla Date: Tue, 30 May 2023 14:31:35 -0700 Subject: [PATCH 05/13] Update the first init accessor example to use a custom parameter name. --- proposals/NNNN-init-accessors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proposals/NNNN-init-accessors.md b/proposals/NNNN-init-accessors.md index 375e08e9af..9c3e856108 100644 --- a/proposals/NNNN-init-accessors.md +++ b/proposals/NNNN-init-accessors.md @@ -57,8 +57,8 @@ This proposal adds _`init` accessors_ to opt computed properties on types into v struct Angle { var degrees: Double var radians: Double { - init(newValue) initializes(degrees) { - degrees = newValue * 180 / .pi + init(initialValue) initializes(degrees) { + degrees = initialValue * 180 / .pi } get { degrees * .pi / 180 } From 50ae97a40682c9e63aab75992fc9684ce48ac0a7 Mon Sep 17 00:00:00 2001 From: Holly Borla Date: Tue, 30 May 2023 14:38:04 -0700 Subject: [PATCH 06/13] Add an acknowledgments section for the init accessors proposal. --- proposals/NNNN-init-accessors.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/proposals/NNNN-init-accessors.md b/proposals/NNNN-init-accessors.md index 9c3e856108..3c52b43a91 100644 --- a/proposals/NNNN-init-accessors.md +++ b/proposals/NNNN-init-accessors.md @@ -356,3 +356,7 @@ This syntax choice is misleading because the effects look like function paramete ### `init` accessors for local variables `init` accessors for local variables have different implications on definite initialization, because re-writing assignment to `init` or `set` is not based on the initialization state of `self`. Local variable getters and setters can also capture any other local variables in scope, which raises more challenges for diagnosing escaping uses before initialization during the same pass where assignments may be re-written to `init` or `set`. As such, local variables with `init` accessors are a future direction. + +## Acknowledgments + +Thank you to TJ Usiyan, Michel Fortin, and others for suggesting alternative syntax ideas for `init` accessor effects; thank you to Pavel Yaskevich for helping with the implementation. \ No newline at end of file From 08387e7b76f68704addf01008522ea1ae1a59289 Mon Sep 17 00:00:00 2001 From: Holly Borla Date: Sat, 10 Jun 2023 13:27:30 -0700 Subject: [PATCH 07/13] Update proposals/NNNN-init-accessors.md Co-authored-by: Frederick Kellison-Linn --- proposals/NNNN-init-accessors.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/proposals/NNNN-init-accessors.md b/proposals/NNNN-init-accessors.md index 3c52b43a91..462f7fb809 100644 --- a/proposals/NNNN-init-accessors.md +++ b/proposals/NNNN-init-accessors.md @@ -4,14 +4,15 @@ * Authors: [Holly Borla](https://github.com/hborla), [Doug Gregor](https://github.com/douggregor) * Review Manager: TBD * Status: **Awaiting implementation** +* Implementation: On `main` behind experimental feature flag `InitAccessors` ## Introduction -Init accessors generalize the out-of-line initialization feature of property wrappers to allow any computed property on types to opt into definite initialization analysis, and subsume initialization of a stored property with custom initialization code. +Init accessors generalize the out-of-line initialization feature of property wrappers to allow any computed property on types to opt into definite initialization analysis, and subsume initialization of a set of stored properties with custom initialization code. ## Motivation -Swift applies [definite initialization analysis](https://en.wikipedia.org/wiki/Definite_assignment_analysis) to stored properties, stored local variables, and variables with property wrappers. Definite initialization ensures that memory is initialized on all paths before it is accessed. A common pattern in Swift code is to use one property as backing storage for one or more computed properties, and abstractions like [property wrappers](https://github.com/apple/swift-evolution/blob/main/proposals/0258-property-wrappers.md) and now [attached macros](https://github.com/apple/swift-evolution/blob/main/proposals/0389-attached-macros.md) help facilitate this pattern. Under this pattern, the backing storage is an implementation detail, and most code works with the computed property, including initializers. +Swift applies [definite initialization analysis](https://en.wikipedia.org/wiki/Definite_assignment_analysis) to stored properties, stored local variables, and variables with property wrappers. Definite initialization ensures that memory is initialized on all paths before it is accessed. A common pattern in Swift code is to use one property as backing storage for one or more computed properties, and abstractions like [property wrappers](https://github.com/apple/swift-evolution/blob/main/proposals/0258-property-wrappers.md) and [attached macros](https://github.com/apple/swift-evolution/blob/main/proposals/0389-attached-macros.md) help facilitate this pattern. Under this pattern, the backing storage is an implementation detail, and most code works with the computed property, including initializers. Property wrappers support bespoke definite initialization that allows initializing the backing property wrapper storage via the computed property, always re-writing initialization-via-wrapped-property in the form `self.value = value` to initialization of the backing storage in the form of `_value = Wrapper(wrappedValue: value)`: @@ -34,7 +35,7 @@ struct S { } ``` -The ad-hoc nature of property wrapper initializers mixed with an exact definite initialization pattern prevent property wrappers with additional arguments from being initialized out-of-line. Furthermore, property-wrapper-like macros cannot achieve the same initializer usability, because any backing storage variables added must be initialized directly instead of supporting initialization through computed properties. For example, the proposed [`@Observable` macro](https://github.com/apple/swift-evolution/blob/main/proposals/0395-observability.md) applies a property-wrapper-like transform that turns stored properties into computed properties backed by the observation APIs, but it provides no way to write an initializer using the original property names like the programmer expects: +The ad-hoc nature of property wrapper initializers mixed with an exact definite initialization pattern prevent property wrappers with additional arguments from being initialized out-of-line. Furthermore, property-wrapper-like macros cannot achieve the same initializer usability, because any backing storage variables added must be initialized directly instead of supporting initialization through computed properties. For example, the [`@Observable` macro](https://github.com/apple/swift-evolution/blob/main/proposals/0395-observability.md) applies a property-wrapper-like transform that turns stored properties into computed properties backed by the observation APIs, but it provides no way to write an initializer using the original property names like the programmer expects: ```swift @Observable @@ -66,7 +67,7 @@ struct Angle { } init(degrees: Double) { - self.degrees = degrees // sets 'self.degrees' directly + self.degrees = degrees // initializes 'self.degrees' directly } init(radians: Double) { @@ -75,7 +76,7 @@ struct Angle { } ``` -The signature of an `init` accessor specifies up to two sets of stored properties: the properties that are accessed (via `accesses`) and the properties that are initialized (via `initializes`) by the accessor. `initializes` and `accesses` are side-effects of the `init` accessor. Access effects specify the other stored properties that can be accessed from within the `init` accessor (no other uses of `self` are allowed), and therefore must be initialized before the computed property's `init` accessor is invoked. The `init` accessor must initialize each of the initialized stored properties on all control flow paths. The `radians` property in the example above specifies no access effect, but initializes the `degrees` property, so it specifies only `initializes: degrees`. +The signature of an `init` accessor specifies up to two sets of stored properties: the properties that are accessed (via `accesses`) and the properties that are initialized (via `initializes`) by the accessor. `initializes` and `accesses` are side-effects of the `init` accessor. Access effects specify the other stored properties that can be accessed from within the `init` accessor (no other uses of `self` are allowed), and therefore must be initialized before the computed property's `init` accessor is invoked. The `init` accessor must initialize each of the initialized stored properties on all control flow paths. The `radians` property in the example above specifies no access effect, but initializes the `degrees` property, so it specifies only `initializes(degrees)`. Access effects allow a computed property to be initialized by placing its contents into another stored property: @@ -217,7 +218,7 @@ struct S { } ``` -An assignment to a stored property before all of `self` is initialized will initialize that stored property. When all of the stored properties listed in the `initializes:` clause of a computed property with an `init` accessor have been initialized, that computed property is virtually initialized: +An assignment to a stored property before all of `self` is initialized will initialize that stored property. When all of the stored properties listed in the `initializes` clause of a computed property with an `init` accessor have been initialized, that computed property is virtually initialized: ```swift struct S { @@ -236,7 +237,7 @@ struct S { } ``` -An assignment to a computed property where at least one of the stored properties listed in `initializes:` is initialized, but `self` is not initialized, is an error. This prevents double-initialization of the underlying stored properties: +An assignment to a computed property where at least one of the stored properties listed in `initializes` is initialized, but `self` is not initialized, is an error. This prevents double-initialization of the underlying stored properties: ```swift struct S { From 532ce7e0052ac72a14a135a210cb440faed331ad Mon Sep 17 00:00:00 2001 From: Holly Borla Date: Mon, 12 Jun 2023 16:34:18 -0700 Subject: [PATCH 08/13] Remove the phrase "virtual initialization". --- proposals/NNNN-init-accessors.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/proposals/NNNN-init-accessors.md b/proposals/NNNN-init-accessors.md index 462f7fb809..f16dec757e 100644 --- a/proposals/NNNN-init-accessors.md +++ b/proposals/NNNN-init-accessors.md @@ -52,7 +52,7 @@ struct Proposal { ## Proposed solution -This proposal adds _`init` accessors_ to opt computed properties on types into virtual initialization that subsumes initialization of a set of zero or more specified stored properties, which allows assigning to computed properties in the body of a type's initializer: +This proposal adds _`init` accessors_ to opt computed properties on types into definite initialization that subsumes initialization of a set of zero or more specified stored properties, which allows assigning to computed properties in the body of a type's initializer: ```swift struct Angle { @@ -200,9 +200,9 @@ The semantics of an assignment inside of a type's initializer depend on whether With this proposal, all of `self` is initialized if: * All stored properties are initialized on all paths, and -* All computed properties with `init` accessors are virtually initialized on all paths. +* All computed properties with `init` accessors are initialized on all paths. -An assignment to a computed property with an `init` accessor before all of `self` is initialized will virtually initialize the computed property and initialize all of the stored properties specified in its `initializes` clause: +An assignment to a computed property with an `init` accessor before all of `self` is initialized will call the computed property's `init` accessor and initialize all of the stored properties specified in its `initializes` clause: ```swift struct S { @@ -218,7 +218,7 @@ struct S { } ``` -An assignment to a stored property before all of `self` is initialized will initialize that stored property. When all of the stored properties listed in the `initializes` clause of a computed property with an `init` accessor have been initialized, that computed property is virtually initialized: +An assignment to a stored property before all of `self` is initialized will initialize that stored property. When all of the stored properties listed in the `initializes` clause of a computed property with an `init` accessor have been initialized, that computed property is considered initialized: ```swift struct S { From 60c814aa1b41b7fb04f7b4fe22ae50bff9d11b17 Mon Sep 17 00:00:00 2001 From: Holly Borla Date: Mon, 12 Jun 2023 16:41:38 -0700 Subject: [PATCH 09/13] Editorial changes. --- proposals/NNNN-init-accessors.md | 34 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/proposals/NNNN-init-accessors.md b/proposals/NNNN-init-accessors.md index f16dec757e..e6f4901273 100644 --- a/proposals/NNNN-init-accessors.md +++ b/proposals/NNNN-init-accessors.md @@ -70,8 +70,8 @@ struct Angle { self.degrees = degrees // initializes 'self.degrees' directly } - init(radians: Double) { - self.radians = radians // calls init accessor with 'radians' + init(radiansParam: Double) { + self.radians = radiansParam // calls init accessor for 'self.radians', passing 'radiansParam' as the argument } } ``` @@ -82,7 +82,7 @@ Access effects allow a computed property to be initialized by placing its conten ```swift struct ProposalViaDictionary { - private var dictionary: [String: String] = [:] + private var dictionary: [String: String] var title: String { init(newValue) accesses(dictionary) { @@ -103,6 +103,7 @@ struct ProposalViaDictionary { } init(title: String, text: String) { + self.dictionary = [:] // 'dictionary' must be initialized before init accessors access it self.title = title // calls init accessor to insert title into the dictionary self.text = text // calls init accessor to insert text into the dictionary @@ -158,17 +159,26 @@ init-effect -> 'initializes' '(' identifier-list ')' access-effect -> 'accesses' '(' identifier-list ')' -identifier-list -> identifier -identifier-list -> identifier ',' identifier-list - accessor-block -> init-accessor ``` -The `identifier` in an `init-accessor-parameter`, if provided, is the name of the parameter that contains the initial value. If not provided, a parameter with the name `newValue` is automatically created. +The `identifier` in an `init-accessor-parameter`, if provided, is the name of the parameter that contains the initial value. If not provided, a parameter with the name `newValue` is automatically created. The minimal init accessor has no parameter list and no initialization effects: + +```swift +struct Minimal { + var value: Int { + init { + print("init accessor called with \(newValue)") + } + + get { 0 } + } +} +``` ### `init` accessor signatures -`init` accessor declarations can optionally specify a signature. An `init` accessor signature is composed of a parameter for the initial value, a list of stored properties that are initialized by this accessor specified with the contextual `initializes` keyword, and a list of stored properties that are accessed by this accessor specified with the contextual `accesses` keyword, all of which are optional: +`init` accessor declarations can optionally specify a signature. An `init` accessor signature is composed of a parameter list, followed by an initialization effects specifier clause. The initialization effects can include a list of stored properties that are initialized by this accessor specified in the argument list of the contextual `initializes` keyword, and a list of stored properties that are accessed by this accessor specified in the argument list of the contextual `accesses` keyword, each of which are optional: ```swift struct S { @@ -190,9 +200,9 @@ struct S { If the accessor uses the default parameter name `newValue` and neither initializes nor accesses any stored property, the signature is not required. -Init accessors can subsume the initialization of a set of stored properties. Subsumed stored properties are specified through the `initializes:` clause of the accessor signature. The body of an `init` accessor is required to initialize the subsumed stored properties on all control flow paths. +Init accessors can subsume the initialization of a set of stored properties. Subsumed stored properties are specified through the `initializes` effect. The body of an `init` accessor is required to initialize the subsumed stored properties on all control flow paths. -Init accessors can also require a set of stored properties to already be initialized when the body is evaluated, which are specified through the `accesses:` cause of the signature. These stored properties can be accessed in the accessor body; no other properties or methods on `self` are available inside the accessor body, nor is `self` available as a whole object (i.e., to call methods on it). +Init accessors can also require a set of stored properties to already be initialized when the body is evaluated, which are specified through the `accesses` effect. These stored properties can be accessed in the accessor body; no other properties or methods on `self` are available inside the accessor body, nor is `self` available as a whole object (i.e., to call methods on it). ### Definite initialization of properties on `self` @@ -289,7 +299,7 @@ init(x: Int, y: Int) { } ``` -A memberwise initializer cannot be synthesized if a stored property that is an `accesses` effect of a computed property is ordered after that computed property in the source code: +A memberwise initializer will not be synthesized if a stored property that is an `accesses` effect of a computed property is ordered after that computed property in the source code: ```swift struct S { @@ -316,8 +326,6 @@ init(x: Int, y: Int) { } ``` -Note that macro-expanded declarations are ordered after the attached-to declaration for peer macros, and at the end of the member list as written for member macros. - ## Source compatibility `init` accessors are an additive capability with new syntax; there is no impact on existing source code. From 7de3e10cd7be8e9ab6fc6b9d761eef47dc52c808 Mon Sep 17 00:00:00 2001 From: Holly Borla Date: Mon, 12 Jun 2023 17:24:41 -0700 Subject: [PATCH 10/13] Update the Alternatives Considered section with more syntax suggestions from the pitch thread. --- proposals/NNNN-init-accessors.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/proposals/NNNN-init-accessors.md b/proposals/NNNN-init-accessors.md index e6f4901273..7ae9d829f6 100644 --- a/proposals/NNNN-init-accessors.md +++ b/proposals/NNNN-init-accessors.md @@ -360,6 +360,15 @@ struct S { This syntax choice is misleading because the effects look like function parameters, while `initializes` behaves more like the output of an init accessor, and `accesses` are not explicitly provided at the call-site. Conceptually, `initializes` and `accesses` are side effects of an `init` accessor, so the proposal was revised to place these modifiers in the effects clause. +Other syntax suggestions from pitch reviewers included: + +* Using a capture-list-style clause, e.g. `init { [&x, y] in ... }` +* Attributes on the computed property itself, e.g. `@initializes(_x) var x: Int { ... }` +* Using more concise effect names, e.g. `writes` and `reads` instead of `initializes` and `accesses` +* And more! + +However, the current synatx in this proposal most accurately models the semantics of initialization effects. An `init` accessor is a function -- not a closure -- that has side-effects related to initialization. _Only_ the `init` accessor has these effects; though the `set` accessor often contains code that looks the same as the code in the `init` accessor, the effects of these accessors are different. Because `init` accessors are called before all of `self` is initialized, they do not recieve a fully-initialized `self` as a parameter like `set` accessors do, and assignments to `initializes` stored properties in `init` accessors have the same semantics as that of a standard initializer, such as suppressing `willSet` and `didSet` observers. These reasons reinforce the decision to specify `initializes` and `accesses` in the effects clause of an `init` accessor. + ## Future directions ### `init` accessors for local variables From 1addb6f9462238e32b2c7d1961a52b07900d3a9a Mon Sep 17 00:00:00 2001 From: Holly Borla Date: Mon, 12 Jun 2023 17:46:34 -0700 Subject: [PATCH 11/13] Add more commentary on member-wise initializers. --- proposals/NNNN-init-accessors.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/proposals/NNNN-init-accessors.md b/proposals/NNNN-init-accessors.md index 7ae9d829f6..581edd91d0 100644 --- a/proposals/NNNN-init-accessors.md +++ b/proposals/NNNN-init-accessors.md @@ -114,7 +114,7 @@ struct ProposalViaDictionary { Both `init` accessors document that they access `dictionary`, which allows them to insert the new values into the dictionary with the appropriate key as part of initialization. This allows one to fully abstract away the storage mechanism used in the type. -With this proposal, property wrappers have no bespoke definite initialization support. Instead, the desugaring includes an `init` accessor for wrapped properties. The property wrapper code in the Motivation section will desugar to the following code: +Finally, computed properties with `init` accessors are privileged in the synthesized member-wise initializer. With this proposal, property wrappers have no bespoke definite and member-wise initialization support. Instead, the desugaring for property wrappers with an `init(wrappedValue:)` includes an `init` accessor for wrapped properties and a member-wise initializer including wrapped values instead of the respective backing storage. The property wrapper code in the Motivation section will desugar to the following code: ```swift @propertyWrapper @@ -133,10 +133,13 @@ struct S { set { _value.wrappedValue = newValue } } + // This initializer is the same as the generated member-wise initializer. init(value: Int) { - self.value = value // Calls 'init' accessor on 'value' + self.value = value // Calls 'init' accessor on 'self.value' } } + +S(value: 10) ``` This proposal allows macros to model the following property-wrapper-like patterns including out-of-line initialization of the computed property: @@ -270,7 +273,7 @@ struct S { ### Memberwise initializers -If a struct does not declare its own initializers, it receives an implicit memberwise initializer based on the stored properties of the struct, because the storage is what needs to be initialized. Because `init` provide a preferred mechanism for initializing storage, the memberwise initializer parameter list will include any computed properties that subsume the initialization of stored properties instead of parameters for those stored properties. +If a struct does not declare its own initializers, it receives an implicit memberwise initializer based on the stored properties of the struct, because the storage is what needs to be initialized. Because many use-cases for `init` accessors are fully abstracting a single computed property to be backed by a single stored property, such as in the property-wrapper use case, an `init` accessor provides a preferred mechansim for initializing storage because the programmer will primarily interact with that storage through the computed property. As such, the memberwise initializer parameter list will include any computed properties that subsume the initialization of stored properties instead of parameters for those stored properties. ```swift struct S { @@ -326,6 +329,8 @@ init(x: Int, y: Int) { } ``` +Use cases for `init` accessors that provide a projection of a stored property as various units through several computed properties don't have a single preferred unit from which to initialize. Most likely, these use cases want a different member-wise initializer for each unit that you can initialize from. If a type contains several computed properties with `init` accessors that initialize the same stored property, a member-wise initializer will not be synthesized. + ## Source compatibility `init` accessors are an additive capability with new syntax; there is no impact on existing source code. From 047c1ab8bb2c73e74be131ac28476ba89febe65e Mon Sep 17 00:00:00 2001 From: Holly Borla Date: Mon, 12 Jun 2023 17:52:34 -0700 Subject: [PATCH 12/13] Add a DI example where a property is not initialized on all paths. --- proposals/NNNN-init-accessors.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/proposals/NNNN-init-accessors.md b/proposals/NNNN-init-accessors.md index 581edd91d0..f7dcc741ac 100644 --- a/proposals/NNNN-init-accessors.md +++ b/proposals/NNNN-init-accessors.md @@ -231,6 +231,34 @@ struct S { } ``` +An assignment to a computed property that has not been initialized on all paths will be re-written to an `init` accessor call: + +```swift +struct S { + var x: Int + var y: Int + var point: (Int, Int) { + init(newValue) initializes(x, y) { + (self.x, self.y) = newValue + } + get { (x, y) } + set { (x, y) = newValue } + } + + init(x: Int, y: Int) { + if (x == y) { + self.point = (x, x) // calls 'init' accessor + } + + // 'self.point' is not initialized on all paths here + + self.point = (x, y) // calls 'init' accessor + + // 'self.point' is initialized on all paths here + } +} +``` + An assignment to a stored property before all of `self` is initialized will initialize that stored property. When all of the stored properties listed in the `initializes` clause of a computed property with an `init` accessor have been initialized, that computed property is considered initialized: ```swift From 01b657eb54301403534daca6942a2c025c043ff5 Mon Sep 17 00:00:00 2001 From: Freddy Kellison-Linn Date: Wed, 14 Jun 2023 17:32:19 -0400 Subject: [PATCH 13/13] Init accessors is SE-0400 --- .../{NNNN-init-accessors.md => 0400-init-accessors.md} | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) rename proposals/{NNNN-init-accessors.md => 0400-init-accessors.md} (98%) diff --git a/proposals/NNNN-init-accessors.md b/proposals/0400-init-accessors.md similarity index 98% rename from proposals/NNNN-init-accessors.md rename to proposals/0400-init-accessors.md index f7dcc741ac..a5631df7dc 100644 --- a/proposals/NNNN-init-accessors.md +++ b/proposals/0400-init-accessors.md @@ -1,10 +1,11 @@ # Init Accessors -* Proposal: [SE-NNNN](NNNN-init-accessors.md) +* Proposal: [SE-0400](0400-init-accessors.md) * Authors: [Holly Borla](https://github.com/hborla), [Doug Gregor](https://github.com/douggregor) -* Review Manager: TBD -* Status: **Awaiting implementation** +* Review Manager: [Frederick Kellison-Linn](https://github.com/Jumhyn) +* Status: **Active review (June 14th...June 26th, 2023)** * Implementation: On `main` behind experimental feature flag `InitAccessors` +* Review: ([pitch](https://forums.swift.org/t/pitch-init-accessors/64881)) ## Introduction @@ -410,4 +411,4 @@ However, the current synatx in this proposal most accurately models the semantic ## Acknowledgments -Thank you to TJ Usiyan, Michel Fortin, and others for suggesting alternative syntax ideas for `init` accessor effects; thank you to Pavel Yaskevich for helping with the implementation. \ No newline at end of file +Thank you to TJ Usiyan, Michel Fortin, and others for suggesting alternative syntax ideas for `init` accessor effects; thank you to Pavel Yaskevich for helping with the implementation.