diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ExperimentalFeaturesFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ExperimentalFeaturesFile.swift index 0879e1a3a41..4ebbaf0dd12 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ExperimentalFeaturesFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ExperimentalFeaturesFile.swift @@ -20,7 +20,7 @@ let experimentalFeaturesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) """ extension Parser { @_spi(ExperimentalLanguageFeatures) - public struct ExperimentalFeatures: OptionSet, Sendable { + public struct ExperimentalFeatures: OptionSet, Hashable, Sendable { public let rawValue: UInt public init(rawValue: UInt) { self.rawValue = rawValue diff --git a/Package.swift b/Package.swift index 9801d35218e..961508b8807 100644 --- a/Package.swift +++ b/Package.swift @@ -166,7 +166,7 @@ let package = Package( .target( name: "SwiftIfConfig", - dependencies: ["SwiftSyntax", "SwiftSyntaxBuilder", "SwiftDiagnostics", "SwiftOperators"], + dependencies: ["SwiftSyntax", "SwiftSyntaxBuilder", "SwiftDiagnostics", "SwiftOperators", "SwiftParser"], exclude: ["CMakeLists.txt"] ), @@ -275,7 +275,13 @@ let package = Package( .target( name: "SwiftSyntaxMacros", - dependencies: ["SwiftDiagnostics", "SwiftParser", "SwiftSyntax", "SwiftSyntaxBuilder"], + dependencies: [ + "SwiftDiagnostics", + "SwiftIfConfig", + "SwiftParser", + "SwiftSyntax", + "SwiftSyntaxBuilder", + ], exclude: ["CMakeLists.txt"] ), @@ -322,6 +328,7 @@ let package = Package( "_SwiftSyntaxGenericTestSupport", "SwiftDiagnostics", "SwiftIDEUtils", + "SwiftIfConfig", "SwiftParser", "SwiftSyntaxMacros", "SwiftSyntaxMacroExpansion", diff --git a/Sources/SwiftCompilerPluginMessageHandling/CMakeLists.txt b/Sources/SwiftCompilerPluginMessageHandling/CMakeLists.txt index e0323a7abfc..3c6a9f416ab 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/CMakeLists.txt +++ b/Sources/SwiftCompilerPluginMessageHandling/CMakeLists.txt @@ -25,6 +25,7 @@ target_link_swift_syntax_libraries(SwiftCompilerPluginMessageHandling PUBLIC SwiftSyntax SwiftBasicFormat SwiftDiagnostics + SwiftIfConfig SwiftParser SwiftSyntaxMacros SwiftSyntaxMacroExpansion diff --git a/Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift b/Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift index 14f30c50d86..4065a814b71 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/CompilerPluginMessageHandler.swift @@ -21,8 +21,10 @@ import _SwiftSyntaxCShims #endif #if compiler(>=6) +internal import SwiftIfConfig public import SwiftSyntaxMacros #else +import SwiftIfConfig import SwiftSyntaxMacros #endif @@ -189,12 +191,25 @@ public class PluginProviderMessageHandler: PluginMessa let macroRole, let discriminator, let expandingSyntax, - let lexicalContext + let lexicalContext, + let staticBuildConfigurationString ): + // Decode the static build configuration. + let staticBuildConfiguration: StaticBuildConfiguration? + if let staticBuildConfigurationString { + var mutableConfigurationString = staticBuildConfigurationString + staticBuildConfiguration = mutableConfigurationString.withUTF8 { + try? JSON.decode(StaticBuildConfiguration.self, from: $0) + } + } else { + staticBuildConfiguration = nil + } + return expandFreestandingMacro( macro: macro, macroRole: macroRole, discriminator: discriminator, + staticBuildConfiguration: staticBuildConfiguration, expandingSyntax: expandingSyntax, lexicalContext: lexicalContext ) @@ -208,12 +223,25 @@ public class PluginProviderMessageHandler: PluginMessa let parentDeclSyntax, let extendedTypeSyntax, let conformanceListSyntax, - let lexicalContext + let lexicalContext, + let staticBuildConfigurationString ): + // Decode the static build configuration. + let staticBuildConfiguration: StaticBuildConfiguration? + if let staticBuildConfigurationString { + var mutableConfigurationString = staticBuildConfigurationString + staticBuildConfiguration = mutableConfigurationString.withUTF8 { + try? JSON.decode(StaticBuildConfiguration.self, from: $0) + } + } else { + staticBuildConfiguration = nil + } + return expandAttachedMacro( macro: macro, macroRole: macroRole, discriminator: discriminator, + staticBuildConfiguration: staticBuildConfiguration, attributeSyntax: attributeSyntax, declSyntax: declSyntax, parentDeclSyntax: parentDeclSyntax, diff --git a/Sources/SwiftCompilerPluginMessageHandling/Macros.swift b/Sources/SwiftCompilerPluginMessageHandling/Macros.swift index 41f630008ec..f571f41c1a3 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/Macros.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/Macros.swift @@ -13,14 +13,18 @@ #if compiler(>=6) internal import SwiftBasicFormat internal import SwiftDiagnostics +@_spi(ExperimentalLanguageFeatures) internal import SwiftIfConfig internal import SwiftOperators +@_spi(ExperimentalLanguageFeatures) internal import SwiftParser internal import SwiftSyntax @_spi(MacroExpansion) @_spi(ExperimentalLanguageFeature) internal import SwiftSyntaxMacroExpansion @_spi(ExperimentalLanguageFeature) internal import SwiftSyntaxMacros #else import SwiftBasicFormat import SwiftDiagnostics +@_spi(ExperimentalLanguageFeatures) import SwiftIfConfig import SwiftOperators +@_spi(ExperimentalLanguageFeatures) import SwiftParser import SwiftSyntax @_spi(MacroExpansion) @_spi(ExperimentalLanguageFeature) import SwiftSyntaxMacroExpansion @_spi(ExperimentalLanguageFeature) import SwiftSyntaxMacros @@ -36,6 +40,8 @@ extension PluginProviderMessageHandler { private static func resolveLexicalContext( _ lexicalContext: [PluginMessage.Syntax]?, sourceManager: SourceManager, + swiftVersion: Parser.SwiftVersion?, + experimentalFeatures: Parser.ExperimentalFeatures?, operatorTable: OperatorTable, fallbackSyntax: some SyntaxProtocol ) -> [Syntax] { @@ -45,7 +51,14 @@ extension PluginProviderMessageHandler { return fallbackSyntax.allMacroLexicalContexts() } - return lexicalContext.map { sourceManager.add($0, foldingWith: operatorTable) } + return lexicalContext.map { + sourceManager.add( + $0, + swiftVersion: swiftVersion, + experimentalFeatures: experimentalFeatures, + foldingWith: operatorTable + ) + } } /// Expand `@freestainding(XXX)` macros. @@ -53,21 +66,32 @@ extension PluginProviderMessageHandler { macro: PluginMessage.MacroReference, macroRole pluginMacroRole: PluginMessage.MacroRole?, discriminator: String, + staticBuildConfiguration: StaticBuildConfiguration?, expandingSyntax: PluginMessage.Syntax, lexicalContext: [PluginMessage.Syntax]? ) -> PluginToHostMessage { let sourceManager = SourceManager(syntaxRegistry: syntaxRegistry) - let syntax = sourceManager.add(expandingSyntax, foldingWith: .standardOperators) + let swiftVersion = staticBuildConfiguration?.parserSwiftVersion + let experimentalFeatures = staticBuildConfiguration?.experimentalFeatures + let syntax = sourceManager.add( + expandingSyntax, + swiftVersion: swiftVersion, + experimentalFeatures: experimentalFeatures, + foldingWith: .standardOperators + ) let context = PluginMacroExpansionContext( sourceManager: sourceManager, lexicalContext: Self.resolveLexicalContext( lexicalContext, sourceManager: sourceManager, + swiftVersion: swiftVersion, + experimentalFeatures: experimentalFeatures, operatorTable: .standardOperators, fallbackSyntax: syntax ), - expansionDiscriminator: discriminator + expansionDiscriminator: discriminator, + staticBuildConfiguration: staticBuildConfiguration ) let expandedSource: String? @@ -113,6 +137,7 @@ extension PluginProviderMessageHandler { macro: PluginMessage.MacroReference, macroRole: PluginMessage.MacroRole, discriminator: String, + staticBuildConfiguration: StaticBuildConfiguration?, attributeSyntax: PluginMessage.Syntax, declSyntax: PluginMessage.Syntax, parentDeclSyntax: PluginMessage.Syntax?, @@ -121,17 +146,27 @@ extension PluginProviderMessageHandler { lexicalContext: [PluginMessage.Syntax]? ) -> PluginToHostMessage { let sourceManager = SourceManager(syntaxRegistry: syntaxRegistry) - let attributeNode = sourceManager.add( - attributeSyntax, - foldingWith: .standardOperators - ).cast(AttributeSyntax.self) - let declarationNode = sourceManager.add(declSyntax) - let parentDeclNode = parentDeclSyntax.map { sourceManager.add($0).cast(DeclSyntax.self) } + let swiftVersion = staticBuildConfiguration?.parserSwiftVersion + let experimentalFeatures = staticBuildConfiguration?.experimentalFeatures + + func addToSourceManager(_ syntax: PluginMessage.Syntax) -> Syntax { + sourceManager.add( + syntax, + swiftVersion: swiftVersion, + experimentalFeatures: experimentalFeatures, + foldingWith: .standardOperators + ) + } + + let attributeNode = addToSourceManager(attributeSyntax) + .cast(AttributeSyntax.self) + let declarationNode = addToSourceManager(declSyntax) + let parentDeclNode = parentDeclSyntax.map { addToSourceManager($0).cast(DeclSyntax.self) } let extendedType = extendedTypeSyntax.map { - sourceManager.add($0).cast(TypeSyntax.self) + addToSourceManager($0).cast(TypeSyntax.self) } let conformanceList = conformanceListSyntax.map { - let placeholderStruct = sourceManager.add($0).cast(StructDeclSyntax.self) + let placeholderStruct = addToSourceManager($0).cast(StructDeclSyntax.self) return placeholderStruct.inheritanceClause!.inheritedTypes } @@ -140,10 +175,13 @@ extension PluginProviderMessageHandler { lexicalContext: Self.resolveLexicalContext( lexicalContext, sourceManager: sourceManager, + swiftVersion: swiftVersion, + experimentalFeatures: experimentalFeatures, operatorTable: .standardOperators, fallbackSyntax: declarationNode ), - expansionDiscriminator: discriminator + expansionDiscriminator: discriminator, + staticBuildConfiguration: staticBuildConfiguration ) // TODO: Make this a 'String?' and remove non-'hasExpandMacroResult' branches. diff --git a/Sources/SwiftCompilerPluginMessageHandling/PluginMacroExpansionContext.swift b/Sources/SwiftCompilerPluginMessageHandling/PluginMacroExpansionContext.swift index 85b5e5d54ec..2a53c520f2a 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/PluginMacroExpansionContext.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/PluginMacroExpansionContext.swift @@ -12,14 +12,16 @@ #if compiler(>=6) internal import SwiftDiagnostics +internal import SwiftIfConfig internal import SwiftOperators -internal import SwiftParser +@_spi(ExperimentalLanguageFeatures) internal import SwiftParser internal import SwiftSyntax internal import SwiftSyntaxMacros #else import SwiftDiagnostics +import SwiftIfConfig import SwiftOperators -import SwiftParser +@_spi(ExperimentalLanguageFeatures) import SwiftParser import SwiftSyntax import SwiftSyntaxMacros #endif @@ -29,6 +31,8 @@ class ParsedSyntaxRegistry { struct Key: Hashable { let source: String let kind: PluginMessage.Syntax.Kind + let swiftVersion: Parser.SwiftVersion + let experimentalFeatures: Parser.ExperimentalFeatures } private var storage: LRUCache @@ -37,8 +41,17 @@ class ParsedSyntaxRegistry { self.storage = LRUCache(capacity: cacheCapacity) } - private func parse(source: String, kind: PluginMessage.Syntax.Kind) -> Syntax { - var parser = Parser(source) + private func parse( + source: String, + kind: PluginMessage.Syntax.Kind, + swiftVersion: Parser.SwiftVersion, + experimentalFeatures: Parser.ExperimentalFeatures + ) -> Syntax { + var parser = Parser( + source, + swiftVersion: swiftVersion, + experimentalFeatures: experimentalFeatures + ) switch kind { case .declaration: return Syntax(DeclSyntax.parse(from: &parser)) @@ -55,13 +68,30 @@ class ParsedSyntaxRegistry { } } - func get(source: String, kind: PluginMessage.Syntax.Kind) -> Syntax { - let key = Key(source: source, kind: kind) + func get( + source: String, + kind: PluginMessage.Syntax.Kind, + swiftVersion: Parser.SwiftVersion?, + experimentalFeatures: Parser.ExperimentalFeatures? + ) -> Syntax { + let swiftVersion = swiftVersion ?? Parser.defaultSwiftVersion + let experimentalFeatures = experimentalFeatures ?? Parser.ExperimentalFeatures() + let key = Key( + source: source, + kind: kind, + swiftVersion: swiftVersion, + experimentalFeatures: experimentalFeatures + ) if let cached = storage[key] { return cached } - let node = parse(source: source, kind: kind) + let node = parse( + source: source, + kind: kind, + swiftVersion: swiftVersion, + experimentalFeatures: experimentalFeatures + ) storage[key] = node return node } @@ -124,10 +154,16 @@ class SourceManager { /// are cached in the source manager to provide `location(of:)` et al. func add( _ syntaxInfo: PluginMessage.Syntax, - foldingWith operatorTable: OperatorTable? = nil + swiftVersion: Parser.SwiftVersion?, + experimentalFeatures: Parser.ExperimentalFeatures?, + foldingWith operatorTable: OperatorTable? ) -> Syntax { - - var node = syntaxRegistry.get(source: syntaxInfo.source, kind: syntaxInfo.kind) + var node = syntaxRegistry.get( + source: syntaxInfo.source, + kind: syntaxInfo.kind, + swiftVersion: swiftVersion, + experimentalFeatures: experimentalFeatures + ) if let operatorTable { node = operatorTable.foldAll(node, errorHandler: { _ in /*ignore*/ }) } @@ -262,6 +298,10 @@ class PluginMacroExpansionContext { /// to produce unique names. private var expansionDiscriminator: String + /// The static build configuration, if any, that will be used for the + /// macro-expanded code. + private var staticBuildConfiguration: StaticBuildConfiguration? + /// Counter for each of the uniqued names. /// /// Used in conjunction with `expansionDiscriminator`. @@ -271,10 +311,16 @@ class PluginMacroExpansionContext { /// macro. internal private(set) var diagnostics: [Diagnostic] = [] - init(sourceManager: SourceManager, lexicalContext: [Syntax], expansionDiscriminator: String = "") { + init( + sourceManager: SourceManager, + lexicalContext: [Syntax], + expansionDiscriminator: String = "", + staticBuildConfiguration: StaticBuildConfiguration? + ) { self.sourceManager = sourceManager self.lexicalContext = lexicalContext self.expansionDiscriminator = expansionDiscriminator + self.staticBuildConfiguration = staticBuildConfiguration } } @@ -321,4 +367,8 @@ extension PluginMacroExpansionContext: MacroExpansionContext { } return AbstractSourceLocation(location) } + + public var buildConfiguration: (any BuildConfiguration)? { + staticBuildConfiguration + } } diff --git a/Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift b/Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift index 4d8b5a3bf72..ab55433521a 100644 --- a/Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift +++ b/Sources/SwiftCompilerPluginMessageHandling/PluginMessages.swift @@ -25,7 +25,8 @@ public enum HostToPluginMessage: Codable { macroRole: PluginMessage.MacroRole? = nil, discriminator: String, syntax: PluginMessage.Syntax, - lexicalContext: [PluginMessage.Syntax]? = nil + lexicalContext: [PluginMessage.Syntax]? = nil, + staticBuildConfiguration: String? = nil ) /// Expand an '@attached' macro. @@ -38,7 +39,8 @@ public enum HostToPluginMessage: Codable { parentDeclSyntax: PluginMessage.Syntax?, extendedTypeSyntax: PluginMessage.Syntax?, conformanceListSyntax: PluginMessage.Syntax?, - lexicalContext: [PluginMessage.Syntax]? = nil + lexicalContext: [PluginMessage.Syntax]? = nil, + staticBuildConfiguration: String? = nil ) /// Optionally implemented message to load a dynamic link library. diff --git a/Sources/SwiftIfConfig/BuildConfiguration.swift b/Sources/SwiftIfConfig/BuildConfiguration.swift index 407aa1a13d6..dfa90599340 100644 --- a/Sources/SwiftIfConfig/BuildConfiguration.swift +++ b/Sources/SwiftIfConfig/BuildConfiguration.swift @@ -14,7 +14,7 @@ import SwiftSyntax /// Describes the ordering of a sequence of bytes that make up a word of /// storage for a particular architecture. -public enum Endianness: String { +public enum Endianness: String, Codable { /// Little endian, meaning that the least significant byte of a word is /// stored at the lowest address. case little diff --git a/Sources/SwiftIfConfig/CMakeLists.txt b/Sources/SwiftIfConfig/CMakeLists.txt index 2df83517c5b..90601a0ae59 100644 --- a/Sources/SwiftIfConfig/CMakeLists.txt +++ b/Sources/SwiftIfConfig/CMakeLists.txt @@ -17,6 +17,7 @@ add_swift_syntax_library(SwiftIfConfig IfConfigDiagnostic.swift IfConfigEvaluation.swift IfConfigFunctions.swift + StaticBuildConfiguration.swift SyntaxLiteralUtils.swift SyntaxProtocol+IfConfig.swift VersionTuple+Parsing.swift @@ -27,4 +28,5 @@ target_link_swift_syntax_libraries(SwiftIfConfig PUBLIC SwiftSyntax SwiftSyntaxBuilder SwiftDiagnostics - SwiftOperators) + SwiftOperators + SwiftParser) diff --git a/Sources/SwiftIfConfig/StaticBuildConfiguration.swift b/Sources/SwiftIfConfig/StaticBuildConfiguration.swift new file mode 100644 index 00000000000..1af7cffda07 --- /dev/null +++ b/Sources/SwiftIfConfig/StaticBuildConfiguration.swift @@ -0,0 +1,462 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 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 +// +//===----------------------------------------------------------------------===// + +#if compiler(>=6) +@_spi(ExperimentalLanguageFeatures) public import SwiftParser +public import SwiftSyntax +#else +@_spi(ExperimentalLanguageFeatures) import SwiftParser +import SwiftSyntax +#endif + +/// A statically-determined build configuration that can be used with any +/// API that requires a build configuration. Static build configurations can +/// be (de-)serialized via Codable. +/// +/// Static build configurations can not be used for canImport checks, because +/// such checks require deeper integration with the compiler itself. +public struct StaticBuildConfiguration: Codable { + public init( + customConditions: Set = [], + features: Set = [], + attributes: Set = [], + targetOSs: Set = [], + targetArchitectures: Set = [], + targetEnvironments: Set = [], + targetRuntimes: Set = [], + targetPointerAuthenticationSchemes: Set = [], + targetObjectFileFormats: Set = [], + targetPointerBitWidth: Int = 64, + targetAtomicBitWidths: [Int] = [], + endianness: Endianness = .little, + languageVersion: VersionTuple, + compilerVersion: VersionTuple + ) { + self.customConditions = customConditions + self.features = features + self.attributes = attributes + self.targetOSs = targetOSs + self.targetArchitectures = targetArchitectures + self.targetEnvironments = targetEnvironments + self.targetRuntimes = targetRuntimes + self.targetPointerAuthenticationSchemes = targetPointerAuthenticationSchemes + self.targetObjectFileFormats = targetObjectFileFormats + self.targetPointerBitWidth = targetPointerBitWidth + self.targetAtomicBitWidths = targetAtomicBitWidths + self.endianness = endianness + self.languageMode = languageVersion + self.compilerVersion = compilerVersion + } + + /// The set of custom conditions that are present and can be used with `#if`. + /// + /// Custom build conditions can be set by the `-D` command line option to + /// the Swift compiler. For example, `-DDEBUG` sets the custom condition + /// named `DEBUG`, which could be checked with, e.g., + /// + /// ```swift + /// #if DEBUG + /// // ... + /// #endif + /// ``` + public var customConditions: Set = [] + + /// The set of language features that are enabled. + /// + /// Features are determined by the Swift compiler, language mode, and other + /// options such as `--enable-upcoming-feature`, and can be checked with + /// the `hasFeature` syntax, e.g., + /// + /// ```swift + /// #if hasFeature(VariadicGenerics) + /// // ... + /// #endif + /// ``` + public var features: Set = [] + + /// The set of attributes that are available. + /// + /// Attributes are determined by the Swift compiler. They can be checked + /// with `hasAttribute` syntax, e.g., + /// + /// ```swift + /// #if hasAttribute(available) + /// // ... + /// #endif + /// ``` + public var attributes: Set = [] + + /// The active target OS, e.g., "Windows", "iOS". + /// + /// The target operating system can be queried with `os()`, e.g., + /// + /// ```swift + /// #if os(Linux) + /// // Linux-specific implementation + /// #endif + /// ``` + public var targetOSs: Set = [] + + /// The active target architectures, e.g., "x64_64". + /// + /// The target processor architecture can be queried with `arch()`, e.g., + /// + /// ```swift + /// #if arch(x86_64) + /// // 64-bit x86 Intel-specific code + /// #endif + /// ``` + public var targetArchitectures: Set = [] + + /// The active target environments, e.g., "simulator". + /// + /// The target environment can be queried with `targetEnvironment()`, + /// e.g., + /// + /// ```swift + /// #if targetEnvironment(simulator) + /// // Simulator-specific code + /// #endif + /// ``` + public var targetEnvironments: Set = [] + + /// The active target runtimes, e.g., _ObjC. + /// + /// The target runtime can only be queried by an experimental syntax + /// `_runtime()`, e.g., + /// + /// ```swift + /// #if _runtime(_ObjC) + /// // Code that depends on Swift being built for use with the Objective-C + /// // runtime, e.g., on Apple platforms. + /// #endif + /// ``` + public var targetRuntimes: Set = [] + + /// The active target's pointer authentication schemes, e.g., "arm64e". + /// + /// The target pointer authentication scheme describes how pointers are + /// signed, as a security mitigation. This scheme can only be queried by + /// an experimental syntax `_ptrath()`, e.g., + /// + /// ```swift + /// #if _ptrauth(arm64e) + /// // Special logic for arm64e pointer signing + /// #endif + /// ``` + public var targetPointerAuthenticationSchemes: Set = [] + + /// The active target's object file formats, e.g., "COFF" + /// + /// The target object file format can only be queried by an experimental + /// syntax `_objectFileFormat()`, e.g., + /// + /// ```swift + /// #if _objectFileFormat(ELF) + /// // Special logic for ELF object file formats + /// #endif + /// ``` + public var targetObjectFileFormats: Set = [] + + /// The bit width of a data pointer for the target architecture. + /// + /// The target's pointer bit width (which also corresponds to the number of + /// bits in `Int`/`UInt`) can only be queried with the experimental syntax + /// `_pointerBitWidth(_)`, e.g., + /// + /// ```swift + /// #if _pointerBitWidth(_32) + /// // 32-bit system + /// #endif + /// ``` + public var targetPointerBitWidth: Int = 64 + + /// The atomic bit widths that are natively supported by the target + /// architecture. + /// + /// This lists all of the bit widths for which the target provides support + /// for atomic operations. It can be queried with + /// `_hasAtomicBitWidth(_)`, e.g. + /// + /// ```swift + /// #if _hasAtomicBitWidth(_64) + /// // 64-bit atomics are available + /// #endif + public var targetAtomicBitWidths: [Int] = [] + + /// The endianness of the target architecture. + /// + /// The target's endianness can onyl be queried with the experimental syntax + /// `_endian()`, where `` can be either "big" or "little", e.g., + /// + /// ```swift + /// #if _endian(little) + /// // Swap some bytes around for network byte order + /// #endif + /// ``` + public var endianness: Endianness = .little + + /// The effective language mode, which can be set by the user (e.g., 5.0). + /// + /// The language version can be queried with the `swift` directive that checks + /// how the supported language version compares, as described by + /// [SE-0212](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0212-compiler-version-directive.md). For example: + /// + /// ```swift + /// #if swift(>=6.0) + /// // Hooray, we can use tasks! + /// ``` + public var languageMode: VersionTuple + + /// The version of the compiler (e.g., 5.9). + /// + /// The compiler version can be queried with the `compiler` directive that + /// checks the specific version of the compiler being used to process the + /// code, e.g., + /// + /// ```swift + /// #if compiler(>=5.7) + /// // Hoorway, we can implicitly open existentials! + /// #endif + public var compilerVersion: VersionTuple +} + +extension StaticBuildConfiguration: BuildConfiguration { + /// Determine whether a given custom build condition has been set. + /// + /// Custom build conditions can be set by the `-D` command line option to + /// the Swift compiler. For example, `-DDEBUG` sets the custom condition + /// named `DEBUG`, which could be checked with, e.g., + /// + /// ```swift + /// #if DEBUG + /// // ... + /// #endif + /// ``` + /// + /// - Parameters: + /// - name: The name of the custom build condition being checked (e.g., + /// `DEBUG`. + /// - Returns: Whether the custom condition is set. + public func isCustomConditionSet(name: String) -> Bool { + // "$FeatureName" can be used to check for a language feature. + if let firstChar = name.first, firstChar == "$" { + return features.contains(String(name.dropFirst())) + } + + return customConditions.contains(name) + } + + /// Determine whether the given feature is enabled. + /// + /// Features are determined by the Swift compiler, language mode, and other + /// options such as `--enable-upcoming-feature`, and can be checked with + /// the `hasFeature` syntax, e.g., + /// + /// ```swift + /// #if hasFeature(VariadicGenerics) + /// // ... + /// #endif + /// ``` + /// + /// - Parameters: + /// - name: The name of the feature being checked. + /// - Returns: Whether the requested feature is available. + public func hasFeature(name: String) -> Bool { + features.contains(name) + } + + /// Determine whether the given attribute is available. + /// + /// Attributes are determined by the Swift compiler. They can be checked + /// with `hasAttribute` syntax, e.g., + /// + /// ```swift + /// #if hasAttribute(available) + /// // ... + /// #endif + /// ``` + /// + /// - Parameters: + /// - name: The name of the attribute being queried. + /// - Returns: Whether the requested attribute is supported. + public func hasAttribute(name: String) -> Bool { + attributes.contains(name) + } + + /// Determine whether a module with the given import path can be imported, + /// with additional version information. + /// + /// This implementation always throws an error, because static build + /// configurations cannot evaluate canImport checks. + public func canImport(importPath: [(TokenSyntax, String)], version: CanImportVersion) throws -> Bool { + throw StaticBuildConfiguration.Error.canImportUnavailable + } + + /// Determine whether the given name is the active target OS (e.g., Linux, iOS). + /// + /// The target operating system can be queried with `os()`, e.g., + /// + /// ```swift + /// #if os(Linux) + /// // Linux-specific implementation + /// #endif + /// ``` + /// + /// - Parameters: + /// - name: The name of the operating system being queried, such as `Linux`, + /// `Windows`, `macOS`, etc. + /// - Returns: Whether the given operating system name is the target operating + /// system, i.e., the operating system for which code is being generated. + public func isActiveTargetOS(name: String) -> Bool { + targetOSs.contains(name) + } + + /// Determine whether the given name is the active target architecture + /// (e.g., x86_64, arm64). + /// + /// The target processor architecture can be queried with `arch()`, e.g., + /// + /// ```swift + /// #if arch(x86_64) + /// // 64-bit x86 Intel-specific code + /// #endif + /// ``` + /// + /// - Parameters: + /// - name: The name of the target architecture to check. + /// - Returns: Whether the given processor architecture is the target + /// architecture. + public func isActiveTargetArchitecture(name: String) -> Bool { + targetArchitectures.contains(name) + } + + /// Determine whether the given name is the active target environment (e.g., simulator) + /// + /// The target environment can be queried with `targetEnvironment()`, + /// e.g., + /// + /// ```swift + /// #if targetEnvironment(simulator) + /// // Simulator-specific code + /// #endif + /// ``` + /// + /// - Parameters: + /// - name: The name of the target environment to check. + /// - Returns: Whether the target platform is for a specific environment, + /// such as a simulator or emulator. + public func isActiveTargetEnvironment(name: String) -> Bool { + targetEnvironments.contains(name) + } + + /// Determine whether the given name is the active target runtime (e.g., _ObjC vs. _Native) + /// + /// The target runtime can only be queried by an experimental syntax + /// `_runtime()`, e.g., + /// + /// ```swift + /// #if _runtime(_ObjC) + /// // Code that depends on Swift being built for use with the Objective-C + /// // runtime, e.g., on Apple platforms. + /// #endif + /// ``` + /// + /// The only other runtime is "none", when Swift isn't tying into any other + /// specific runtime. + /// + /// - Parameters: + /// - name: The name of the runtime. + /// - Returns: Whether the target runtime matches the given name. + public func isActiveTargetRuntime(name: String) -> Bool { + targetRuntimes.contains(name) + } + + /// Determine whether the given name is the active target pointer authentication scheme (e.g., arm64e). + /// + /// The target pointer authentication scheme describes how pointers are + /// signed, as a security mitigation. This scheme can only be queried by + /// an experimental syntax `_ptrath()`, e.g., + /// + /// ```swift + /// #if _ptrauth(arm64e) + /// // Special logic for arm64e pointer signing + /// #endif + /// ``` + /// - Parameters: + /// - name: The name of the pointer authentication scheme to check. + /// - Returns: Whether the code generated for the target will use the given + /// pointer authentication scheme. + public func isActiveTargetPointerAuthentication(name: String) -> Bool { + targetPointerAuthenticationSchemes.contains(name) + } + + /// Determine whether the given name is the active target object file format (e.g., ELF). + /// + /// The target object file format can only be queried by an experimental + /// syntax `_objectFileFormat()`, e.g., + /// + /// ```swift + /// #if _objectFileFormat(ELF) + /// // Special logic for ELF object file formats + /// #endif + /// ``` + /// - Parameters: + /// - name: The name of the object file format. + /// - Returns: Whether the target object file format matches the given name. + @_spi(ExperimentalLanguageFeatures) + public func isActiveTargetObjectFileFormat(name: String) -> Bool { + targetObjectFileFormats.contains(name) + } + + /// Equivalent to `languageMode`, but required for conformance to the + /// `BuildConfiguration` protocol. + public var languageVersion: VersionTuple { + languageMode + } +} + +extension StaticBuildConfiguration { + enum Error: Swift.Error { + /// Indicates when the static build configuration was asked to evaluate + /// canImport, which it cannot do correctly. + case canImportUnavailable + } +} + +extension StaticBuildConfiguration { + /// The Swift version that can be set for the parser. + public var parserSwiftVersion: Parser.SwiftVersion { + if languageMode < VersionTuple(5) { + return .v4 + } else if languageMode < VersionTuple(6) { + return .v5 + } else if languageMode < VersionTuple(7) { + return .v6 + } else { + return Parser.defaultSwiftVersion + } + } + + /// Determine the set of experimental features that are enabled by this + /// static build configuration. + @_spi(ExperimentalLanguageFeatures) + public var experimentalFeatures: Parser.ExperimentalFeatures { + var result: Parser.ExperimentalFeatures = [] + for feature in features { + if let experimentalFeature = Parser.ExperimentalFeatures(name: feature) { + result.insert(experimentalFeature) + } + } + return result + } +} diff --git a/Sources/SwiftIfConfig/VersionTuple.swift b/Sources/SwiftIfConfig/VersionTuple.swift index d31d544b59b..ce2484c5a3c 100644 --- a/Sources/SwiftIfConfig/VersionTuple.swift +++ b/Sources/SwiftIfConfig/VersionTuple.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// /// Describes a version such as `5.9`. -public struct VersionTuple: Sendable { +public struct VersionTuple: Sendable, Codable { /// The components of the version tuple, start with the major version. public var components: [Int] diff --git a/Sources/SwiftParser/Parser.swift b/Sources/SwiftParser/Parser.swift index d60b37d7533..ef268d21da5 100644 --- a/Sources/SwiftParser/Parser.swift +++ b/Sources/SwiftParser/Parser.swift @@ -137,7 +137,7 @@ public struct Parser { #endif /// The Swift version as which source files should be parsed if no Swift version is explicitly specified in the parser. - static let defaultSwiftVersion: SwiftVersion = .v6 + public static let defaultSwiftVersion: SwiftVersion = .v6 var _emptyRawMultipleTrailingClosureElementListSyntax: RawMultipleTrailingClosureElementListSyntax? diff --git a/Sources/SwiftParser/generated/ExperimentalFeatures.swift b/Sources/SwiftParser/generated/ExperimentalFeatures.swift index 1c12c14a148..3051769b6f7 100644 --- a/Sources/SwiftParser/generated/ExperimentalFeatures.swift +++ b/Sources/SwiftParser/generated/ExperimentalFeatures.swift @@ -15,7 +15,7 @@ extension Parser { @_spi(ExperimentalLanguageFeatures) - public struct ExperimentalFeatures: OptionSet, Sendable { + public struct ExperimentalFeatures: OptionSet, Hashable, Sendable { public let rawValue: UInt public init(rawValue: UInt) { diff --git a/Sources/SwiftSyntaxMacroExpansion/BasicMacroExpansionContext.swift b/Sources/SwiftSyntaxMacroExpansion/BasicMacroExpansionContext.swift index 013531a1507..56efff677ce 100644 --- a/Sources/SwiftSyntaxMacroExpansion/BasicMacroExpansionContext.swift +++ b/Sources/SwiftSyntaxMacroExpansion/BasicMacroExpansionContext.swift @@ -12,11 +12,13 @@ #if compiler(>=6) public import SwiftDiagnostics +public import SwiftIfConfig internal import SwiftOperators public import SwiftSyntax public import SwiftSyntaxMacros #else import SwiftDiagnostics +import SwiftIfConfig import SwiftOperators import SwiftSyntax import SwiftSyntaxMacros @@ -62,6 +64,9 @@ public class BasicMacroExpansionContext { /// /// Used in conjunction with `expansionDiscriminator`. var uniqueNames: [String: Int] = [:] + + /// The build configuration that will be applied to the expanded code. + var buildConfiguration: (any BuildConfiguration)? = nil } /// State shared by different instances of the macro expansion context, @@ -82,12 +87,14 @@ public class BasicMacroExpansionContext { public init( lexicalContext: [Syntax] = [], expansionDiscriminator: String = "__macro_local_", - sourceFiles: [SourceFileSyntax: KnownSourceFile] = [:] + sourceFiles: [SourceFileSyntax: KnownSourceFile] = [:], + buildConfiguration: (any BuildConfiguration)? = nil ) { self.sharedState = SharedState() self.lexicalContext = lexicalContext self.expansionDiscriminator = expansionDiscriminator self.sharedState.sourceFiles = sourceFiles + self.sharedState.buildConfiguration = buildConfiguration } /// Create a new macro evaluation context that shares most of its global diff --git a/Sources/SwiftSyntaxMacros/CMakeLists.txt b/Sources/SwiftSyntaxMacros/CMakeLists.txt index 1d046f2c35a..b89a2b13826 100644 --- a/Sources/SwiftSyntaxMacros/CMakeLists.txt +++ b/Sources/SwiftSyntaxMacros/CMakeLists.txt @@ -29,5 +29,6 @@ add_swift_syntax_library(SwiftSyntaxMacros ) target_link_swift_syntax_libraries(SwiftSyntaxMacros PUBLIC + SwiftIfConfig SwiftSyntaxBuilder ) diff --git a/Sources/SwiftSyntaxMacros/MacroExpansionContext.swift b/Sources/SwiftSyntaxMacros/MacroExpansionContext.swift index 608a5760743..53bee858e3d 100644 --- a/Sources/SwiftSyntaxMacros/MacroExpansionContext.swift +++ b/Sources/SwiftSyntaxMacros/MacroExpansionContext.swift @@ -12,10 +12,12 @@ #if compiler(>=6) public import SwiftDiagnostics +public import SwiftIfConfig public import SwiftSyntax import SwiftSyntaxBuilder #else import SwiftDiagnostics +import SwiftIfConfig import SwiftSyntax import SwiftSyntaxBuilder #endif @@ -70,6 +72,15 @@ public protocol MacroExpansionContext: AnyObject { /// This array can be empty if there is no context, for example when a /// freestanding macro is used at file scope. var lexicalContext: [Syntax] { get } + + /// Returns the build configuration that will be used with the generated + /// source code. Macro implementations can use this information to determine + /// the context into which they are generating code. + /// + /// When the build configuration is not known, for example because the + /// compiler has not provided this information to the macro implementation, + /// this returns nil. + var buildConfiguration: (any BuildConfiguration)? { get } } extension MacroExpansionContext { @@ -187,3 +198,8 @@ public enum SourceLocationFilePathMode { /// e.g., `/home/taylor/alison.swift`. case filePath } + +extension MacroExpansionContext { + /// Default implementation that produces no build configuration. + public var buildConfiguration: (any BuildConfiguration)? { nil } +} diff --git a/Sources/SwiftSyntaxMacrosGenericTestSupport/Assertions.swift b/Sources/SwiftSyntaxMacrosGenericTestSupport/Assertions.swift index 64cd8e7aaa8..3450dbaac06 100644 --- a/Sources/SwiftSyntaxMacrosGenericTestSupport/Assertions.swift +++ b/Sources/SwiftSyntaxMacrosGenericTestSupport/Assertions.swift @@ -13,6 +13,7 @@ #if compiler(>=6) import SwiftBasicFormat public import SwiftDiagnostics +public import SwiftIfConfig @_spi(FixItApplier) import SwiftIDEUtils import SwiftParser import SwiftParserDiagnostics @@ -24,6 +25,7 @@ private import _SwiftSyntaxGenericTestSupport import SwiftBasicFormat import SwiftDiagnostics @_spi(FixItApplier) import SwiftIDEUtils +import SwiftIfConfig import SwiftParser import SwiftParserDiagnostics import SwiftSyntax @@ -497,6 +499,7 @@ public func assertMacroExpansion( testModuleName: String = "TestModule", testFileName: String = "test.swift", indentationWidth: Trivia = .spaces(4), + buildConfiguration: (any BuildConfiguration)? = nil, failureHandler: (TestFailureSpec) -> Void, fileID: StaticString = #fileID, filePath: StaticString = #filePath, @@ -509,7 +512,8 @@ public func assertMacroExpansion( // Expand all macros in the source. let context = BasicMacroExpansionContext( - sourceFiles: [origSourceFile: .init(moduleName: testModuleName, fullFilePath: testFileName)] + sourceFiles: [origSourceFile: .init(moduleName: testModuleName, fullFilePath: testFileName)], + buildConfiguration: buildConfiguration ) func contextGenerator(_ syntax: Syntax) -> BasicMacroExpansionContext { diff --git a/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift b/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift index 7c131c62878..ffcb4d7e6a6 100644 --- a/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift +++ b/Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift @@ -11,12 +11,14 @@ //===----------------------------------------------------------------------===// #if compiler(>=6) +public import SwiftIfConfig public import SwiftSyntax public import SwiftSyntaxMacroExpansion public import SwiftSyntaxMacros @_spi(XCTestFailureLocation) public import SwiftSyntaxMacrosGenericTestSupport private import XCTest #else +import SwiftIfConfig import SwiftSyntax import SwiftSyntaxMacroExpansion import SwiftSyntaxMacros @@ -44,8 +46,9 @@ public typealias DiagnosticSpec = SwiftSyntaxMacrosGenericTestSupport.Diagnostic /// - testModuleName: The name of the test module to use. /// - testFileName: The name of the test file name to use. /// - indentationWidth: The indentation width used in the expansion. -/// -/// - SeeAlso: ``assertMacroExpansion(_:expandedSource:diagnostics:macroSpecs:applyFixIts:fixedSource:testModuleName:testFileName:indentationWidth:file:line:)`` +/// - buildConfiguration: a build configuration that will be made available +/// to the macro implementation +/// - SeeAlso: ``assertMacroExpansion(_:expandedSource:diagnostics:macroSpecs:applyFixIts:fixedSource:testModuleName:testFileName:indentationWidth:buildConfiguration:file:line:)`` /// to also specify the list of conformances passed to the macro expansion. public func assertMacroExpansion( _ originalSource: String, @@ -57,6 +60,7 @@ public func assertMacroExpansion( testModuleName: String = "TestModule", testFileName: String = "test.swift", indentationWidth: Trivia = .spaces(4), + buildConfiguration: (any BuildConfiguration)? = nil, file: StaticString = #filePath, line: UInt = #line ) { @@ -71,6 +75,7 @@ public func assertMacroExpansion( testModuleName: testModuleName, testFileName: testFileName, indentationWidth: indentationWidth, + buildConfiguration: buildConfiguration, file: file, line: line ) @@ -104,6 +109,7 @@ public func assertMacroExpansion( testModuleName: String = "TestModule", testFileName: String = "test.swift", indentationWidth: Trivia = .spaces(4), + buildConfiguration: (any BuildConfiguration)? = nil, file: StaticString = #filePath, line: UInt = #line ) { @@ -117,6 +123,7 @@ public func assertMacroExpansion( testModuleName: testModuleName, testFileName: testFileName, indentationWidth: indentationWidth, + buildConfiguration: buildConfiguration, failureHandler: { XCTFail($0.message, file: $0.location.staticFilePath, line: $0.location.unsignedLine) }, diff --git a/SwiftParserCLI/Package.swift b/SwiftParserCLI/Package.swift index c59ec321d32..ef12c957302 100644 --- a/SwiftParserCLI/Package.swift +++ b/SwiftParserCLI/Package.swift @@ -25,6 +25,7 @@ let package = Package( "InstructionCounter", .product(name: "SwiftBasicFormat", package: "swift-syntax"), .product(name: "SwiftDiagnostics", package: "swift-syntax"), + .product(name: "SwiftIfConfig", package: "swift-syntax"), .product(name: "SwiftOperators", package: "swift-syntax"), .product(name: "SwiftParser", package: "swift-syntax"), .product(name: "SwiftParserDiagnostics", package: "swift-syntax"), diff --git a/SwiftParserCLI/Sources/swift-parser-cli/Commands/Print.swift b/SwiftParserCLI/Sources/swift-parser-cli/Commands/Print.swift new file mode 100644 index 00000000000..8f47fc99d5c --- /dev/null +++ b/SwiftParserCLI/Sources/swift-parser-cli/Commands/Print.swift @@ -0,0 +1,30 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2023 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 ArgumentParser +import SwiftParser +import SwiftSyntax + +struct Print: ParsableCommand, ParseCommand { + static var configuration = CommandConfiguration( + commandName: "print", + abstract: "Print the parsed source file after applying any other arguments" + ) + + @OptionGroup + var arguments: ParseArguments + + func run() throws { + let (tree, _) = try parsedSourceFile(wantDiagnostics: false) + print(tree.description) + } +} diff --git a/SwiftParserCLI/Sources/swift-parser-cli/Commands/PrintDiags.swift b/SwiftParserCLI/Sources/swift-parser-cli/Commands/PrintDiags.swift index 048ff69880c..a98c139ccdf 100644 --- a/SwiftParserCLI/Sources/swift-parser-cli/Commands/PrintDiags.swift +++ b/SwiftParserCLI/Sources/swift-parser-cli/Commands/PrintDiags.swift @@ -28,25 +28,18 @@ struct PrintDiags: ParsableCommand, ParseCommand { var colorize: Bool = false func run() throws { - try sourceFileContents.withUnsafeBufferPointer { sourceBuffer in - let tree = Parser.parse(source: sourceBuffer) - var diags = ParseDiagnosticsGenerator.diagnostics(for: tree) - if foldSequences { - diags += foldAllSequences(tree).1 - } - - var group = GroupedDiagnostics() - group.addSourceFile(tree: tree, displayName: sourceFileName, diagnostics: diags) - let annotatedSource = DiagnosticsFormatter.annotateSources( - in: group, - colorize: colorize || TerminalHelper.isConnectedToTerminal - ) - - print(annotatedSource) - - if diags.isEmpty { - print("No diagnostics produced") - } + let (tree, diags) = try parsedSourceFile() + var group = GroupedDiagnostics() + group.addSourceFile(tree: tree, displayName: sourceFileName, diagnostics: diags) + let annotatedSource = DiagnosticsFormatter.annotateSources( + in: group, + colorize: colorize || TerminalHelper.isConnectedToTerminal + ) + + print(annotatedSource) + + if diags.isEmpty { + print("No diagnostics produced") } } } diff --git a/SwiftParserCLI/Sources/swift-parser-cli/Commands/PrintTree.swift b/SwiftParserCLI/Sources/swift-parser-cli/Commands/PrintTree.swift index fa35da89b62..52467f8f29d 100644 --- a/SwiftParserCLI/Sources/swift-parser-cli/Commands/PrintTree.swift +++ b/SwiftParserCLI/Sources/swift-parser-cli/Commands/PrintTree.swift @@ -27,17 +27,7 @@ struct PrintTree: ParsableCommand, ParseCommand { var includeTrivia: Bool = false func run() throws { - try sourceFileContents.withUnsafeBufferPointer { sourceBuffer in - let tree = Parser.parse(source: sourceBuffer) - - let resultTree: Syntax - if foldSequences { - resultTree = foldAllSequences(tree).0 - } else { - resultTree = Syntax(tree) - } - - print(resultTree.debugDescription(includeTrivia: includeTrivia)) - } + let (tree, _) = try parsedSourceFile(wantDiagnostics: false) + print(tree.debugDescription(includeTrivia: includeTrivia)) } } diff --git a/SwiftParserCLI/Sources/swift-parser-cli/ParseCommand.swift b/SwiftParserCLI/Sources/swift-parser-cli/ParseCommand.swift index db9a5f4a7cd..61210c97108 100644 --- a/SwiftParserCLI/Sources/swift-parser-cli/ParseCommand.swift +++ b/SwiftParserCLI/Sources/swift-parser-cli/ParseCommand.swift @@ -11,6 +11,12 @@ //===----------------------------------------------------------------------===// import ArgumentParser +import Foundation +import SwiftDiagnostics +import SwiftIfConfig +import SwiftParser +import SwiftParserDiagnostics +import SwiftSyntax struct ParseArguments: ParsableArguments { @Argument(help: "The source file that should be parsed; if omitted, use stdin") @@ -21,6 +27,9 @@ struct ParseArguments: ParsableArguments { @Flag(name: .long, help: "Perform sequence folding with the standard operators") var foldSequences: Bool = false + + @Option(help: "Apply the given static build configuration to the source text before further processing") + var buildConfiguration: String? } /// A command that has arguments to parse source code @@ -50,4 +59,51 @@ extension ParseCommand { /// Whether sequence folding using standard operators should be performed var foldSequences: Bool { arguments.foldSequences } + + /// Parse the source file, applying any additional configuration options + /// such as sequence folding, and return it with diagnostics. + func parsedSourceFile( + wantDiagnostics: Bool = true + ) throws -> (SourceFileSyntax, [Diagnostic]) { + return try sourceFileContents.withUnsafeBufferPointer { sourceBuffer in + // Parse the sources + var tree = Parser.parse(source: sourceBuffer) + + // If we want diagnostics, gather them from the parser. + var diags: [Diagnostic] = [] + if wantDiagnostics { + diags += ParseDiagnosticsGenerator.diagnostics(for: tree) + } + + // If we are supposed to fold sequences, do it now. + if foldSequences { + let (folded, foldDiags) = foldAllSequences(tree) + + tree = folded.cast(SourceFileSyntax.self) + if wantDiagnostics { + diags += foldDiags + } + } + + // If we are supposed to apply a build configuration, do it now. + if let buildConfiguration = arguments.buildConfiguration { + // Load the build configuration. + let buildConfigurationText = try Data(contentsOf: URL(fileURLWithPath: buildConfiguration)) + let staticBuildConfiguration = try JSONDecoder().decode( + StaticBuildConfiguration.self, + from: buildConfigurationText + ) + + // Apply the build configuration. + let (configured, configuredDiags) = tree.removingInactive(in: staticBuildConfiguration) + + tree = configured.cast(SourceFileSyntax.self) + if wantDiagnostics { + diags += configuredDiags + } + } + + return (tree, diags) + } + } } diff --git a/SwiftParserCLI/Sources/swift-parser-cli/swift-parser-cli.swift b/SwiftParserCLI/Sources/swift-parser-cli/swift-parser-cli.swift index 21f02d924c0..e1266525973 100644 --- a/SwiftParserCLI/Sources/swift-parser-cli/swift-parser-cli.swift +++ b/SwiftParserCLI/Sources/swift-parser-cli/swift-parser-cli.swift @@ -32,6 +32,7 @@ class SwiftParserCli: ParsableCommand { subcommands: [ BasicFormat.self, PerformanceTest.self, + Print.self, PrintDiags.self, PrintTree.self, Reduce.self, diff --git a/Tests/SwiftIfConfigTest/ActiveRegionTests.swift b/Tests/SwiftIfConfigTest/ActiveRegionTests.swift index 0642823bc34..0f8f5aabbf9 100644 --- a/Tests/SwiftIfConfigTest/ActiveRegionTests.swift +++ b/Tests/SwiftIfConfigTest/ActiveRegionTests.swift @@ -20,12 +20,13 @@ import _SwiftSyntaxGenericTestSupport import _SwiftSyntaxTestSupport public class ActiveRegionTests: XCTestCase { - let linuxBuildConfig = TestingBuildConfiguration( + let linuxBuildConfig = StaticBuildConfiguration( customConditions: ["DEBUG", "ASSERTS"], features: ["ParameterPacks"], - attributes: ["available"] + attributes: ["available"], + languageVersion: VersionTuple(6), + compilerVersion: VersionTuple(6, 2) ) - func testActiveRegions() throws { try assertActiveCode( """ diff --git a/Tests/SwiftIfConfigTest/EvaluateTests.swift b/Tests/SwiftIfConfigTest/EvaluateTests.swift index 77a677977f4..849806dd218 100644 --- a/Tests/SwiftIfConfigTest/EvaluateTests.swift +++ b/Tests/SwiftIfConfigTest/EvaluateTests.swift @@ -21,7 +21,11 @@ import _SwiftSyntaxTestSupport public class EvaluateTests: XCTestCase { func testLiterals() throws { - let buildConfig = TestingBuildConfiguration(customConditions: ["DEBUG", "ASSERTS"]) + let buildConfig = StaticBuildConfiguration( + customConditions: ["DEBUG", "ASSERTS"], + languageVersion: VersionTuple(6), + compilerVersion: VersionTuple(6, 2) + ) assertIfConfig("true", .active, configuration: buildConfig) assertIfConfig("false", .inactive, configuration: buildConfig) @@ -71,7 +75,11 @@ public class EvaluateTests: XCTestCase { } func testCustomConfigs() throws { - let buildConfig = TestingBuildConfiguration(customConditions: ["DEBUG", "ASSERTS", "try"]) + let buildConfig = StaticBuildConfiguration( + customConditions: ["DEBUG", "ASSERTS", "try"], + languageVersion: VersionTuple(6), + compilerVersion: VersionTuple(6, 2) + ) assertIfConfig("DEBUG", .active, configuration: buildConfig) assertIfConfig("NODEBUG", .inactive, configuration: buildConfig) @@ -136,7 +144,11 @@ public class EvaluateTests: XCTestCase { } func testBadExpressions() throws { - let buildConfig = TestingBuildConfiguration(customConditions: ["DEBUG", "ASSERTS"]) + let buildConfig = StaticBuildConfiguration( + customConditions: ["DEBUG", "ASSERTS"], + languageVersion: VersionTuple(6), + compilerVersion: VersionTuple(6, 2) + ) assertIfConfig( "3.14159", @@ -178,14 +190,22 @@ public class EvaluateTests: XCTestCase { } func testFeatures() throws { - let buildConfig = TestingBuildConfiguration(features: ["ParameterPacks"]) + let buildConfig = StaticBuildConfiguration( + features: ["ParameterPacks"], + languageVersion: VersionTuple(6), + compilerVersion: VersionTuple(6, 2) + ) assertIfConfig("hasFeature(ParameterPacks)", .active, configuration: buildConfig) assertIfConfig("hasFeature(HigherKindedGenerics)", .inactive, configuration: buildConfig) } func testAttributes() throws { - let buildConfig = TestingBuildConfiguration(attributes: ["available"]) + let buildConfig = StaticBuildConfiguration( + attributes: ["available"], + languageVersion: VersionTuple(6), + compilerVersion: VersionTuple(6, 2) + ) assertIfConfig("hasAttribute(available)", .active, configuration: buildConfig) assertIfConfig("hasAttribute(unsafeUnavailable)", .inactive, configuration: buildConfig) @@ -533,7 +553,11 @@ public class EvaluateTests: XCTestCase { assertIfConfig( "defined(FOO)", .active, - configuration: TestingBuildConfiguration(customConditions: ["FOO"]), + configuration: StaticBuildConfiguration( + customConditions: ["FOO"], + languageVersion: VersionTuple(6), + compilerVersion: VersionTuple(6, 2) + ), diagnostics: [ DiagnosticSpec( message: message, @@ -588,6 +612,15 @@ public class EvaluateTests: XCTestCase { ] ) } + + func testStaticBuildConfigCanImport() throws { + let config = StaticBuildConfiguration( + languageVersion: VersionTuple(6), + compilerVersion: VersionTuple(6) + ) + + XCTAssertThrowsError(try config.canImport(importPath: [], version: .unversioned)) + } } /// Assert the results of evaluating the condition within an `#if` against the diff --git a/Tests/SwiftIfConfigTest/VisitorTests.swift b/Tests/SwiftIfConfigTest/VisitorTests.swift index 2c6960c7516..bcd0ea4eb12 100644 --- a/Tests/SwiftIfConfigTest/VisitorTests.swift +++ b/Tests/SwiftIfConfigTest/VisitorTests.swift @@ -45,8 +45,8 @@ class AllActiveVisitor: ActiveSyntaxAnyVisitor { } } -class NameCheckingVisitor: ActiveSyntaxAnyVisitor { - let configuration: TestingBuildConfiguration +class NameCheckingVisitor: ActiveSyntaxAnyVisitor { + let configuration: Configuration /// The set of names we are expected to visit. Any syntax nodes with /// names that aren't here will be rejected, and each of the names listed @@ -54,7 +54,7 @@ class NameCheckingVisitor: ActiveSyntaxAnyVisitor { var expectedNames: Set init( - configuration: TestingBuildConfiguration, + configuration: Configuration, expectedNames: Set, configuredRegions: ConfiguredRegions? = nil ) {