Thanks everyone for the healthy discussion and all the feedback so far. Many people have raised their concerns about the migration story for this new language feature. Especially in setups that are not the semantically versioned package world. We agree that it is important that this new feature can be easily adopted in every setup and doesn't result in a lot of churn. I just pushed a new commit that includes our proposed migration paths. I would love to hear from everyone if the proposed changes would solve their concerns around migrating code to this new feature. Below is a copy of the new migration paths section from the proposal.
Migration paths
The following section is outlining the migration paths and tools we propose to provide for different kinds of projects to adopt the proposed feature. The goal is to reduce churn across the ecosystem while still allowing us to align the default behavior of enums. There are many scenarios why these migration paths must exist such as:
- Projects split up into multiple packages
- Projects build with other tools than Swift PM
- Projects explicitly vendoring packages without wanting to modify the original
source
- Projects that prefer to deal with source breaks as they come up rather than
writing source-stable code
Semantically versioned packages
Semantically versioned packages are the primary reason for this proposal. The
expected migration path for packages when adopting the proposed feature is one
of the two:
- API stable adoption by turning on the feature and marking all existing public
enums with @frozen
- API breaking adoption by turning on the feature and tagging a new major if the public API contains enums
Projects with multiple non-semantically versioned packages
A common project setup is splitting the code base into multiple packages that
are not semantically versioned. This can either be done by using local packages or by using revision locked dependencies. The packages in such a setup are often considered part of the same logical collection of code and would like to follow the same source stability rules as same module or same package code. We propose to extend the package manifest to allow overriding the package name used by a target.
extension SwiftSetting {
/// Defines the package name used by the target.
///
/// This setting is passed as the `-package-name` flag
/// to the compiler. It allows overriding the package name on a
/// per target basis. The default package name is the package identity.
///
/// - Important: Package names should only be aligned across co-developed and
/// co-released packages.
///
/// - Parameters:
/// - name: The package name to use.
/// - condition: A condition that restricts the application of the build
/// setting.
public static func packageName(_ name: String, _ condition: PackageDescription.BuildSettingCondition? = nil) -> PackageDescription.SwiftSetting
}
This allows to construct arbitrary package domains across multiple targets
inside a single package or across multiple packages. When adopting the
ExtensibleEnums
feature across multiple packages the new Swift setting can be used to continue allowing exhaustive matching. While this setting allows treating multiple targets as part of the same package. This setting should only be used across packages when the packages are both co-developed and co-released.
Other build systems
Swift PM isn't the only system used to create and build Swift projects. Build
systems and IDEs such as Bazel or Xcode offer support for Swift projects as
well. When using such tools it is common to split a project into multiple
targets/modules. Since those targets/modules are by default not considered to be part of the package, when adopting the ExtensibleEnums
feature it would
require to either add an @unknown default
when switching over enums defined in other targets/modules or marking all public enums as @frozen
. Similarly, to the above to avoid this churn we recommend specifying the -package-name
flag to the compiler for all targets/modules that should be considered as part of the
same unit.
Escape hatch
There might still be cases where developers need to consume a module that is
outside of their control which adopts the ExtensibleEnums
feature. For such
cases we propose to introduce a new flag --assume-source-stable-package
that allows assuming modules of a package as source stable. When checking if a switch needs to be exhaustive we will check if the code is either in the same module, the same package, or if the defining package is assumed to be source stable. This flag can be passed multiple times to define a set of assumed-source-stable packages.
// a.swift inside Package A
public enum MyEnum {
case foo
case bar
}
// b.swift inside Package B compiled with `--assume-source-stable-package A`
switch myEnum { // No @unknown default case needed
case .foo:
print("foo")
case .bar:
print("bar")
}
In general, we recommend to avoid using this flag but it provides an important
escape hatch to the ecosystem.