Skip to content

Commit 789ace3

Browse files
authored
[Swift migrate] Handle migration for optional features (#8711)
In addition to upcoming and experimental features, handle migration for "optional" features (the only one now being StrictMemorySafety). Read optional features from the supported-features JSON coming from the compiler and use their provided flags. While here, use the newly-introduced "categories" field from the JSON to filter the list of categories rather than assuming it's the same as the feature name.
1 parent cc4468b commit 789ace3

File tree

12 files changed

+207
-62
lines changed

12 files changed

+207
-62
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// swift-tools-version:6.2
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "InferIsolatedConformancesMigration",
7+
targets: [
8+
.target(name: "Diagnostics", path: "Sources", exclude: ["Fixed"]),
9+
]
10+
)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@MainActor
2+
class C: nonisolated Equatable {
3+
let name = "Hello"
4+
5+
nonisolated static func ==(lhs: C, rhs: C) -> Bool {
6+
lhs.name == rhs.name
7+
}
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@MainActor
2+
class C: Equatable {
3+
let name = "Hello"
4+
5+
nonisolated static func ==(lhs: C, rhs: C) -> Bool {
6+
lhs.name == rhs.name
7+
}
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// swift-tools-version:6.2
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "StrictMemorySafetyMigration",
7+
targets: [
8+
.target(name: "Diagnostics", path: "Sources", exclude: ["Fixed"]),
9+
]
10+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@unsafe func f() { }
2+
3+
func g() {
4+
unsafe f()
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@unsafe func f() { }
2+
3+
func g() {
4+
f()
5+
}

Sources/Commands/PackageCommands/AddSetting.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extension SwiftPackageCommand {
2929
case experimentalFeature
3030
case upcomingFeature
3131
case languageMode
32-
case strictMemorySafety
32+
case strictMemorySafety = "StrictMemorySafety"
3333
}
3434

3535
package static let configuration = CommandConfiguration(
@@ -150,8 +150,8 @@ extension SwiftPackageCommand {
150150
manifest: manifestSyntax
151151
)
152152
case .strictMemorySafety:
153-
guard value.isEmpty else {
154-
throw ValidationError("'strictMemorySafety' doesn't have an argument")
153+
guard value.isEmpty || value == SwiftSetting.strictMemorySafety.rawValue else {
154+
throw ValidationError("'strictMemorySafety' does not support argument '\(value)'")
155155
}
156156

157157
editResult = try AddSwiftSetting.strictMemorySafety(

Sources/Commands/PackageCommands/Migrate.swift

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ extension SwiftPackageCommand {
127127
for module in modules {
128128
let fixit = try SwiftFixIt(
129129
diagnosticFiles: module.diagnosticFiles,
130-
categories: Set(features.map(\.name)),
130+
categories: Set(features.flatMap(\.categories)),
131131
fileSystem: swiftCommandState.fileSystem
132132
)
133133
try fixit.applyFixIts()
@@ -139,7 +139,7 @@ extension SwiftPackageCommand {
139139
print("> Updating manifest.")
140140
for module in modules.map(\.module) {
141141
swiftCommandState.observabilityScope.emit(debug: "Adding feature(s) to '\(module.name)'.")
142-
self.updateManifest(
142+
try self.updateManifest(
143143
for: module.name,
144144
add: features,
145145
using: swiftCommandState
@@ -159,12 +159,7 @@ extension SwiftPackageCommand {
159159

160160
let addFeaturesToModule = { (module: ResolvedModule) in
161161
for feature in features {
162-
module.underlying.buildSettings.add(.init(values: [
163-
"-Xfrontend",
164-
"-enable-\(feature.upcoming ? "upcoming" : "experimental")-feature",
165-
"-Xfrontend",
166-
"\(feature.name):migrate",
167-
]), for: .OTHER_SWIFT_FLAGS)
162+
module.underlying.buildSettings.add(.init(values: feature.migrationFlags), for: .OTHER_SWIFT_FLAGS)
168163
}
169164
}
170165

@@ -198,16 +193,11 @@ extension SwiftPackageCommand {
198193
for target: String,
199194
add features: [SwiftCompilerFeature],
200195
using swiftCommandState: SwiftCommandState
201-
) {
196+
) throws {
202197
typealias SwiftSetting = SwiftPackageCommand.AddSetting.SwiftSetting
203198

204-
let settings: [(SwiftSetting, String)] = features.map {
205-
switch $0 {
206-
case .upcoming(name: let name, migratable: _, enabledIn: _):
207-
(.upcomingFeature, "\(name)")
208-
case .experimental(name: let name, migratable: _):
209-
(.experimentalFeature, "\(name)")
210-
}
199+
let settings: [(SwiftSetting, String)] = try features.map {
200+
(try $0.swiftSetting, $0.name)
211201
}
212202

213203
do {
@@ -218,10 +208,68 @@ extension SwiftPackageCommand {
218208
verbose: !self.globalOptions.logging.quiet
219209
)
220210
} catch {
221-
swiftCommandState.observabilityScope.emit(error: "Could not update manifest for '\(target)' (\(error)). Please enable '\(features.map(\.name).joined(separator: ", "))' features manually.")
211+
swiftCommandState.observabilityScope.emit(error: "Could not update manifest for '\(target)' (\(error)). Please enable '\(try features.map { try $0.swiftSettingDescription }.joined(separator: ", "))' features manually.")
222212
}
223213
}
224214

225215
public init() {}
226216
}
227217
}
218+
219+
fileprivate extension SwiftCompilerFeature {
220+
/// Produce the set of command-line flags to pass to the compiler to enable migration for this feature.
221+
var migrationFlags: [String] {
222+
precondition(migratable)
223+
224+
switch self {
225+
case .upcoming(name: let name, migratable: _, categories: _, enabledIn: _):
226+
return ["-Xfrontend", "-enable-upcoming-feature", "-Xfrontend", "\(name):migrate"]
227+
case .experimental(name: let name, migratable: _, categories: _):
228+
return ["-Xfrontend", "-enable-experimental-feature", "-Xfrontend", "\(name):migrate"]
229+
case .optional(name: _, migratable: _, categories: _, flagName: let flagName):
230+
let flags = flagName.split(separator: " ")
231+
var resultFlags: [String] = []
232+
for (index, flag) in flags.enumerated() {
233+
resultFlags.append("-Xfrontend")
234+
if index == flags.endIndex - 1 {
235+
resultFlags.append(String(flag) + ":migrate")
236+
} else {
237+
resultFlags.append(String(flag))
238+
}
239+
}
240+
241+
return resultFlags
242+
}
243+
}
244+
245+
/// Produce the Swift setting corresponding to this compiler feature.
246+
var swiftSetting: SwiftPackageCommand.AddSetting.SwiftSetting {
247+
get throws {
248+
switch self {
249+
case .upcoming:
250+
return .upcomingFeature
251+
case .experimental:
252+
return .experimentalFeature
253+
case .optional(name: "StrictMemorySafety", migratable: _, categories: _, flagName: _):
254+
return .strictMemorySafety
255+
case .optional(name: let name, migratable: _, categories: _, flagName: _):
256+
throw InternalError("Unsupported optional feature: \(name)")
257+
}
258+
}
259+
}
260+
261+
var swiftSettingDescription: String {
262+
get throws {
263+
switch self {
264+
case .upcoming(name: let name, migratable: _, categories: _, enabledIn: _):
265+
return #".enableUpcomingFeature("\#(name)")"#
266+
case .experimental(name: let name, migratable: _, categories: _):
267+
return #".enableExperimentalFeature("\#(name)")"#
268+
case .optional(name: "StrictMemorySafety", migratable: _, categories: _, flagName: _):
269+
return ".strictMemorySafety()"
270+
case .optional(name: let name, migratable: _, categories: _, flagName: _):
271+
throw InternalError("Unsupported optional feature: \(name)")
272+
}
273+
}
274+
}
275+
}

Sources/PackageModel/Toolchain+SupportedFeatures.swift

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,43 +13,60 @@
1313
import Basics
1414

1515
import enum TSCBasic.JSON
16+
import protocol TSCBasic.JSONMappable
1617
import TSCUtility
1718

1819
public enum SwiftCompilerFeature {
19-
case upcoming(name: String, migratable: Bool, enabledIn: SwiftLanguageVersion)
20-
case experimental(name: String, migratable: Bool)
20+
case optional(name: String, migratable: Bool, categories: [String], flagName: String)
21+
case upcoming(name: String, migratable: Bool, categories: [String], enabledIn: SwiftLanguageVersion)
22+
case experimental(name: String, migratable: Bool, categories: [String])
2123

24+
public var optional: Bool {
25+
switch self {
26+
case .optional: true
27+
case .upcoming, .experimental: false
28+
}
29+
}
2230
public var upcoming: Bool {
2331
switch self {
2432
case .upcoming: true
25-
case .experimental: false
33+
case .optional, .experimental: false
2634
}
2735
}
2836

2937
public var experimental: Bool {
3038
switch self {
31-
case .upcoming: false
39+
case .optional, .upcoming: false
3240
case .experimental: true
3341
}
3442
}
3543

3644
public var name: String {
3745
switch self {
38-
case .upcoming(name: let name, migratable: _, enabledIn: _):
39-
name
40-
case .experimental(name: let name, migratable: _):
46+
case .optional(name: let name, migratable: _, categories: _, flagName: _),
47+
.upcoming(name: let name, migratable: _, categories: _, enabledIn: _),
48+
.experimental(name: let name, migratable: _, categories: _):
4149
name
4250
}
4351
}
4452

4553
public var migratable: Bool {
4654
switch self {
47-
case .upcoming(name: _, migratable: let migratable, enabledIn: _):
48-
migratable
49-
case .experimental(name: _, migratable: let migratable):
55+
case .optional(name: _, migratable: let migratable, categories: _, flagName: _),
56+
.upcoming(name: _, migratable: let migratable, categories: _, enabledIn: _),
57+
.experimental(name: _, migratable: let migratable, categories: _):
5058
migratable
5159
}
5260
}
61+
62+
public var categories: [String] {
63+
switch self {
64+
case .optional(name: _, migratable: _, categories: let categories, flagName: _),
65+
.upcoming(name: _, migratable: _, categories: let categories, enabledIn: _),
66+
.experimental(name: _, migratable: _, categories: let categories):
67+
categories
68+
}
69+
}
5370
}
5471

5572
extension Toolchain {
@@ -85,8 +102,23 @@ extension Toolchain {
85102

86103
let features: JSON = try parsedSupportedFeatures.get("features")
87104

105+
let optional: [SwiftCompilerFeature] = try (features.get("optional") as [JSON]?)?.map { (json: JSON) in
106+
let name: String = try json.get("name")
107+
let categories: [String]? = try json.getArrayIfAvailable("categories")
108+
let migratable: Bool? = json.get("migratable")
109+
let flagName: String = try json.get("flag_name")
110+
111+
return .optional(
112+
name: name,
113+
migratable: migratable ?? false,
114+
categories: categories ?? [name],
115+
flagName: flagName
116+
)
117+
} ?? []
118+
88119
let upcoming: [SwiftCompilerFeature] = try features.getArray("upcoming").map {
89120
let name: String = try $0.get("name")
121+
let categories: [String]? = try $0.getArrayIfAvailable("categories")
90122
let migratable: Bool? = $0.get("migratable")
91123
let enabledIn: String = try $0.get("enabled_in")
92124

@@ -97,21 +129,34 @@ extension Toolchain {
97129
return .upcoming(
98130
name: name,
99131
migratable: migratable ?? false,
132+
categories: categories ?? [name],
100133
enabledIn: mode
101134
)
102135
}
103136

104137
let experimental: [SwiftCompilerFeature] = try features.getArray("experimental").map {
105138
let name: String = try $0.get("name")
139+
let categories: [String]? = try $0.getArrayIfAvailable("categories")
106140
let migratable: Bool? = $0.get("migratable")
107141

108142
return .experimental(
109143
name: name,
110-
migratable: migratable ?? false
144+
migratable: migratable ?? false,
145+
categories: categories ?? [name]
111146
)
112147
}
113148

114-
return upcoming + experimental
149+
return optional + upcoming + experimental
150+
}
151+
}
152+
}
153+
154+
fileprivate extension JSON {
155+
func getArrayIfAvailable<T: JSONMappable>(_ key: String) throws -> [T]? {
156+
do {
157+
return try get(key)
158+
} catch MapError.missingKey(key) {
159+
return nil
115160
}
116161
}
117162
}

Sources/PackageModelSyntax/AddSwiftSetting.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public enum AddSwiftSetting {
7373
manifest: SourceFileSyntax
7474
) throws -> PackageEditResult {
7575
try self.addToTarget(
76-
target, name: "strictMemorySafety",
76+
target, name: "strictMemorySafety()",
7777
value: String?.none,
7878
firstIntroduced: .v6_2,
7979
manifest: manifest

0 commit comments

Comments
 (0)