1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import SwiftDiagnostics
import SwiftSyntax
import SwiftSyntaxBuilder
/// Describes the kinds of diagnostics that can occur when processing #if
/// conditions. This is an Error-conforming type so we can throw errors when
/// needed, but the cases themselves are a mix of warnings and errors when
/// rendered as a diagnostic.
enum IfConfigDiagnostic: Error, CustomStringConvertible {
case unknownExpression(ExprSyntax)
case unhandledFunction(name: String, syntax: ExprSyntax)
case requiresUnlabeledArgument(name: String, role: String, syntax: ExprSyntax)
case unsupportedVersionOperator(name: String, operator: TokenSyntax)
case invalidVersionOperand(name: String, syntax: ExprSyntax)
case emptyVersionComponent(syntax: ExprSyntax)
case compilerVersionOutOfRange(value: Int, upperLimit: Int, syntax: ExprSyntax)
case compilerVersionSecondComponentNotWildcard(syntax: ExprSyntax)
case compilerVersionTooManyComponents(syntax: ExprSyntax)
case canImportMissingModule(syntax: ExprSyntax)
case canImportLabel(syntax: ExprSyntax)
case canImportTwoParameters(syntax: ExprSyntax)
case ignoredTrailingComponents(version: VersionTuple, syntax: ExprSyntax)
case integerLiteralCondition(syntax: ExprSyntax, replacement: Bool)
case likelySimulatorPlatform(syntax: ExprSyntax)
case likelyTargetOS(syntax: ExprSyntax, replacement: ExprSyntax?)
case endiannessDoesNotMatch(syntax: ExprSyntax, argument: String)
case macabiIsMacCatalyst(syntax: ExprSyntax)
case expectedModuleName(syntax: ExprSyntax)
case badInfixOperator(syntax: ExprSyntax)
case badPrefixOperator(syntax: ExprSyntax)
case unexpectedDefined(syntax: ExprSyntax, argument: String)
var description: String {
switch self {
case .unknownExpression:
return "invalid conditional compilation expression"
case .unhandledFunction(name: let name, syntax: _):
return "build configuration cannot handle '\(name)'"
case .requiresUnlabeledArgument(name: let name, role: let role, syntax: _):
return "'\(name)' requires a single unlabeled argument for the \(role)"
case .unsupportedVersionOperator(name: let name, operator: let op):
return "'\(name)' version check does not support operator '\(op.trimmedDescription)'"
case .invalidVersionOperand(name: let name, syntax: let version):
return "'\(name)' version check has invalid version '\(version.trimmedDescription)'"
case .emptyVersionComponent(syntax: _):
return "found empty version component"
case .compilerVersionOutOfRange(value: let value, upperLimit: let upperLimit, syntax: _):
return "compiler version component '\(value)' is not in the allowed range 0...\(upperLimit)"
case .compilerVersionSecondComponentNotWildcard(syntax: _):
return "the second version component is not used for comparison in legacy compiler versions"
case .compilerVersionTooManyComponents(syntax: _):
return "compiler version must not have more than five components"
case .canImportMissingModule(syntax: _):
return "'canImport' requires a module name"
case .canImportLabel(syntax: _):
return "second parameter of 'canImport' should be labeled as _version or _underlyingVersion"
case .canImportTwoParameters(syntax: _):
return "'canImport' can take only two parameters"
case .ignoredTrailingComponents(version: let version, syntax: _):
return "trailing components of version '\(version.description)' are ignored"
case .integerLiteralCondition(syntax: let syntax, replacement: let replacement):
return "'\(syntax.trimmedDescription)' is not a valid conditional compilation expression, use '\(replacement)'"
case .likelySimulatorPlatform:
return
"platform condition appears to be testing for simulator environment; use 'targetEnvironment(simulator)' instead"
case .likelyTargetOS(syntax: _, replacement: let replacement?):
return "'TARGET_OS_*' preprocessor macros are not available in Swift; use '\(replacement)' instead"
case .likelyTargetOS(syntax: _, replacement: nil):
return "'TARGET_OS_*' preprocessor macros are not available in Swift; use 'os(...)' conditionals instead"
case .macabiIsMacCatalyst:
return "'macabi' has been renamed to 'macCatalyst'"
case .endiannessDoesNotMatch:
return "unknown endianness for build configuration '_endian' (must be 'big' or 'little')"
case .expectedModuleName:
return "expected module name"
case .badInfixOperator:
return "expected '&&' or '||' expression"
case .badPrefixOperator:
return "expected unary '!' expression"
case .unexpectedDefined:
return
"compilation conditions in Swift are always boolean and do not need to be checked for existence with 'defined()'"
}
}
/// Retrieve the syntax node associated with this error.
var syntax: Syntax {
switch self {
case .unknownExpression(let syntax),
.unhandledFunction(name: _, syntax: let syntax),
.requiresUnlabeledArgument(name: _, role: _, syntax: let syntax),
.invalidVersionOperand(name: _, syntax: let syntax),
.emptyVersionComponent(syntax: let syntax),
.compilerVersionOutOfRange(value: _, upperLimit: _, syntax: let syntax),
.compilerVersionTooManyComponents(syntax: let syntax),
.compilerVersionSecondComponentNotWildcard(syntax: let syntax),
.canImportMissingModule(syntax: let syntax),
.canImportLabel(syntax: let syntax),
.canImportTwoParameters(syntax: let syntax),
.ignoredTrailingComponents(version: _, syntax: let syntax),
.integerLiteralCondition(syntax: let syntax, replacement: _),
.likelySimulatorPlatform(syntax: let syntax),
.likelyTargetOS(syntax: let syntax, replacement: _),
.endiannessDoesNotMatch(syntax: let syntax, argument: _),
.macabiIsMacCatalyst(syntax: let syntax),
.expectedModuleName(syntax: let syntax),
.badInfixOperator(syntax: let syntax),
.badPrefixOperator(syntax: let syntax),
.unexpectedDefined(syntax: let syntax, argument: _):
return Syntax(syntax)
case .unsupportedVersionOperator(name: _, operator: let op):
return Syntax(op)
}
}
}
extension IfConfigDiagnostic: DiagnosticMessage {
var message: String { description }
var diagnosticID: MessageID {
.init(domain: "SwiftIfConfig", id: "IfConfigDiagnostic")
}
var severity: SwiftDiagnostics.DiagnosticSeverity {
switch self {
case .compilerVersionSecondComponentNotWildcard, .ignoredTrailingComponents,
.likelySimulatorPlatform, .likelyTargetOS, .endiannessDoesNotMatch, .macabiIsMacCatalyst:
return .warning
default: return .error
}
}
private struct SimpleFixItMessage: FixItMessage {
var message: String
var fixItID: MessageID {
.init(domain: "SwiftIfConfig", id: "IfConfigFixIt")
}
}
var asDiagnostic: Diagnostic {
// For the integer literal condition we have a Fix-It.
if case .integerLiteralCondition(let syntax, let replacement) = self {
return Diagnostic(
node: syntax,
message: self,
fixIt: .replace(
message: SimpleFixItMessage(
message: "replace with Boolean literal '\(replacement)'"
),
oldNode: syntax,
newNode: BooleanLiteralExprSyntax(
literal: .keyword(replacement ? .true : .false)
)
)
)
}
// For the likely targetEnvironment(simulator) condition we have a Fix-It.
if case .likelySimulatorPlatform(let syntax) = self {
return Diagnostic(
node: syntax,
message: self,
fixIt: .replace(
message: SimpleFixItMessage(
message: "replace with 'targetEnvironment(simulator)'"
),
oldNode: syntax,
newNode: "targetEnvironment(simulator)" as ExprSyntax
)
)
}
// For the likely TARGET_OS_* condition we may have a Fix-It.
if case .likelyTargetOS(let syntax, let replacement?) = self {
return Diagnostic(
node: syntax,
message: self,
fixIt: .replace(
message: SimpleFixItMessage(
message: "replace with '\(replacement)'"
),
oldNode: syntax,
newNode: replacement
)
)
}
// For the targetEnvironment(macabi) -> macCatalyst rename we have a Fix-It.
if case .macabiIsMacCatalyst(syntax: let syntax) = self {
return Diagnostic(
node: syntax,
message: self,
fixIt: .replace(
message: SimpleFixItMessage(
message: "replace with 'macCatalyst'"
),
oldNode: syntax,
newNode: "macCatalyst" as ExprSyntax
)
)
}
// For the targetEnvironment(macabi) -> macCatalyst rename we have a Fix-It.
if case .unexpectedDefined(syntax: let syntax, argument: let argument) = self {
return Diagnostic(
node: syntax,
message: self,
fixIt: .replace(
message: SimpleFixItMessage(
message: "remove 'defined()'"
),
oldNode: syntax,
newNode: TokenSyntax.identifier(argument)
)
)
}
return Diagnostic(node: syntax, message: self)
}
}
|