Skip to content

Commit 5de21b3

Browse files
authored
Merge pull request #2810 from swiftlang/jed/target-os
Add a warning for TARGET_OS_* compilation conditions
2 parents 6436c51 + 562d721 commit 5de21b3

File tree

3 files changed

+138
-1
lines changed

3 files changed

+138
-1
lines changed

Sources/SwiftIfConfig/IfConfigDiagnostic.swift

+24-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ enum IfConfigDiagnostic: Error, CustomStringConvertible {
3434
case ignoredTrailingComponents(version: VersionTuple, syntax: ExprSyntax)
3535
case integerLiteralCondition(syntax: ExprSyntax, replacement: Bool)
3636
case likelySimulatorPlatform(syntax: ExprSyntax)
37+
case likelyTargetOS(syntax: ExprSyntax, replacement: ExprSyntax?)
3738
case endiannessDoesNotMatch(syntax: ExprSyntax, argument: String)
3839
case macabiIsMacCatalyst(syntax: ExprSyntax)
3940
case expectedModuleName(syntax: ExprSyntax)
@@ -89,6 +90,12 @@ enum IfConfigDiagnostic: Error, CustomStringConvertible {
8990
return
9091
"platform condition appears to be testing for simulator environment; use 'targetEnvironment(simulator)' instead"
9192

93+
case .likelyTargetOS(syntax: _, replacement: let replacement?):
94+
return "'TARGET_OS_*' preprocessor macros are not available in Swift; use '\(replacement)' instead"
95+
96+
case .likelyTargetOS(syntax: _, replacement: nil):
97+
return "'TARGET_OS_*' preprocessor macros are not available in Swift; use 'os(...)' conditionals instead"
98+
9299
case .macabiIsMacCatalyst:
93100
return "'macabi' has been renamed to 'macCatalyst'"
94101

@@ -127,6 +134,7 @@ enum IfConfigDiagnostic: Error, CustomStringConvertible {
127134
.ignoredTrailingComponents(version: _, syntax: let syntax),
128135
.integerLiteralCondition(syntax: let syntax, replacement: _),
129136
.likelySimulatorPlatform(syntax: let syntax),
137+
.likelyTargetOS(syntax: let syntax, replacement: _),
130138
.endiannessDoesNotMatch(syntax: let syntax, argument: _),
131139
.macabiIsMacCatalyst(syntax: let syntax),
132140
.expectedModuleName(syntax: let syntax),
@@ -151,7 +159,7 @@ extension IfConfigDiagnostic: DiagnosticMessage {
151159
var severity: SwiftDiagnostics.DiagnosticSeverity {
152160
switch self {
153161
case .compilerVersionSecondComponentNotWildcard, .ignoredTrailingComponents,
154-
.likelySimulatorPlatform, .endiannessDoesNotMatch, .macabiIsMacCatalyst:
162+
.likelySimulatorPlatform, .likelyTargetOS, .endiannessDoesNotMatch, .macabiIsMacCatalyst:
155163
return .warning
156164
default: return .error
157165
}
@@ -198,6 +206,21 @@ extension IfConfigDiagnostic: DiagnosticMessage {
198206
)
199207
}
200208

209+
// For the likely TARGET_OS_* condition we may have a Fix-It.
210+
if case .likelyTargetOS(let syntax, let replacement?) = self {
211+
return Diagnostic(
212+
node: syntax,
213+
message: self,
214+
fixIt: .replace(
215+
message: SimpleFixItMessage(
216+
message: "replace with '\(replacement)'"
217+
),
218+
oldNode: syntax,
219+
newNode: replacement
220+
)
221+
)
222+
}
223+
201224
// For the targetEnvironment(macabi) -> macCatalyst rename we have a Fix-It.
202225
if case .macabiIsMacCatalyst(syntax: let syntax) = self {
203226
return Diagnostic(

Sources/SwiftIfConfig/IfConfigEvaluation.swift

+48
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ func evaluateIfConfig(
9999
if let identExpr = condition.as(DeclReferenceExprSyntax.self),
100100
let ident = identExpr.simpleIdentifier?.name
101101
{
102+
if let targetOSDiagnostic = diagnoseLikelyTargetOSTest(at: identExpr, name: ident) {
103+
extraDiagnostics.append(targetOSDiagnostic)
104+
}
102105
// Evaluate the custom condition. If the build configuration cannot answer this query, fail.
103106
return checkConfiguration(at: identExpr) {
104107
(active: try configuration.isCustomConditionSet(name: ident), syntaxErrorsAllowed: false)
@@ -640,6 +643,51 @@ private func diagnoseLikelySimulatorEnvironmentTest(
640643
return IfConfigDiagnostic.likelySimulatorPlatform(syntax: ExprSyntax(binOp)).asDiagnostic
641644
}
642645

646+
/// If this identifier looks like it is a `TARGET_OS_*` compilation condition,
647+
/// produce a diagnostic that suggests replacing it with the `os(*)` syntax.
648+
///
649+
/// For example, this checks for conditions like:
650+
///
651+
/// ```
652+
/// #if TARGET_OS_IOS
653+
/// ```
654+
///
655+
/// which should be replaced with
656+
///
657+
/// ```
658+
/// #if os(iOS)
659+
/// ```
660+
private func diagnoseLikelyTargetOSTest(
661+
at reference: DeclReferenceExprSyntax,
662+
name: String
663+
) -> Diagnostic? {
664+
let prefix = "TARGET_OS_"
665+
guard name.hasPrefix(prefix) else { return nil }
666+
let osName = String(name.dropFirst(prefix.count))
667+
668+
if unmappedTargetOSNames.contains(osName) {
669+
return IfConfigDiagnostic.likelyTargetOS(syntax: ExprSyntax(reference), replacement: nil).asDiagnostic
670+
}
671+
672+
guard let replacement = targetOSNameMap[osName] else { return nil }
673+
674+
return IfConfigDiagnostic.likelyTargetOS(syntax: ExprSyntax(reference), replacement: replacement).asDiagnostic
675+
}
676+
677+
// TARGET_OS_* macros that don’t have a direct Swift equivalent
678+
private let unmappedTargetOSNames = ["WIN32", "UNIX", "MAC", "IPHONE", "EMBEDDED"]
679+
private let targetOSNameMap: [String: ExprSyntax] = [
680+
"WINDOWS": "os(Windows)",
681+
"LINUX": "os(Linux)",
682+
"OSX": "os(macOS)",
683+
"IOS": "os(iOS)",
684+
"MACCATALYST": "targetEnvironment(macCatalyst)",
685+
"TV": "os(tvOS)",
686+
"WATCH": "os(watchOS)",
687+
"VISION": "os(visionOS)",
688+
"SIMULATOR": "targetEnvironment(simulator)",
689+
]
690+
643691
extension IfConfigClauseSyntax {
644692
/// Fold the operators within an #if condition, turning sequence expressions
645693
/// involving the various allowed operators (&&, ||, !) into well-structured

Tests/SwiftIfConfigTest/EvaluateTests.swift

+66
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,72 @@ public class EvaluateTests: XCTestCase {
239239
)
240240
}
241241

242+
func testTargetOS() throws {
243+
assertIfConfig("TARGET_OS_DISHWASHER", .inactive)
244+
245+
assertIfConfig(
246+
"TARGET_OS_IOS",
247+
.inactive,
248+
diagnostics: [
249+
DiagnosticSpec(
250+
message: "'TARGET_OS_*' preprocessor macros are not available in Swift; use 'os(iOS)' instead",
251+
line: 1,
252+
column: 1,
253+
severity: .warning,
254+
fixIts: [
255+
FixItSpec(message: "replace with 'os(iOS)'")
256+
]
257+
)
258+
]
259+
)
260+
261+
assertIfConfig(
262+
"TARGET_OS_OSX",
263+
.inactive,
264+
diagnostics: [
265+
DiagnosticSpec(
266+
message: "'TARGET_OS_*' preprocessor macros are not available in Swift; use 'os(macOS)' instead",
267+
line: 1,
268+
column: 1,
269+
severity: .warning,
270+
fixIts: [
271+
FixItSpec(message: "replace with 'os(macOS)'")
272+
]
273+
)
274+
]
275+
)
276+
277+
assertIfConfig(
278+
"TARGET_OS_MACCATALYST",
279+
.inactive,
280+
diagnostics: [
281+
DiagnosticSpec(
282+
message:
283+
"'TARGET_OS_*' preprocessor macros are not available in Swift; use 'targetEnvironment(macCatalyst)' instead",
284+
line: 1,
285+
column: 1,
286+
severity: .warning,
287+
fixIts: [
288+
FixItSpec(message: "replace with 'targetEnvironment(macCatalyst)'")
289+
]
290+
)
291+
]
292+
)
293+
294+
assertIfConfig(
295+
"TARGET_OS_WIN32",
296+
.inactive,
297+
diagnostics: [
298+
DiagnosticSpec(
299+
message: "'TARGET_OS_*' preprocessor macros are not available in Swift; use 'os(...)' conditionals instead",
300+
line: 1,
301+
column: 1,
302+
severity: .warning
303+
)
304+
]
305+
)
306+
}
307+
242308
func testVersions() throws {
243309
assertIfConfig("swift(>=5.5)", .active)
244310
assertIfConfig("swift(<6)", .active)

0 commit comments

Comments
 (0)