From dbab768bc058c23b7d5b99827680a19443519136 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Tue, 18 Feb 2025 10:04:17 -0800 Subject: [PATCH 1/3] Initial draft of proposal for `compilerSettings`. --- proposals/0000-compiler-settings.md | 283 ++++++++++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 proposals/0000-compiler-settings.md diff --git a/proposals/0000-compiler-settings.md b/proposals/0000-compiler-settings.md new file mode 100644 index 0000000000..661b59b11e --- /dev/null +++ b/proposals/0000-compiler-settings.md @@ -0,0 +1,283 @@ +# compilerSettings: a top level statement for enabling compiler flags locally in a specific file + +* Proposal: [SE-NNNN](NNNN-filename.md) +* Authors: [Michael Gottesman](https://github.com/gottesmm) +* Review Manager: TBD +* Status: **Awaiting implementation** +* Vision: [[Prospective Vision] Improving the approachability of data-race safety](https://forums.swift.org/t/prospective-vision-improving-the-approachability-of-data-race-safety/76183) +* Review: ([pitch](https://forums.swift.org/...)) + +## Introduction + +We propose introducing a new top level statement called `compilerSettings` that +allows for compiler flags to be enabled on a single file. This can be used to +enable warningsAsError, upcoming features, and default isolation among other +flags. For example: + +```swift +compilerSettings [ + .warningAsError(.concurrency), + .strictConcurrency(.complete), + .enableUpcomingFeature(.existentialAny), + .defaultIsolation(MainActor.self), +] +``` + +## Motivation + +Today Swift contains multiple ways of changing compiler and language behavior +from the command line including: + +* Enabling experimental and upcoming features. +* Enabling warnings as errors for diagnostic groups. +* Enabling strict concurrency when compiling pre-Swift 6 code. +* Specifying the default actor isolation via [Controlling Default Actor Isolation](). +* Requiring code to use [Strict Memory Safety](). + +Since these compiler and language behaviors can only be manipulated via the +command line and the compiler compiles one module at a time, users are forced to +apply these flags one module at a time instead of one file at a time. This +results in several forms of developer friction that we outline below. + +### Unnecessary Module Splitting + +Since compiler flags can only be applied to entire modules, one cannot apply a +compiler flag to a subset of files of a module. When confronted with this, users +are forced to split the subset of files into a separate module creating an +unnecessary module split. We foresee that this will occur more often in the +future if new features like [Controlling Default Actor Isolation]() and [Strict +MemorySafety]() are accepted by Swift Evolution. Consider the following +situations where unnecessary module splitting would be forced: + +* Consider a framework that by default uses `nonisolated` isolation, but wants + to expose a set of UI elements for users of the framework. In such a case, it + is reasonable to expect the author to want to make those specific files + containing UI elements to have a default isolation of `@MainActor` instead of + nonisolated. Without this feature one must introduce an additional module to + contain the UI elements for the framework when semantically there is really + only one framework. + +* Imagine an app that has enabled `@MainActor` isolation by default since it + contains mostly UI elements but that wishes to implement helper abstractions + that interact with a database on a background task. This helper code is + naturally expressed as having a default isolation of nonisolated implying that + one must either split the database helpers into a separate module or must mark + most declarations in the database code with nonisolated by hand. + +* Visualize a banking framework that contains both UI elements and functionality + for directly manipulating the account data of a customer. In such a case the + code that actually modifies customer data may want to enable strict memory + safety to increase security. In contrast, strict memory safety is not + necessary for the UI elements that the framework defines. To support both uses + cases, a module split must be introduced. + +### Preventing per-file based migration of large modules using warningsAsErrors + +Swift has taken the position that updating modules for a new language mode or +feature should be accomplished by: + +1. Enabling warnings on the entire module by enabling upcoming features from the + command line. + +2. Updating the module incrementally until all of the warnings have been + eliminated from the module. + +3. Updating the language version of the module so from that point on warnings + will become errors to prevent backsliding. + +This creates developer friction when applied to large modules, since the size of +the module makes it difficult to eliminate all of the warnings at once. This +results in either the module being updated over time on main during which main +is left in an intermediate, partially updated state or the module being updated +on a branch that is merged once all updates have been completed. In the former +case, it is easy to introduce new warnings as new code is added to the codebase +since only warnings are being used. In the later case, the branch must be kept +up to date in the face of changes being made to the codebase on main, forcing +one to continually need to update the branch to resolve merge conflicts against +main. + +In contrast by allowing for command line flags to be applied one file at a time, +users can update code using a warningsAsErrors based migration path. This +involves instead: + +1. Enabling warningsAsErrors for the new feature on the specific file. + +2. Fixing all of the errors in the file. + +3. Commiting the updated file into main. + +Since one is only updating one file at a time and using warningsAsErrors, the +updates can be done incrementally on a per file basis, backsliding cannot occur +in the file, and most importantly main is never in an intermediate, partially +updated state. + +## Proposed solution + +We propose introducing a new top level statement called `compilerSettings` that +takes a list of enums of type `CompilerSetting`. Each enum case's associated +values represent arguments that would be passed as an argument to the compiler +flag on the command line. The compiler upon parsing a `compilerSettings` +statement updates its internal state to set the appropriate settings before +compiling the rest of the file. An example of such a `compilerSettings` +statement is the following: + +```swift +compilerSettings [ + .warningAsError(.concurrency), + .strictConcurrency(.complete), + .enableUpcomingFeature(.existentialAny), + .defaultIsolation(MainActor.self), +] +``` + +By specifying compiler options in such a manner, we are able to solve all of the +problems above: + +1. The programmer can avoid module splitting when they wish to use compiler + flags only on a specific file. + +2. The programmer can specify options on a per file basis instead of only on a + per module basis allowing for warningsAsError to be used per file instead of + per module migration to use new features. + +As an additional benefit since enums are being used, code completion can make +the compiler options discoverable to the user in the IDE. + +## Detailed design + +We will update the swift grammar to allow for a new top level statement called +`compilerSettings` that takes a list of expressions: + +```text +top-level-statement ::= 'compilerSettings' '[' (expr ',')* expr ','? ']' +``` + +Each expression passed to `compilerSettings` is an instance of the enum +`CompilerSetting` that specifies a command line option that is to be applied to +the file. `CompilerSetting` will be a resilient enum defined in the standard +library that provides the following API: + +``` +public enum CompilerSetting { + case enableUpcomingFeature(UpcomingFeatureCompilerSetting) + case enableExperimentalFeature(ExperimentalFeatureCompilerSetting) + case warningAsError(WarningAsErrorCompilerSetting) + case defaultIsolation(Actor.Type) + case strictConcurrency(StrictConcurrencyCompilerSetting) +} +``` + +We purposely make `CompilerSetting` resilient so additional kinds of compiler +settings can be added over time without breaking ABI. + +`CompilerSetting` provides an enum case for each command line option and each +case is able to provide associated values to specify arguments that would +normally be passed on the command line to the option. These associated values +can be one of: + +* an already existing type (for example `Actor.Type`). + +* a custom fixed enum defined as a subtype of `CompilerSetting` (for example + `StrictConcurrencyCompilerSetting`). + +* a custom enum defined as a subtype of `CompilerSetting` that the compiler + synthesizes cases for depending on internal compiler data (for example + features for `UpcomingFeatureCompilerSetting`). + +We purposely keep all custom types defined for `CompilerSetting` as nested types +within `CompilerSetting` in order to avoid polluting the global namespace. Thus +we would necessarily define in the stdlib all of the custom types as: + +```swift +extension CompilerSetting { + // Cases synthesized by the compiler. + public enum UpcomingFeatureCompilerSetting { } + + // Cases synthesized by the compiler. + public enum ExperimentalFeatureCompilerSetting { } + + // Cases synthesized by the compiler. + public enum WarningsAsErrorsCompilerSetting { } + + // We know ahead of time all of the cases, so this is specified explicitly. + public enum StrictConcurrency { + case minimal + case targeted + case complete + } +} +``` + +In order to ensure that compiler settings are easy to find in the file, we +require that `compilerSettings` be the first statement in the file. + +By default command line flags will not be allowed to be passed to +`compilerSettings`. Instead, we will require a compiler flag to explicitly opt +in to being supported by `compilerSettings`. We require that since: + +1. Certain compiler flags like `-enable-library-evolution` have effects that can + not be constrained to a single file since they are inherently module wide + flags. + +2. Other compiler flags whose impacts would necessitate exposing + `compilerSettings` in textual modules. We view supported `compilerSettings` + as an anti-goal since appearing in the module header makes enabling + `compilerSettings` affect the way the module is interpreted by users, while + we are designing `compilerSettings` to have an impact strictly local to the + file. NOTE: This does not preclude compiler options that modify the way the + code in the file is exposed in textual modules by modifying the code's + representation in the textual module directly. + +## Source compatibility + +The addition of the `compilerSetting` keyword does not impact parsing of other +constructs since it can only be parsed as part of top level code in a position +where one must have a keyword preventing any ambiguity. Adding the enum +`CompilerSetting` cannot cause a name conflict since the name shadowing rules +added to the compiler for `Result` will also guarantee that any user defined +`CompilerSetting` will take precedence over the `CompilerSetting` type. In such +a situation, the user can spell `CompilerSetting` as `Swift.CompilerSetting` as +needed. All of the nested types within `CompilerSetting` cannot cause any source +compatibility issues since they are namespaced within `CompilerSetting`. + +## ABI compatibility + +This proposal does not inherently break ABI. But it can break ABI if a command +line option is enabled that causes the generated code's ABI to change. + +## Implications on adoption + +Adopters of this proposal should be aware that while this feature does not +inherently break ABI, enabling options using `compilerSettings` that break ABI +can result in ABI breakage. + +## Future directions + +Even though we purposely chose not to reuse `SwiftSetting` from `SwiftPM` (see +Alternatives Considered), we could add a new static method onto `SwiftSetting` +called `SwiftSetting.defineCompilerSetting` or provide an overload for +`SwiftSetting.define` that takes a `CompilerSetting` enum and uses it to pass +flags onto a specific target. This would allow for SwiftPM users to take +advantage of the discoverability provided by using enums in comparison with the +stringly typed APIs that `SwiftSetting` uses today to enable experimental and +upcoming features. This generalized API also would ensure that `SwiftSetting` +does not need to be updated to support more kinds of command line options since +the compiler would be able to "inject" support for such options into +`SwiftPM`. This would most likely require out sub-enum cases to be String enums +so that SwiftPM can just use their string representation. + +## Alternatives considered + +Instead of using `compilerSettings` and `CompilerSetting`, we could instead use +`swiftSettings` and `SwiftSetting` respectively. The reason why the authors +avoided this is that `swiftSetting` and `SwiftSetting` are currently used by +swiftpm to specify build settings resulting in potential user confusion since +`SwiftSetting` uses stringly typed APIs instead of the more discoverable enum +based APIs above. If instead one attempted to move `SwiftSetting` into the +standard library, one would have to expose many parts of SwiftPM's internal +types into the standard library such as `BuildSettingData`, +`BuildSettingCondition`, `Platform`, `BuildConfiguration`, and more. + +## Acknowledgments + +TODO From 3029caa010ee238d8396c1a5014a21b865c5ecb0 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Tue, 4 Mar 2025 08:44:06 -0800 Subject: [PATCH 2/3] Update proposal for #SwiftSettings. --- proposals/0000-compiler-settings.md | 283 ------------------- proposals/0000-swift-settings.md | 421 ++++++++++++++++++++++++++++ 2 files changed, 421 insertions(+), 283 deletions(-) delete mode 100644 proposals/0000-compiler-settings.md create mode 100644 proposals/0000-swift-settings.md diff --git a/proposals/0000-compiler-settings.md b/proposals/0000-compiler-settings.md deleted file mode 100644 index 661b59b11e..0000000000 --- a/proposals/0000-compiler-settings.md +++ /dev/null @@ -1,283 +0,0 @@ -# compilerSettings: a top level statement for enabling compiler flags locally in a specific file - -* Proposal: [SE-NNNN](NNNN-filename.md) -* Authors: [Michael Gottesman](https://github.com/gottesmm) -* Review Manager: TBD -* Status: **Awaiting implementation** -* Vision: [[Prospective Vision] Improving the approachability of data-race safety](https://forums.swift.org/t/prospective-vision-improving-the-approachability-of-data-race-safety/76183) -* Review: ([pitch](https://forums.swift.org/...)) - -## Introduction - -We propose introducing a new top level statement called `compilerSettings` that -allows for compiler flags to be enabled on a single file. This can be used to -enable warningsAsError, upcoming features, and default isolation among other -flags. For example: - -```swift -compilerSettings [ - .warningAsError(.concurrency), - .strictConcurrency(.complete), - .enableUpcomingFeature(.existentialAny), - .defaultIsolation(MainActor.self), -] -``` - -## Motivation - -Today Swift contains multiple ways of changing compiler and language behavior -from the command line including: - -* Enabling experimental and upcoming features. -* Enabling warnings as errors for diagnostic groups. -* Enabling strict concurrency when compiling pre-Swift 6 code. -* Specifying the default actor isolation via [Controlling Default Actor Isolation](). -* Requiring code to use [Strict Memory Safety](). - -Since these compiler and language behaviors can only be manipulated via the -command line and the compiler compiles one module at a time, users are forced to -apply these flags one module at a time instead of one file at a time. This -results in several forms of developer friction that we outline below. - -### Unnecessary Module Splitting - -Since compiler flags can only be applied to entire modules, one cannot apply a -compiler flag to a subset of files of a module. When confronted with this, users -are forced to split the subset of files into a separate module creating an -unnecessary module split. We foresee that this will occur more often in the -future if new features like [Controlling Default Actor Isolation]() and [Strict -MemorySafety]() are accepted by Swift Evolution. Consider the following -situations where unnecessary module splitting would be forced: - -* Consider a framework that by default uses `nonisolated` isolation, but wants - to expose a set of UI elements for users of the framework. In such a case, it - is reasonable to expect the author to want to make those specific files - containing UI elements to have a default isolation of `@MainActor` instead of - nonisolated. Without this feature one must introduce an additional module to - contain the UI elements for the framework when semantically there is really - only one framework. - -* Imagine an app that has enabled `@MainActor` isolation by default since it - contains mostly UI elements but that wishes to implement helper abstractions - that interact with a database on a background task. This helper code is - naturally expressed as having a default isolation of nonisolated implying that - one must either split the database helpers into a separate module or must mark - most declarations in the database code with nonisolated by hand. - -* Visualize a banking framework that contains both UI elements and functionality - for directly manipulating the account data of a customer. In such a case the - code that actually modifies customer data may want to enable strict memory - safety to increase security. In contrast, strict memory safety is not - necessary for the UI elements that the framework defines. To support both uses - cases, a module split must be introduced. - -### Preventing per-file based migration of large modules using warningsAsErrors - -Swift has taken the position that updating modules for a new language mode or -feature should be accomplished by: - -1. Enabling warnings on the entire module by enabling upcoming features from the - command line. - -2. Updating the module incrementally until all of the warnings have been - eliminated from the module. - -3. Updating the language version of the module so from that point on warnings - will become errors to prevent backsliding. - -This creates developer friction when applied to large modules, since the size of -the module makes it difficult to eliminate all of the warnings at once. This -results in either the module being updated over time on main during which main -is left in an intermediate, partially updated state or the module being updated -on a branch that is merged once all updates have been completed. In the former -case, it is easy to introduce new warnings as new code is added to the codebase -since only warnings are being used. In the later case, the branch must be kept -up to date in the face of changes being made to the codebase on main, forcing -one to continually need to update the branch to resolve merge conflicts against -main. - -In contrast by allowing for command line flags to be applied one file at a time, -users can update code using a warningsAsErrors based migration path. This -involves instead: - -1. Enabling warningsAsErrors for the new feature on the specific file. - -2. Fixing all of the errors in the file. - -3. Commiting the updated file into main. - -Since one is only updating one file at a time and using warningsAsErrors, the -updates can be done incrementally on a per file basis, backsliding cannot occur -in the file, and most importantly main is never in an intermediate, partially -updated state. - -## Proposed solution - -We propose introducing a new top level statement called `compilerSettings` that -takes a list of enums of type `CompilerSetting`. Each enum case's associated -values represent arguments that would be passed as an argument to the compiler -flag on the command line. The compiler upon parsing a `compilerSettings` -statement updates its internal state to set the appropriate settings before -compiling the rest of the file. An example of such a `compilerSettings` -statement is the following: - -```swift -compilerSettings [ - .warningAsError(.concurrency), - .strictConcurrency(.complete), - .enableUpcomingFeature(.existentialAny), - .defaultIsolation(MainActor.self), -] -``` - -By specifying compiler options in such a manner, we are able to solve all of the -problems above: - -1. The programmer can avoid module splitting when they wish to use compiler - flags only on a specific file. - -2. The programmer can specify options on a per file basis instead of only on a - per module basis allowing for warningsAsError to be used per file instead of - per module migration to use new features. - -As an additional benefit since enums are being used, code completion can make -the compiler options discoverable to the user in the IDE. - -## Detailed design - -We will update the swift grammar to allow for a new top level statement called -`compilerSettings` that takes a list of expressions: - -```text -top-level-statement ::= 'compilerSettings' '[' (expr ',')* expr ','? ']' -``` - -Each expression passed to `compilerSettings` is an instance of the enum -`CompilerSetting` that specifies a command line option that is to be applied to -the file. `CompilerSetting` will be a resilient enum defined in the standard -library that provides the following API: - -``` -public enum CompilerSetting { - case enableUpcomingFeature(UpcomingFeatureCompilerSetting) - case enableExperimentalFeature(ExperimentalFeatureCompilerSetting) - case warningAsError(WarningAsErrorCompilerSetting) - case defaultIsolation(Actor.Type) - case strictConcurrency(StrictConcurrencyCompilerSetting) -} -``` - -We purposely make `CompilerSetting` resilient so additional kinds of compiler -settings can be added over time without breaking ABI. - -`CompilerSetting` provides an enum case for each command line option and each -case is able to provide associated values to specify arguments that would -normally be passed on the command line to the option. These associated values -can be one of: - -* an already existing type (for example `Actor.Type`). - -* a custom fixed enum defined as a subtype of `CompilerSetting` (for example - `StrictConcurrencyCompilerSetting`). - -* a custom enum defined as a subtype of `CompilerSetting` that the compiler - synthesizes cases for depending on internal compiler data (for example - features for `UpcomingFeatureCompilerSetting`). - -We purposely keep all custom types defined for `CompilerSetting` as nested types -within `CompilerSetting` in order to avoid polluting the global namespace. Thus -we would necessarily define in the stdlib all of the custom types as: - -```swift -extension CompilerSetting { - // Cases synthesized by the compiler. - public enum UpcomingFeatureCompilerSetting { } - - // Cases synthesized by the compiler. - public enum ExperimentalFeatureCompilerSetting { } - - // Cases synthesized by the compiler. - public enum WarningsAsErrorsCompilerSetting { } - - // We know ahead of time all of the cases, so this is specified explicitly. - public enum StrictConcurrency { - case minimal - case targeted - case complete - } -} -``` - -In order to ensure that compiler settings are easy to find in the file, we -require that `compilerSettings` be the first statement in the file. - -By default command line flags will not be allowed to be passed to -`compilerSettings`. Instead, we will require a compiler flag to explicitly opt -in to being supported by `compilerSettings`. We require that since: - -1. Certain compiler flags like `-enable-library-evolution` have effects that can - not be constrained to a single file since they are inherently module wide - flags. - -2. Other compiler flags whose impacts would necessitate exposing - `compilerSettings` in textual modules. We view supported `compilerSettings` - as an anti-goal since appearing in the module header makes enabling - `compilerSettings` affect the way the module is interpreted by users, while - we are designing `compilerSettings` to have an impact strictly local to the - file. NOTE: This does not preclude compiler options that modify the way the - code in the file is exposed in textual modules by modifying the code's - representation in the textual module directly. - -## Source compatibility - -The addition of the `compilerSetting` keyword does not impact parsing of other -constructs since it can only be parsed as part of top level code in a position -where one must have a keyword preventing any ambiguity. Adding the enum -`CompilerSetting` cannot cause a name conflict since the name shadowing rules -added to the compiler for `Result` will also guarantee that any user defined -`CompilerSetting` will take precedence over the `CompilerSetting` type. In such -a situation, the user can spell `CompilerSetting` as `Swift.CompilerSetting` as -needed. All of the nested types within `CompilerSetting` cannot cause any source -compatibility issues since they are namespaced within `CompilerSetting`. - -## ABI compatibility - -This proposal does not inherently break ABI. But it can break ABI if a command -line option is enabled that causes the generated code's ABI to change. - -## Implications on adoption - -Adopters of this proposal should be aware that while this feature does not -inherently break ABI, enabling options using `compilerSettings` that break ABI -can result in ABI breakage. - -## Future directions - -Even though we purposely chose not to reuse `SwiftSetting` from `SwiftPM` (see -Alternatives Considered), we could add a new static method onto `SwiftSetting` -called `SwiftSetting.defineCompilerSetting` or provide an overload for -`SwiftSetting.define` that takes a `CompilerSetting` enum and uses it to pass -flags onto a specific target. This would allow for SwiftPM users to take -advantage of the discoverability provided by using enums in comparison with the -stringly typed APIs that `SwiftSetting` uses today to enable experimental and -upcoming features. This generalized API also would ensure that `SwiftSetting` -does not need to be updated to support more kinds of command line options since -the compiler would be able to "inject" support for such options into -`SwiftPM`. This would most likely require out sub-enum cases to be String enums -so that SwiftPM can just use their string representation. - -## Alternatives considered - -Instead of using `compilerSettings` and `CompilerSetting`, we could instead use -`swiftSettings` and `SwiftSetting` respectively. The reason why the authors -avoided this is that `swiftSetting` and `SwiftSetting` are currently used by -swiftpm to specify build settings resulting in potential user confusion since -`SwiftSetting` uses stringly typed APIs instead of the more discoverable enum -based APIs above. If instead one attempted to move `SwiftSetting` into the -standard library, one would have to expose many parts of SwiftPM's internal -types into the standard library such as `BuildSettingData`, -`BuildSettingCondition`, `Platform`, `BuildConfiguration`, and more. - -## Acknowledgments - -TODO diff --git a/proposals/0000-swift-settings.md b/proposals/0000-swift-settings.md new file mode 100644 index 0000000000..af2b9edabd --- /dev/null +++ b/proposals/0000-swift-settings.md @@ -0,0 +1,421 @@ +# #SwiftSettings: a macro for enabling compiler flags locally in a specific file + +* Proposal: [SE-NNNN](NNNN-filename.md) +* Authors: [Michael Gottesman](https://github.com/gottesmm) +* Review Manager: TBD +* Status: **Awaiting implementation** +* Vision: [[Prospective Vision] Improving the approachability of data-race safety](https://forums.swift.org/t/prospective-vision-improving-the-approachability-of-data-race-safety/76183) +* Previous Revision: [1](https://github.com/swiftlang/swift-evolution/blob/dbab768bc058c23b7d5b99827680a19443519136/proposals/0000-compiler-settings.md) +* Review: ([pitch](https://forums.swift.org/t/pitch-compilersettings-a-top-level-statement-for-enabling-compiler-flags-locally-in-a-specific-file/)) + +## Introduction + +We propose introducing a new macro called `#SwiftSettings` that allows for +compiler flags to be enabled on a single file. This can be used to enable +warningsAsError, strict concurrency, and default isolation. For example: + +```swift +#SwiftSettings( + .warningsAsErrors(.concurrency), + .strictConcurrency(.complete), + .defaultIsolation(MainActor.self) +) +``` + +## Motivation + +Today Swift contains multiple ways of changing compiler and language behavior +from the command line including: + +* Enabling warnings as errors for diagnostic groups. +* Enabling strict concurrency when compiling pre-Swift 6 code. +* Specifying the default actor isolation via [Controlling Default Actor Isolation](). + +Since these compiler and language behaviors can only be manipulated via the +command line and the compiler compiles one module at a time, users are forced to +apply these flags one module at a time instead of one file at a time. This +results in several forms of developer friction that we outline below. + +### Unnecessary Module Splitting + +Since compiler flags can only be applied to entire modules, one cannot apply a +compiler flag to a subset of files of a module. When confronted with this, users +are forced to split the subset of files into a separate module creating an +unnecessary module split. We foresee that this will occur more often in the +future if new features like [Controlling Default Actor Isolation]() are accepted +by Swift Evolution. Consider the following situations where unnecessary module +splitting would be forced: + +* Consider a framework that by default uses `nonisolated` isolation, but wants + to expose a set of UI elements for users of the framework. In such a case, it + is reasonable to expect the author to want to make those specific files + containing UI elements to have a default isolation of `@MainActor` instead of + nonisolated. Without this feature one must introduce an additional module to + contain the UI elements for the framework when semantically there is really + only one framework. + +* Imagine an app that has enabled `@MainActor` isolation by default since it + contains mostly UI elements but that wishes to implement helper abstractions + that interact with a database on a background task. This helper code is + naturally expressed as having a default isolation of nonisolated implying that + one must either split the database helpers into a separate module or must mark + most declarations in the database code with nonisolated by hand. + +### Preventing per-file based warningsAsErrors migration of large modules to strict concurrency + +Swift has taken the position that updating modules for strict concurrency should +be accomplished by: + +1. Enabling strict concurrency on the entire module causing the compiler to emit + warnings. + +2. Updating the module incrementally until all of the warnings have been + eliminated from the module. + +3. Updating the language version of the module to swift 6 so from that point on + warnings will become errors to prevent backsliding. + +This creates developer friction when applied to large modules, since the size of +the module makes it difficult to eliminate all of the warnings at once. This +results in either the module being updated over time on main during which main +is left in an intermediate, partially updated state or the module being updated +on a branch that is merged once all updates have been completed. In the former +case, it is easy to introduce new warnings as new code is added to the codebase +since only warnings are being used. In the later case, the branch must be kept +up to date in the face of changes being made to the codebase on main, forcing +one to continually need to update the branch to resolve merge conflicts against +main. + +In contrast by allowing for strict concurrency to be applied one file at a time, +users can update code using a warningsAsErrors based migration path. This +involves instead: + +1. Enabling strict concurrency and warningsAsErrors for strict concurrency on + the specific file. + +2. Fixing all of the errors in the file. + +3. Commiting the updated file into main. + +Since one is only updating one file at a time and using warningsAsErrors, the +updates can be done incrementally on a per file basis, backsliding cannot occur +in the file, and most importantly main is never in an intermediate, partially +updated state. We think that this approach will make it significantly easier for +larger modules to be migrated to strict concurrency. + +## Proposed solution + +We propose introducing a new macro called `#SwiftSettings` that takes a list of +structs of type `SwiftSetting`. `SwiftSetting` will possess a static method for +each command line argument that it supports. Any parameters that would be passed +on the command line are passed as parameters to the static method. The compiler +upon parsing a `#SwiftSettings` macro updates its internal state to set the +appropriate settings before compiling the rest of the file. An example of such a +`#SwiftSettings` invocation is the following: + +```swift +#SwiftSettings( + .warningsAsErrors(.concurrency), + .strictConcurrency(.complete), + .defaultIsolation(MainActor.self) +) +``` + +By specifying compiler options in such a manner, we are able to solve all of the +problems above: + +1. The programmer can avoid module splitting when they wish to use compiler + flags only on a specific file. + +2. The programmer can specify strict concurrency on a per file basis instead of + only on a per module basis allowing for warningsAsError to be used per file + instead of per module migration to use new features. + +As an additional benefit since strongly typed methods are being used instead of +providing a single method that takes a string to specify the command line +parameter code completion can make the compiler options discoverable to the user +in the IDE. + +NOTE: By default, only a few specified command line options will be +supported. In the future, this can be loosened as appropriate (see future +directions). + +## Detailed design + +We will introduce into the standard library a new declaration macro called +`#SwiftSettings` and a new struct called `SwiftSetting` that `#SwiftSettings` +takes as a parameter: + +```swift +@freestanding(declaration) +public macro SwiftSettings(_ settings: SwiftSetting...) = Builtin.SwiftSettingsMacro + +public struct SwiftSetting { + public init() { fatalError("Cannot construct an instance of SwiftSetting") } +} +``` + +`#SwiftSettings` will expand to an empty string and is used only for syntactic +and documentation in the IDE. + +`SwiftSetting` is a resilient struct that cannot be constructed without a +runtime error and is only used for the purposes of specifying command line +arguments to a `#SwiftSettings` macro. Support for individual command line +options is added to `SwiftSetting` by defining a static method on `SwiftSetting` +in an extension. By using static methods and extensions in this manner, we are +able to make `SwiftSetting` extensible without requiring the standard library to +be updated whenever a new options is added since the extension can be defined in +other modules. As an example, we add support for `defaultIsolation` via an +extension in the Concurrency module as follows: + +```swift +extension SwiftSetting { + public static func defaultIsolation(_ isolation: Actor.Type?) -> SwiftSetting { SwiftSetting() } +} +``` + +Since this extension is in the `Concurrency` module, we are able to use +`Actor.Type?` to specify the default isolation to be used which would not be +possible if defaultIsolation was defined in the standard library since `Actor` +is defined in Concurrency, + +Each static method on `SwiftSetting` is expecting to return a `SwiftSetting` +struct. The actual value returned is not important since this code will not be +executed. + +By default, `#SwiftSetting` will only support an explicit group of command line +flags. These are: + +* strict-concurrency +* warnings-as-errors +* default isolation. + +These options are compatible with `#SwiftSettings` since they possess the +following three characteristics: + +* Impact parsing + +* Emit additional warnings and errors + +* Modify the program in a manner that can be reflected in a textual interface + without the need for `#SwiftSettings` to be serialized into a textual + interface file. + +* Are not options that cause module wide effects that cannot be constrained to a + single file. An example of such a setting is `enable-library-evolution`. + +In the future, more options can be added as appropriate (see future +directions). In order to ensure that any such options also obey the above +restrictions, we expect that any feature that wants to be supported by +`#SwiftSettings` must as part of their swift-evolution process consider the ABI +and source stability impacts of adding support to `#SwiftSettings`. At minimum, +the feature must obey the above requirements. + +If a command line option is set to different values at the module and file +level, the setting at the file level will take precedence. + +Beyond adding support for `defaultIsolation` in Concurrency, we will also add +support for strict concurrency and warningsAsErrors. + +Support for strict concurrency will be added by defining an extension of +`SwiftSetting` in `Concurrency` that contains an enum that specifies the cases +that strict concurrency can take and a static method called `strictConcurrency` +that takes that enum: + +```swift +extension SwiftSetting { + public enum StrictConcurrencySetting { + case Minimal + case Targeted + case Complete + } + + public static func strictConcurrency(_ setting: StrictConcurrencySetting) -> SwiftSetting { ... } +} +``` + +Support for warningsAsErrors will be added by defining an extension of +`SwiftSetting` in stdlibCore that defines a struct called DiagnosticGroup and a +static function called `warningsAsErrors`: + +```swift +extension SwiftSetting { + public struct DiagnosticGroup { + public init() { fatalError("Cannot construct a DiagnosticGroup") } + } + + public static func warningsAsErrors(_ diagnosticGroup: DiagnosticGroup) -> SwiftSetting { ... } +} +``` + +`DiagnosticGroup` is similar to `SwiftSetting` in that it is a resilient struct +that cannot be constructed without a runtime error and provides static +properties for each of the DiagnosticGroup cases that are supported by it: + +```swift +// In StdlibCore +extension SwiftSetting.DiagnosticGroup { + static var deprecatedDeclarations: SwiftSetting.DiagnosticGroup { ... } +} + +// In Concurrency +extension SwiftSetting.DiagnosticGroup { + static var concurrency: SwiftSetting.DiagnosticGroup { ... } + static var preconcurrencyImport: SwiftSetting.DiagnosticGroup { ... } +} +``` + +Thus one can specify that concurrency warnings should be errors by writing: + +```swift +#SwiftSettings( + .warningsAsErrors(.concurrency) +) +``` + +## Source compatibility + +The addition of the `#SwiftSettings` and `SwiftSetting` cannot impact the +parsing of other constructs since the name shadowing rules in the compiler will +ensure that any local macros or types that shadow those names will take +precedence. If the user wishes to still use `#SwiftSettings`, the user can spell +the macro as `#Swift.SwiftSettings` as needed. + +Importantly this means that `#SwiftSettings` if used in the swiftpm code base or +in `Package.swift` files would always need to be namespaced. The authors think +that this is an acceptable trade-off since: + +* The `swiftpm` codebase is one project out of many and can just namespace as + appropriate. +* `Package.swift` files really shouldn't need `#SwiftSettings` since they are + very self contained APIs. + +## ABI compatibility + +This proposal does not inherently break ABI. But it can break ABI if a command +line option is enabled that causes the generated code's ABI to change. As part +of enabling an option, one must consider such implications. + +## Implications on adoption + +Adopters of this proposal should be aware that while this feature does not +inherently break ABI, enabling options using `#SwiftSettings` that break ABI can +result in ABI breakage. + +## Future directions + +### Adding support for Upcoming Features + +We could add support for Upcoming Features to `#SwiftSettings`. By default all +upcoming features would _NOT_ be supported by `#SwiftSettings`. Instead, +Upcoming Features would need to opt-in specifically to `#SwiftSettings` +support. In order to opt-in, the upcoming feature would need to specify in its +swift-evolution proposal any ABI, API, or source stability issues arising from +having `#SwiftSettings` support. The authors expect that if we were to support +this, we would introduce a enum based API: + +```swift +extension SwiftSetting { + public enum UpcomingFeature { ... } + + public static func enableUpcomingFeature(_ feature: UpcomingFeature) -> SwiftSetting { ... } +} +``` + +and would either require supported upcoming features to be explicitly written +into the `UpcomingFeature` enum or use a compiler code generation approach. If +the compiler code generation approach was used, we would populate +`UpcomingFeature` with all known cases from the compiler dynamically at compile +time and use availability to disable the cases associated with features that do +not support `SwiftSettings`. The nice thing about the latter approach is that +all features can be found in the IDE. + +### Adding new APIs to SwiftPM that take SwiftSetting + +Even though we purposely chose not to reuse `SwiftSetting` from `SwiftPM` (see +Alternatives Considered), we could add a new overload to `SwiftSetting.define` +that takes a `Swift.SwiftSetting` struct and uses it to pass flags onto a +specific target. This would allow for SwiftPM users to take advantage of the +discoverability provided by specifying compiler flags using strongly typed APIs +instead of the stringly typed APIs that `SwiftSetting` uses today. This +generalized API also would ensure that `SwiftSetting` does not need to be +updated to support more kinds of command line options since the compiler would +be able to "inject" support for such options into `SwiftPM`. This would require +us to allow for `SwiftSettings` structs to be constructed and provide state +within them that `SwiftPM` can process to determine the relevant behavior. Since +`SwiftSetting` is a resilient struct, we can in the future add this support +without breaking ABI or API. + +NOTE: Since the `SwiftPM` API would specify in its signature that it takes a +`Swift.SwiftSetting`, the user will still be able to take advantage of the type +checker to avoid needing to prefix any calls to static methods on +`Swift.SwiftSetting`. + +## Alternatives considered + +### Using a bespoke syntax instead of a macro + +Instead of using a macro, we considered using a bespoke syntax called +`compilerSettings` that looked as follows: + +```swift +compilerSettings [ + .warningsAsErrors(.concurrency), + .strictConcurrency(.complete), + .defaultIsolation(MainActor.self) +] +``` + +We decided not to go with this approach since: + +* We would be introducing an additional unneeded bespoke syntax to the language. +* Macros are already recognized by users as a manner to change compiler behavior + at compile time. + +### Reusing `SwiftSetting` from SwiftPM + +Instead of using our own `SwiftSetting` struct, we could reuse parts of +`SwiftPM`. The authors upon investigating this approach noticed that If one +attempted to move SwiftPM's `SwiftSetting` into the standard library, one would +have to expose many parts of SwiftPM's internal types into the standard library +such as `BuildSettingData`, `BuildSettingCondition`, `Platform`, +`BuildConfiguration`, and more. + +### Using a "fake" syntatic macro instead of a real macro + +Instead of using a real macro, we could use a bespoke "fake" macro. This would +involve the compiler providing custom parsing for `#SwiftSettings`. The compiler +would then explicitly pattern match against the syntax for the various command +line options so the struct `SwiftSetting` would not be required. + +The reason why we decided not to go with this approach is that: + +1. It prevents the ability to perform lookup at point in the IDE and introduces + more bespoke code into the compiler to support this feature. +2. It would prevent the introduction of new SwiftPM APIs in the future that take + a `Swift.SwiftSetting`. + +### "import feature" syntax + +We could also use an "import" syntax to enable features like Python and +Scala. We decided to not follow this approach since `#` is a manner in swift of +saying "compile time". + +### Typealias syntax + +We could also use a typealiased based syntax. But this would only be able to be +used to specify the current global actor and would not let us add additional +features such as controlling strict concurrency or warnings as errors. + +### Adding the ability to push/pop compiler settings + +We do not think that it makes sense to implement the ability to push/pop +compiler settings since we are attempting to specifically support compiler +settings that semantically can have file wide implications. If we wanted to +support push/pop of compiler settings it would involve different trade-offs and +restrictions on what settings would be able to be used since the restrictions +would necessarily need to be greater. + +## Acknowledgments + +TODO From cf7975ea26b06efb6cb5b62de4a1d10b6858b7fc Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Wed, 12 Mar 2025 21:28:35 -0700 Subject: [PATCH 3/3] Update for #SwiftSettings --- proposals/0000-swift-settings.md | 297 +++++++++++++++++-------------- 1 file changed, 161 insertions(+), 136 deletions(-) diff --git a/proposals/0000-swift-settings.md b/proposals/0000-swift-settings.md index af2b9edabd..5873d607e9 100644 --- a/proposals/0000-swift-settings.md +++ b/proposals/0000-swift-settings.md @@ -3,48 +3,40 @@ * Proposal: [SE-NNNN](NNNN-filename.md) * Authors: [Michael Gottesman](https://github.com/gottesmm) * Review Manager: TBD -* Status: **Awaiting implementation** +* Status: Implemented on main behind flag -enable-experimental-feature SwiftSettings * Vision: [[Prospective Vision] Improving the approachability of data-race safety](https://forums.swift.org/t/prospective-vision-improving-the-approachability-of-data-race-safety/76183) -* Previous Revision: [1](https://github.com/swiftlang/swift-evolution/blob/dbab768bc058c23b7d5b99827680a19443519136/proposals/0000-compiler-settings.md) +* Previous Revision: [1](https://github.com/swiftlang/swift-evolution/blob/dbab768bc058c23b7d5b99827680a19443519136/proposals/0000-compiler-settings.md) [2](https://github.com/swiftlang/swift-evolution/blob/3029caa010ee238d8396c1a5014a21b865c5ecb0/proposals/0000-swift-settings.md) * Review: ([pitch](https://forums.swift.org/t/pitch-compilersettings-a-top-level-statement-for-enabling-compiler-flags-locally-in-a-specific-file/)) ## Introduction We propose introducing a new macro called `#SwiftSettings` that allows for -compiler flags to be enabled on a single file. This can be used to enable -warningsAsError, strict concurrency, and default isolation. For example: +compiler flags to be enabled on a single file. Initially this will only be used +to support controlling the default isolation. For example: ```swift #SwiftSettings( - .warningsAsErrors(.concurrency), - .strictConcurrency(.complete), .defaultIsolation(MainActor.self) ) ``` +In the future, we wish to extend this to also include support for controlling +`warningsAsErrors`, `strictConcurrency`, and other flags. See future directions +for more details. + ## Motivation Today Swift contains multiple ways of changing compiler and language behavior -from the command line including: - -* Enabling warnings as errors for diagnostic groups. -* Enabling strict concurrency when compiling pre-Swift 6 code. -* Specifying the default actor isolation via [Controlling Default Actor Isolation](). - -Since these compiler and language behaviors can only be manipulated via the -command line and the compiler compiles one module at a time, users are forced to -apply these flags one module at a time instead of one file at a time. This -results in several forms of developer friction that we outline below. - -### Unnecessary Module Splitting - -Since compiler flags can only be applied to entire modules, one cannot apply a -compiler flag to a subset of files of a module. When confronted with this, users -are forced to split the subset of files into a separate module creating an -unnecessary module split. We foresee that this will occur more often in the -future if new features like [Controlling Default Actor Isolation]() are accepted -by Swift Evolution. Consider the following situations where unnecessary module -splitting would be forced: +from the command line including specifying the default actor isolation via +[Controlling Default Actor Isolation](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0466-control-default-actor-isolation.md). Since +these compiler and language behaviors can only be manipulated via the command +line and the compiler compiles one module at a time, users are forced to apply +these flags one module at a time instead of one file at a time. When confronted +with this, users are forced to split the subset of files into a separate module +creating an unnecessary module split. We foresee that this will occur more often +in the future if new features like [Controlling Default Actor Isolation](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0466-control-default-actor-isolation.md) +are accepted by Swift Evolution. Consider the following situations where +unnecessary module splitting would be forced: * Consider a framework that by default uses `nonisolated` isolation, but wants to expose a set of UI elements for users of the framework. In such a case, it @@ -61,48 +53,6 @@ splitting would be forced: one must either split the database helpers into a separate module or must mark most declarations in the database code with nonisolated by hand. -### Preventing per-file based warningsAsErrors migration of large modules to strict concurrency - -Swift has taken the position that updating modules for strict concurrency should -be accomplished by: - -1. Enabling strict concurrency on the entire module causing the compiler to emit - warnings. - -2. Updating the module incrementally until all of the warnings have been - eliminated from the module. - -3. Updating the language version of the module to swift 6 so from that point on - warnings will become errors to prevent backsliding. - -This creates developer friction when applied to large modules, since the size of -the module makes it difficult to eliminate all of the warnings at once. This -results in either the module being updated over time on main during which main -is left in an intermediate, partially updated state or the module being updated -on a branch that is merged once all updates have been completed. In the former -case, it is easy to introduce new warnings as new code is added to the codebase -since only warnings are being used. In the later case, the branch must be kept -up to date in the face of changes being made to the codebase on main, forcing -one to continually need to update the branch to resolve merge conflicts against -main. - -In contrast by allowing for strict concurrency to be applied one file at a time, -users can update code using a warningsAsErrors based migration path. This -involves instead: - -1. Enabling strict concurrency and warningsAsErrors for strict concurrency on - the specific file. - -2. Fixing all of the errors in the file. - -3. Commiting the updated file into main. - -Since one is only updating one file at a time and using warningsAsErrors, the -updates can be done incrementally on a per file basis, backsliding cannot occur -in the file, and most importantly main is never in an intermediate, partially -updated state. We think that this approach will make it significantly easier for -larger modules to be migrated to strict concurrency. - ## Proposed solution We propose introducing a new macro called `#SwiftSettings` that takes a list of @@ -115,36 +65,27 @@ appropriate settings before compiling the rest of the file. An example of such a ```swift #SwiftSettings( - .warningsAsErrors(.concurrency), - .strictConcurrency(.complete), .defaultIsolation(MainActor.self) ) ``` -By specifying compiler options in such a manner, we are able to solve all of the -problems above: - -1. The programmer can avoid module splitting when they wish to use compiler - flags only on a specific file. - -2. The programmer can specify strict concurrency on a per file basis instead of - only on a per module basis allowing for warningsAsError to be used per file - instead of per module migration to use new features. +By specifying compiler options in such a manner, we are able to avoid the need +for module splitting if a user wishes to change the default isolation only on a +specific file. As an additional benefit since strongly typed methods are being used instead of providing a single method that takes a string to specify the command line parameter code completion can make the compiler options discoverable to the user in the IDE. -NOTE: By default, only a few specified command line options will be -supported. In the future, this can be loosened as appropriate (see future -directions). +NOTE: Initially we will only support default isolation. In the future, this can +be loosened as appropriate (see future directions). ## Detailed design We will introduce into the standard library a new declaration macro called `#SwiftSettings` and a new struct called `SwiftSetting` that `#SwiftSettings` -takes as a parameter: +takes as a variadic parameter: ```swift @freestanding(declaration) @@ -155,7 +96,7 @@ public struct SwiftSetting { } ``` -`#SwiftSettings` will expand to an empty string and is used only for syntactic +`#SwiftSettings` will expand to an empty string and is used only for its syntax and documentation in the IDE. `SwiftSetting` is a resilient struct that cannot be constructed without a @@ -183,26 +124,18 @@ Each static method on `SwiftSetting` is expecting to return a `SwiftSetting` struct. The actual value returned is not important since this code will not be executed. -By default, `#SwiftSetting` will only support an explicit group of command line -flags. These are: - -* strict-concurrency -* warnings-as-errors -* default isolation. +`#SwiftSetting` will only support an explicit opted-in group of command line +flags. This initially will only include default isolation. All such options must +possess the following characteristics to be compatible with `#SwiftSettings`: -These options are compatible with `#SwiftSettings` since they possess the -following three characteristics: +* No impact on parsing -* Impact parsing - -* Emit additional warnings and errors - -* Modify the program in a manner that can be reflected in a textual interface +* Only modify the program in a manner that can be reflected in a textual interface without the need for `#SwiftSettings` to be serialized into a textual interface file. -* Are not options that cause module wide effects that cannot be constrained to a - single file. An example of such a setting is `enable-library-evolution`. +* Does not cause module wide effects that cannot be constrained to a single + file. An example of such a setting is `enable-library-evolution`. In the future, more options can be added as appropriate (see future directions). In order to ensure that any such options also obey the above @@ -211,11 +144,97 @@ restrictions, we expect that any feature that wants to be supported by and source stability impacts of adding support to `#SwiftSettings`. At minimum, the feature must obey the above requirements. +Each argument can be passed to `#SwiftSettings` exactly once. Otherwise, an +error will be emitted: + +```swift +#SwiftSetting( + .defaultIsolation(MainActor.self), + .defaultIsolation(nil) // Error! +) + +#SwiftSetting( + .defaultIsolation(MainActor.self) // Error! +) +``` + If a command line option is set to different values at the module and file level, the setting at the file level will take precedence. -Beyond adding support for `defaultIsolation` in Concurrency, we will also add -support for strict concurrency and warningsAsErrors. +## Source compatibility + +The addition of the `#SwiftSettings` and `SwiftSetting` cannot impact the +parsing of other constructs since the name shadowing rules in the compiler will +ensure that any local macros or types that shadow those names will take +precedence. If the user wishes to still use `#SwiftSettings`, the user can spell +the macro as `#Swift.SwiftSettings` as needed. + +Importantly this means that `#SwiftSettings` if used in the swiftpm code base or +in `Package.swift` files would always need to be namespaced. The authors think +that this is an acceptable trade-off since: + +* The `swiftpm` codebase is one project out of many and can just namespace as + appropriate. +* `Package.swift` files really shouldn't need `#SwiftSettings` since they are + very self contained APIs. + +## ABI compatibility + +This proposal does not inherently break ABI. But it can break ABI if a command +line option is enabled that causes the generated code's ABI to change. As part +of enabling an option, one must consider such implications. + +## Implications on adoption + +Adopters of this proposal should be aware that while this feature does not +inherently break ABI, enabling options using `#SwiftSettings` that break ABI can +result in ABI breakage. + +## Future directions + +### Adding support for warningsAsErrors and strictConcurrency to allow for per-file warningsAsErrors based migration of large modules to strict concurrency + +Swift has taken the position that updating modules for strict concurrency should +be accomplished by: + +1. Enabling strict concurrency on the entire module causing the compiler to emit + warnings. + +2. Updating the module incrementally until all of the warnings have been + eliminated from the module. + +3. Updating the language version of the module to swift 6 so from that point on + warnings will become errors to prevent backsliding. + +This creates developer friction when applied to large modules, since the size of +the module makes it difficult to eliminate all of the warnings at once. This +results in either the module being updated over time on main during which main +is left in an intermediate, partially updated state or the module being updated +on a branch that is merged once all updates have been completed. In the former +case, it is easy to introduce new warnings as new code is added to the codebase +since only warnings are being used. In the later case, the branch must be kept +up to date in the face of changes being made to the codebase on main, forcing +one to continually need to update the branch to resolve merge conflicts against +main. + +In contrast by allowing for strict concurrency to be applied one file at a time, +users can update code using a warningsAsErrors based migration path. This +involves instead: + +1. Enabling strict concurrency and warningsAsErrors for strict concurrency on + the specific file. + +2. Fixing all of the errors in the file. + +3. Commiting the updated file into main. + +Since one is only updating one file at a time and using warningsAsErrors, the +updates can be done incrementally on a per file basis, backsliding cannot occur +in the file, and most importantly main is never in an intermediate, partially +updated state. We think that this approach will make it significantly easier for +larger modules to be migrated to strict concurrency. + +#### `strictConcurrency` Support for strict concurrency will be added by defining an extension of `SwiftSetting` in `Concurrency` that contains an enum that specifies the cases @@ -234,9 +253,12 @@ extension SwiftSetting { } ``` +#### `warningsAsErrors` + Support for warningsAsErrors will be added by defining an extension of `SwiftSetting` in stdlibCore that defines a struct called DiagnosticGroup and a -static function called `warningsAsErrors`: +static function called `warningsAsErrors` that takes a variadic list of +DiagnosticGroup that should be applied: ```swift extension SwiftSetting { @@ -244,7 +266,7 @@ extension SwiftSetting { public init() { fatalError("Cannot construct a DiagnosticGroup") } } - public static func warningsAsErrors(_ diagnosticGroup: DiagnosticGroup) -> SwiftSetting { ... } + public static func warningsAsErrors(_ diagnosticGroup: DiagnosticGroup...) -> SwiftSetting { SwiftSetting() } } ``` @@ -273,37 +295,6 @@ Thus one can specify that concurrency warnings should be errors by writing: ) ``` -## Source compatibility - -The addition of the `#SwiftSettings` and `SwiftSetting` cannot impact the -parsing of other constructs since the name shadowing rules in the compiler will -ensure that any local macros or types that shadow those names will take -precedence. If the user wishes to still use `#SwiftSettings`, the user can spell -the macro as `#Swift.SwiftSettings` as needed. - -Importantly this means that `#SwiftSettings` if used in the swiftpm code base or -in `Package.swift` files would always need to be namespaced. The authors think -that this is an acceptable trade-off since: - -* The `swiftpm` codebase is one project out of many and can just namespace as - appropriate. -* `Package.swift` files really shouldn't need `#SwiftSettings` since they are - very self contained APIs. - -## ABI compatibility - -This proposal does not inherently break ABI. But it can break ABI if a command -line option is enabled that causes the generated code's ABI to change. As part -of enabling an option, one must consider such implications. - -## Implications on adoption - -Adopters of this proposal should be aware that while this feature does not -inherently break ABI, enabling options using `#SwiftSettings` that break ABI can -result in ABI breakage. - -## Future directions - ### Adding support for Upcoming Features We could add support for Upcoming Features to `#SwiftSettings`. By default all @@ -330,6 +321,19 @@ time and use availability to disable the cases associated with features that do not support `SwiftSettings`. The nice thing about the latter approach is that all features can be found in the IDE. +### Adding support for Strict Memory Safety + +We could also in the future add support to `#SwiftSettings` for Strict Memory +Safety. + +### Adding the ability to opt out of module-level flags like warningsAsErrors + +We could add support for turning off features like warningsAsErrors at the file +level. This would involve adding a new `disableWarningsAsErrors` method onto +`SwiftSetting`. This would be useful when only a few files fail warningsAsErrors +and the user wishes to only suppress warningsAsErrors in those specific files in +order to avoid needing to unnnecessarily split a module. + ### Adding new APIs to SwiftPM that take SwiftSetting Even though we purposely chose not to reuse `SwiftSetting` from `SwiftPM` (see @@ -414,8 +418,29 @@ compiler settings since we are attempting to specifically support compiler settings that semantically can have file wide implications. If we wanted to support push/pop of compiler settings it would involve different trade-offs and restrictions on what settings would be able to be used since the restrictions -would necessarily need to be greater. +would necessarily need to be greater. As an example, we could never allow for + +### Extending SwiftSetting to include parsing options + +We could make it so that parsing `#SwiftSetting` is done directly in the parser +when we parse part of the macro grammar. This would work by evaluating the +`#SwiftSetting` from its syntax and performing our pattern matching at parse +time instead of at type checker time. We would then not create an AST for +`#SwiftSetting`. This current proposal is compatible with the parsing approach +since in such a case, `#SwiftSetting` would never show up at AST time. + +### Allowing for Experimental Features to opt into `#SwiftSettings` + +We could allow for experimental features to opt into `#SwiftSettings` just like +upcoming features can. We think at this point that there are dangers to exposing +support in this manner for experimental features since experimental features +have not gone through the evolution process and through that process proven that +they obey the relevant characteristics required of options for `#SwiftSettings` +support. That being said, it may be reasonable to allow for this when the +compiler is compiled with asserts so that features can test this behavior during +development. ## Acknowledgments -TODO +I would like to thank Holly Borla, Doug Gregor, Konrad Malawski and the rest of +Swift Evolution for feedback on this proposal.