diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ExperimentalFeaturesFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/ExperimentalFeaturesFile.swift index ee22372fd29..ec8107ff149 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 { + public struct ExperimentalFeatures: OptionSet, Sendable { public let rawValue: UInt public init(rawValue: UInt) { self.rawValue = rawValue diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/LayoutNodesParsableFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/LayoutNodesParsableFile.swift index 19d4e3b90bf..ae3cca163cb 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/LayoutNodesParsableFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/LayoutNodesParsableFile.swift @@ -41,7 +41,7 @@ let layoutNodesParsableFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { defer { withExtendedLifetime(parser) {} } let node = parser.\(parserFunction)() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } """ diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/KeywordFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/KeywordFile.swift index 2218fb2c526..b9d66101534 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/KeywordFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/KeywordFile.swift @@ -24,7 +24,7 @@ let lookupTable = ArrayExprSyntax(leftSquare: .leftSquareToken(trailingTrivia: . let keywordFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { try! EnumDeclSyntax( """ - public enum Keyword: UInt8, Hashable + public enum Keyword: UInt8, Hashable, Sendable """ ) { for keyword in Keyword.allCases { diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxEnumFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxEnumFile.swift index a4c6cebe2c2..c97d1dd6519 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxEnumFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxEnumFile.swift @@ -19,7 +19,7 @@ let syntaxEnumFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { try! EnumDeclSyntax( """ /// Enum to exhaustively switch over all different syntax nodes. - public enum SyntaxEnum + public enum SyntaxEnum: Sendable """ ) { DeclSyntax("case token(TokenSyntax)") diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxKindFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxKindFile.swift index f65137ff215..c329413c27a 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxKindFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxKindFile.swift @@ -19,7 +19,7 @@ let syntaxKindFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { try! EnumDeclSyntax( """ /// Enumerates the known kinds of Syntax represented in the Syntax tree. - public enum SyntaxKind + public enum SyntaxKind: Sendable """ ) { DeclSyntax("case token") diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift index 5cafdbeac22..3be6a1bb08c 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxRewriterFile.swift @@ -50,7 +50,7 @@ let syntaxRewriterFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { } return withExtendedLifetime(rewritten) { - return Syntax(node).replacingSelf(rewritten.raw, rawNodeArena: rewritten.raw.arena, allocationArena: SyntaxArena()) + return Syntax(node).replacingSelf(rewritten.raw, rawNodeArena: rewritten.raw.arenaReference.retained, allocationArena: SyntaxArena()) } } """ diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/TokenKindFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/TokenKindFile.swift index 4fe983366b1..242e0952e81 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/TokenKindFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/TokenKindFile.swift @@ -19,7 +19,7 @@ let tokenKindFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { try! EnumDeclSyntax( """ /// Enumerates the kinds of tokens in the Swift language. - public enum TokenKind: Hashable + public enum TokenKind: Hashable, Sendable """ ) { for tokenSpec in Token.allCases.map(\.spec) { diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/TriviaPiecesFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/TriviaPiecesFile.swift index 6b1c97f8aa7..4276cd8298a 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/TriviaPiecesFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/TriviaPiecesFile.swift @@ -26,7 +26,7 @@ let triviaPiecesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { /// /// In general, you should deal with the actual Trivia collection instead /// of individual pieces whenever possible. - public enum TriviaPiece + public enum TriviaPiece: Sendable """ ) { for trivia in TRIVIAS { @@ -176,7 +176,7 @@ let triviaPiecesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { /// In contrast to ``TriviaPiece``, a ``RawTriviaPiece`` does not own the source /// text of the trivia. @_spi(RawSyntax) - public enum RawTriviaPiece: Equatable + public enum RawTriviaPiece: Equatable, Sendable """ ) { for trivia in TRIVIAS { diff --git a/Sources/SwiftDiagnostics/Diagnostic.swift b/Sources/SwiftDiagnostics/Diagnostic.swift index 939bfea6d57..80ff1954b55 100644 --- a/Sources/SwiftDiagnostics/Diagnostic.swift +++ b/Sources/SwiftDiagnostics/Diagnostic.swift @@ -12,7 +12,7 @@ import SwiftSyntax -public struct Diagnostic: CustomDebugStringConvertible { +public struct Diagnostic: CustomDebugStringConvertible, Sendable { /// The message that should be displayed to the user public let diagMessage: DiagnosticMessage @@ -74,7 +74,7 @@ public struct Diagnostic: CustomDebugStringConvertible { } } -public struct DiagnosticsError: Error { +public struct DiagnosticsError: Error, Sendable { public var diagnostics: [Diagnostic] /// The diagnostics must contain at least one with severity == `.error`. diff --git a/Sources/SwiftDiagnostics/FixIt.swift b/Sources/SwiftDiagnostics/FixIt.swift index f9664e1d48c..df6b1021402 100644 --- a/Sources/SwiftDiagnostics/FixIt.swift +++ b/Sources/SwiftDiagnostics/FixIt.swift @@ -15,7 +15,7 @@ import SwiftSyntax /// Types conforming to this protocol represent Fix-It messages that can be /// shown to the client. /// The messages should describe the change that the Fix-It will perform -public protocol FixItMessage { +public protocol FixItMessage: Sendable { /// The Fix-It message that should be displayed in the client. var message: String { get } @@ -24,8 +24,8 @@ public protocol FixItMessage { } /// A Fix-It that can be applied to resolve a diagnostic. -public struct FixIt { - public enum Change { +public struct FixIt: Sendable { + public enum Change: Sendable { /// Replace `oldNode` by `newNode`. case replace(oldNode: Syntax, newNode: Syntax) /// Replace the leading trivia on the given token diff --git a/Sources/SwiftDiagnostics/Message.swift b/Sources/SwiftDiagnostics/Message.swift index 2bb6a905ced..ea2451fdc3f 100644 --- a/Sources/SwiftDiagnostics/Message.swift +++ b/Sources/SwiftDiagnostics/Message.swift @@ -35,7 +35,7 @@ public enum DiagnosticSeverity: Sendable { /// Types conforming to this protocol represent diagnostic messages that can be /// shown to the client. -public protocol DiagnosticMessage { +public protocol DiagnosticMessage: Sendable { /// The diagnostic message that should be displayed in the client. var message: String { get } diff --git a/Sources/SwiftDiagnostics/Note.swift b/Sources/SwiftDiagnostics/Note.swift index 5a868da2ae5..c1f25e96c5c 100644 --- a/Sources/SwiftDiagnostics/Note.swift +++ b/Sources/SwiftDiagnostics/Note.swift @@ -15,7 +15,7 @@ import SwiftSyntax /// Types conforming to this protocol represent note messages that can be /// shown to the client. /// The messages should describe what the note is pointing at. -public protocol NoteMessage { +public protocol NoteMessage: Sendable { /// The message that should be displayed in the client. var message: String { get } @@ -31,7 +31,7 @@ extension NoteMessage { } /// A note that points to another node that's relevant for a Diagnostic. -public struct Note: CustomDebugStringConvertible { +public struct Note: CustomDebugStringConvertible, Sendable { /// The node whose location the node is pointing. public let node: Syntax diff --git a/Sources/SwiftIDEUtils/SyntaxClassification.swift b/Sources/SwiftIDEUtils/SyntaxClassification.swift index 9d3cc87719a..fd66e58e41b 100644 --- a/Sources/SwiftIDEUtils/SyntaxClassification.swift +++ b/Sources/SwiftIDEUtils/SyntaxClassification.swift @@ -12,7 +12,7 @@ @_spi(RawSyntax) import SwiftSyntax -public enum SyntaxClassification { +public enum SyntaxClassification: Sendable { /// An attribute starting with an `@`. case attribute /// A block comment starting with `/**` and ending with `*/. diff --git a/Sources/SwiftIDEUtils/SyntaxClassifier.swift b/Sources/SwiftIDEUtils/SyntaxClassifier.swift index 3f3d5333433..ca7f3b8fdd1 100644 --- a/Sources/SwiftIDEUtils/SyntaxClassifier.swift +++ b/Sources/SwiftIDEUtils/SyntaxClassifier.swift @@ -85,7 +85,7 @@ fileprivate struct TokenKindAndText { } /// Represents a source range that is associated with a syntax classification. -public struct SyntaxClassifiedRange: Equatable { +public struct SyntaxClassifiedRange: Equatable, Sendable { public var kind: SyntaxClassification public var range: ByteSourceRange @@ -264,7 +264,7 @@ private struct ClassificationVisitor { } /// Provides a sequence of ``SyntaxClassifiedRange``s for a syntax node. -public struct SyntaxClassifications: Sequence { +public struct SyntaxClassifications: Sequence, Sendable { public typealias Iterator = Array.Iterator var classifications: [SyntaxClassifiedRange] diff --git a/Sources/SwiftOperators/Operator.swift b/Sources/SwiftOperators/Operator.swift index fde1e65bf80..7756c850b47 100644 --- a/Sources/SwiftOperators/Operator.swift +++ b/Sources/SwiftOperators/Operator.swift @@ -19,7 +19,7 @@ import SwiftSyntax public typealias OperatorName = String /// Describes the kind of an operator. -public enum OperatorKind: String { +public enum OperatorKind: String, Sendable { /// Infix operator such as the + in a + b. case infix @@ -31,7 +31,7 @@ public enum OperatorKind: String { } /// Describes an operator. -public struct Operator { +public struct Operator: Sendable { public let kind: OperatorKind public let name: OperatorName public let precedenceGroup: PrecedenceGroupName? diff --git a/Sources/SwiftOperators/OperatorError.swift b/Sources/SwiftOperators/OperatorError.swift index 37ab188e7b6..9136ab605ae 100644 --- a/Sources/SwiftOperators/OperatorError.swift +++ b/Sources/SwiftOperators/OperatorError.swift @@ -12,7 +12,7 @@ import SwiftSyntax /// Describes errors that can occur when working with user-defined operators. -public enum OperatorError: Error { +public enum OperatorError: Error, Sendable { /// Error produced when a given precedence group already exists in the /// precedence graph. case groupAlreadyExists(existing: PrecedenceGroup, new: PrecedenceGroup) diff --git a/Sources/SwiftOperators/OperatorTable.swift b/Sources/SwiftOperators/OperatorTable.swift index 5ff34872e5b..7b6d0ad7af8 100644 --- a/Sources/SwiftOperators/OperatorTable.swift +++ b/Sources/SwiftOperators/OperatorTable.swift @@ -19,7 +19,7 @@ import SwiftSyntax /// semantic representation, validating the correctness of those declarations, /// and "folding" sequence expression syntax into a structured expression /// syntax tree. -public struct OperatorTable { +public struct OperatorTable: Sendable { var precedenceGraph: PrecedenceGraph = .init() var infixOperators: [OperatorName: Operator] = [:] var prefixOperators: [OperatorName: Operator] = [:] diff --git a/Sources/SwiftOperators/PrecedenceGraph.swift b/Sources/SwiftOperators/PrecedenceGraph.swift index d8940256d80..10be03b312e 100644 --- a/Sources/SwiftOperators/PrecedenceGraph.swift +++ b/Sources/SwiftOperators/PrecedenceGraph.swift @@ -13,7 +13,7 @@ import SwiftSyntax /// Describes the relative precedence of two groups. -enum Precedence { +enum Precedence: Sendable { case unrelated case higherThan case lowerThan @@ -35,7 +35,7 @@ enum Precedence { /// A graph formed from a set of precedence groups, which can be used to /// determine the relative precedence of two precedence groups. -struct PrecedenceGraph { +struct PrecedenceGraph: Sendable { /// The known set of precedence groups, found by name. var precedenceGroups: [PrecedenceGroupName: PrecedenceGroup] = [:] diff --git a/Sources/SwiftOperators/PrecedenceGroup.swift b/Sources/SwiftOperators/PrecedenceGroup.swift index be175c749d8..33d52d7fcb6 100644 --- a/Sources/SwiftOperators/PrecedenceGroup.swift +++ b/Sources/SwiftOperators/PrecedenceGroup.swift @@ -18,7 +18,7 @@ import SwiftSyntax public typealias PrecedenceGroupName = String /// The associativity of a precedence group. -public enum Associativity: String { +public enum Associativity: String, Sendable { /// The precedence group is nonassociative, meaning that one must /// parenthesize when there are multiple operators in a sequence, e.g., /// if ^ was nonassociative, a ^ b ^ c would need to be disambiguated as @@ -38,9 +38,9 @@ public enum Associativity: String { /// Describes the relationship of a precedence group to another precedence /// group. -public struct PrecedenceRelation { +public struct PrecedenceRelation: Sendable { /// Describes the kind of a precedence relation. - public enum Kind { + public enum Kind: Sendable { case higherThan case lowerThan @@ -93,7 +93,7 @@ public struct PrecedenceRelation { /// precedence, e.g., /// /// infix operator *: MultiplicationPrecedence -public struct PrecedenceGroup { +public struct PrecedenceGroup: Sendable { /// The name of the group, which must be unique. public var name: PrecedenceGroupName diff --git a/Sources/SwiftParser/CollectionNodes+Parsable.swift b/Sources/SwiftParser/CollectionNodes+Parsable.swift index 900607b0e12..62b9a270de6 100644 --- a/Sources/SwiftParser/CollectionNodes+Parsable.swift +++ b/Sources/SwiftParser/CollectionNodes+Parsable.swift @@ -45,7 +45,7 @@ fileprivate extension SyntaxCollection { } else { // First unwrap: We know that children.last exists because children is not empty // Second unwrap: This is a collection and collections never have optional children. Thus the last child can’t be nil. - let lastWithRemainder = parser.parseRemainder(into: layoutView.children.last!!) + let lastWithRemainder = parser.parseRemainder(into: layoutView.children[layoutView.children.count - 1]!) let raw = layoutView.replacingChild(at: layoutView.children.count - 1, with: lastWithRemainder, arena: parser.arena) return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } diff --git a/Sources/SwiftParser/IncrementalParseTransition.swift b/Sources/SwiftParser/IncrementalParseTransition.swift index 6af928f558a..d7fba721036 100644 --- a/Sources/SwiftParser/IncrementalParseTransition.swift +++ b/Sources/SwiftParser/IncrementalParseTransition.swift @@ -283,7 +283,7 @@ fileprivate struct SyntaxCursor { /// The raw `edits` of this struct are guaranteed to /// 1. not be overlapping. /// 2. be in increasing source offset order. -public struct ConcurrentEdits { +public struct ConcurrentEdits: Sendable { enum ConcurrentEditsError: Error, CustomStringConvertible { case editsNotConcurrent diff --git a/Sources/SwiftParser/ParseSourceFile.swift b/Sources/SwiftParser/ParseSourceFile.swift index 6b0a2149fa2..e2cd32aa9dc 100644 --- a/Sources/SwiftParser/ParseSourceFile.swift +++ b/Sources/SwiftParser/ParseSourceFile.swift @@ -132,7 +132,7 @@ extension Parser { /// /// This contains the parsed syntax tree and additional information on how far the parser looked ahead to parse each node. /// This information is required to perform an incremental parse of the tree after applying edits to it. -public struct IncrementalParseResult { +public struct IncrementalParseResult: Sendable { /// The syntax tree from parsing source public let tree: SourceFileSyntax /// The lookahead ranges for syntax nodes describe diff --git a/Sources/SwiftParser/Parser.swift b/Sources/SwiftParser/Parser.swift index e92d0e31e2f..7c0814d6923 100644 --- a/Sources/SwiftParser/Parser.swift +++ b/Sources/SwiftParser/Parser.swift @@ -850,7 +850,7 @@ class LookaheadTrackerOwner { } /// Record the lookahead ranges for syntax nodes. -public struct LookaheadRanges { +public struct LookaheadRanges: Sendable { /// For each node that is recorded for re-use, the number of UTF-8 bytes that the parser looked ahead to parse the node, measured from the start of the node’s leading trivia. /// /// This information can be used to determine whether a node can be reused in incremental parse. A node can only be re-used if no byte in its looked range has changed. diff --git a/Sources/SwiftParser/generated/ExperimentalFeatures.swift b/Sources/SwiftParser/generated/ExperimentalFeatures.swift index 40c9d7fa21d..d6616ae9697 100644 --- a/Sources/SwiftParser/generated/ExperimentalFeatures.swift +++ b/Sources/SwiftParser/generated/ExperimentalFeatures.swift @@ -14,7 +14,7 @@ extension Parser { @_spi(ExperimentalLanguageFeatures) - public struct ExperimentalFeatures: OptionSet { + public struct ExperimentalFeatures: OptionSet, Sendable { public let rawValue: UInt public init(rawValue: UInt) { diff --git a/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift b/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift index 477650b20b2..a0519f04c95 100644 --- a/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift +++ b/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift @@ -32,7 +32,7 @@ extension AccessorBlockSyntax: SyntaxParseable { } let node = parser.parseAccessorBlock() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -50,7 +50,7 @@ extension AccessorDeclSyntax: SyntaxParseable { } let node = parser.parseAccessorDecl() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -68,7 +68,7 @@ extension AttributeSyntax: SyntaxParseable { } let node = parser.parseAttribute() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -86,7 +86,7 @@ extension CatchClauseSyntax: SyntaxParseable { } let node = parser.parseCatchClause() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -104,7 +104,7 @@ extension ClosureParameterSyntax: SyntaxParseable { } let node = parser.parseClosureParameter() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -122,7 +122,7 @@ extension CodeBlockItemSyntax: SyntaxParseable { } let node = parser.parseNonOptionalCodeBlockItem() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -140,7 +140,7 @@ extension CodeBlockSyntax: SyntaxParseable { } let node = parser.parseCodeBlock() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -158,7 +158,7 @@ extension DeclSyntax: SyntaxParseable { } let node = parser.parseDeclaration() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -176,7 +176,7 @@ extension EnumCaseParameterSyntax: SyntaxParseable { } let node = parser.parseEnumCaseParameter() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -194,7 +194,7 @@ extension ExprSyntax: SyntaxParseable { } let node = parser.parseExpression() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -212,7 +212,7 @@ extension FunctionParameterSyntax: SyntaxParseable { } let node = parser.parseFunctionParameter() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -230,7 +230,7 @@ extension GenericParameterClauseSyntax: SyntaxParseable { } let node = parser.parseGenericParameters() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -248,7 +248,7 @@ extension MemberBlockSyntax: SyntaxParseable { } let node = parser.parseMemberBlock() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -266,7 +266,7 @@ extension PatternSyntax: SyntaxParseable { } let node = parser.parsePattern() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -284,7 +284,7 @@ extension SourceFileSyntax: SyntaxParseable { } let node = parser.parseSourceFile() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -302,7 +302,7 @@ extension StmtSyntax: SyntaxParseable { } let node = parser.parseStatement() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -320,7 +320,7 @@ extension SwitchCaseSyntax: SyntaxParseable { } let node = parser.parseSwitchCase() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } @@ -338,7 +338,7 @@ extension TypeSyntax: SyntaxParseable { } let node = parser.parseType() let raw = RawSyntax(parser.parseRemainder(into: node)) - return Syntax(raw: raw, rawNodeArena: raw.arena).cast(Self.self) + return Syntax(raw: raw, rawNodeArena: parser.arena).cast(Self.self) } } diff --git a/Sources/SwiftParserDiagnostics/LexerDiagnosticMessages.swift b/Sources/SwiftParserDiagnostics/LexerDiagnosticMessages.swift index f4f4e8cf47d..3fa4c124828 100644 --- a/Sources/SwiftParserDiagnostics/LexerDiagnosticMessages.swift +++ b/Sources/SwiftParserDiagnostics/LexerDiagnosticMessages.swift @@ -86,7 +86,7 @@ public enum StaticTokenWarning: String, DiagnosticMessage { } public struct InvalidFloatingPointExponentDigit: TokenError { - public enum Kind { + public enum Kind: Sendable { case digit(Unicode.Scalar) case character(Unicode.Scalar) } @@ -103,7 +103,7 @@ public struct InvalidFloatingPointExponentDigit: TokenError { } public struct InvalidDigitInIntegerLiteral: TokenError { - public enum Kind { + public enum Kind: Sendable { case binary(Unicode.Scalar) case octal(Unicode.Scalar) case decimal(Unicode.Scalar) diff --git a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift index 75955a37219..1baba86846c 100644 --- a/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift +++ b/Sources/SwiftParserDiagnostics/ParserDiagnosticMessages.swift @@ -409,7 +409,7 @@ public struct InvalidIdentifierError: ParserError { } public struct InvalidIndentationInMultiLineStringLiteralError: ParserError { - public enum Kind { + public enum Kind: Sendable { case insufficientIndentation case unexpectedSpace case unexpectedTab diff --git a/Sources/SwiftSyntax/AbsolutePosition.swift b/Sources/SwiftSyntax/AbsolutePosition.swift index 3cfbad6456f..bd2ce4efe9d 100644 --- a/Sources/SwiftSyntax/AbsolutePosition.swift +++ b/Sources/SwiftSyntax/AbsolutePosition.swift @@ -12,7 +12,7 @@ /// An absolute position in a source file as text - the absolute utf8Offset from /// the start of the file. -public struct AbsolutePosition: Comparable, Hashable { +public struct AbsolutePosition: Comparable, Hashable, Sendable { public let utf8Offset: Int static let startOfFile = AbsolutePosition(utf8Offset: 0) diff --git a/Sources/SwiftSyntax/AbsoluteRawSyntax.swift b/Sources/SwiftSyntax/AbsoluteRawSyntax.swift index 42b9b74e5a1..a3f5aec25e7 100644 --- a/Sources/SwiftSyntax/AbsoluteRawSyntax.swift +++ b/Sources/SwiftSyntax/AbsoluteRawSyntax.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -struct AbsoluteRawSyntax { +struct AbsoluteRawSyntax: Sendable { let raw: RawSyntax let info: AbsoluteSyntaxInfo diff --git a/Sources/SwiftSyntax/AbsoluteSyntaxInfo.swift b/Sources/SwiftSyntax/AbsoluteSyntaxInfo.swift index 3f5e9eee868..0d281f63a8a 100644 --- a/Sources/SwiftSyntax/AbsoluteSyntaxInfo.swift +++ b/Sources/SwiftSyntax/AbsoluteSyntaxInfo.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -struct AbsoluteSyntaxPosition { +struct AbsoluteSyntaxPosition: Sendable { /// The UTF-8 offset of the syntax node in the source file let offset: UInt32 let indexInParent: UInt32 @@ -32,7 +32,7 @@ struct AbsoluteSyntaxPosition { /// `AbsoluteSyntaxInfo` represents the information that relates a `RawSyntax` /// to a source file tree, like its absolute source offset. -struct AbsoluteSyntaxInfo { +struct AbsoluteSyntaxInfo: Sendable { let position: AbsoluteSyntaxPosition let nodeId: SyntaxIdentifier diff --git a/Sources/SwiftSyntax/CMakeLists.txt b/Sources/SwiftSyntax/CMakeLists.txt index 89013d99ee9..7fdd8573031 100644 --- a/Sources/SwiftSyntax/CMakeLists.txt +++ b/Sources/SwiftSyntax/CMakeLists.txt @@ -23,6 +23,7 @@ add_swift_syntax_library(SwiftSyntax SwiftSyntaxCompatibility.swift Syntax.swift SyntaxArena.swift + SyntaxArenaAllocatedBuffer.swift SyntaxChildren.swift SyntaxCollection.swift SyntaxHashable.swift diff --git a/Sources/SwiftSyntax/MemoryLayout.swift b/Sources/SwiftSyntax/MemoryLayout.swift index 17f5d73b3ee..e4666e52dd1 100644 --- a/Sources/SwiftSyntax/MemoryLayout.swift +++ b/Sources/SwiftSyntax/MemoryLayout.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// // See `MemoryLayoutTest.swift`. -@_spi(Testing) public enum SyntaxMemoryLayout { +@_spi(Testing) public enum SyntaxMemoryLayout: Sendable { public struct Value: Equatable, Sendable { var size: Int var stride: Int diff --git a/Sources/SwiftSyntax/Raw/RawSyntax.swift b/Sources/SwiftSyntax/Raw/RawSyntax.swift index 583de2c3298..7a48f01f4c8 100644 --- a/Sources/SwiftSyntax/Raw/RawSyntax.swift +++ b/Sources/SwiftSyntax/Raw/RawSyntax.swift @@ -10,8 +10,9 @@ // //===----------------------------------------------------------------------===// -@_spi(RawSyntax) public typealias RawSyntaxBuffer = UnsafeBufferPointer -typealias RawTriviaPieceBuffer = UnsafeBufferPointer +@_spi(RawSyntax) public typealias RawSyntaxBuffer = SyntaxArenaAllocatedBufferPointer + +typealias RawTriviaPieceBuffer = SyntaxArenaAllocatedBufferPointer fileprivate extension SyntaxKind { /// Whether this node kind should be considered as `hasError` for purposes of `RecursiveRawSyntaxFlags`. @@ -20,7 +21,7 @@ fileprivate extension SyntaxKind { } } -struct RecursiveRawSyntaxFlags: OptionSet { +struct RecursiveRawSyntaxFlags: OptionSet, Sendable { let rawValue: UInt8 /// Whether the tree contained by this layout has any @@ -36,8 +37,10 @@ struct RecursiveRawSyntaxFlags: OptionSet { } /// Node data for RawSyntax tree. Tagged union plus common data. -internal struct RawSyntaxData { - internal enum Payload { +internal struct RawSyntaxData: Sendable { + internal enum Payload: Sendable { + /// - Important: A raw syntax node for a parsed token must always be allocated in a `ParsingSyntaxArena` so we can + /// parse the trivia in the token. case parsedToken(ParsedToken) case materializedToken(MaterializedToken) case layout(Layout) @@ -47,7 +50,7 @@ internal struct RawSyntaxData { /// /// The RawSyntax's `arena` must have a valid trivia parsing function to /// lazily materialize the leading/trailing trivia pieces. - struct ParsedToken { + struct ParsedToken: Sendable { var tokenKind: RawTokenKind /// Whole text of this token including leading/trailing trivia. @@ -97,7 +100,7 @@ internal struct RawSyntaxData { } /// Token typically created with `TokenSyntax.`. - struct MaterializedToken { + struct MaterializedToken: Sendable { var tokenKind: RawTokenKind var tokenText: SyntaxText var triviaPieces: RawTriviaPieceBuffer @@ -150,7 +153,7 @@ internal struct RawSyntaxData { } /// Layout node including collections. - struct Layout { + struct Layout: Sendable { var kind: SyntaxKind var layout: RawSyntaxBuffer var byteLength: Int @@ -177,10 +180,10 @@ extension RawSyntaxData.ParsedToken { extension RawSyntaxData.MaterializedToken { var leadingTrivia: RawTriviaPieceBuffer { - RawTriviaPieceBuffer(rebasing: triviaPieces[.. - init(pointer: UnsafePointer) { + var pointer: SyntaxArenaAllocatedPointer + init(pointer: SyntaxArenaAllocatedPointer) { self.pointer = pointer } init(arena: __shared SyntaxArena, payload: RawSyntaxData.Payload) { let arenaRef = SyntaxArenaRef(arena) - self.init(pointer: arena.intern(RawSyntaxData(payload: payload, arenaReference: arenaRef))) + let data = RawSyntaxData( + payload: payload, + arenaReference: arenaRef + ) + self.init(pointer: SyntaxArenaAllocatedPointer(arena.intern(data))) } var rawData: RawSyntaxData { - unsafeAddress { pointer } + pointer.pointee } internal var arenaReference: SyntaxArenaRef { rawData.arenaReference } - @_spi(RawSyntax) - public var arena: SyntaxArena { - rawData.arenaReference.value - } - internal var payload: RawSyntaxData.Payload { get { rawData.payload } } @@ -350,18 +352,6 @@ extension RawSyntax { } } -extension RawSyntax { - @_spi(RawSyntax) - public func toOpaque() -> UnsafeRawPointer { - UnsafeRawPointer(pointer) - } - - @_spi(RawSyntax) - public static func fromOpaque(_ pointer: UnsafeRawPointer) -> RawSyntax { - Self(pointer: pointer.assumingMemoryBound(to: RawSyntaxData.self)) - } -} - extension RawTriviaPiece { func withSyntaxText(body: (SyntaxText) throws -> Void) rethrows { if let syntaxText = storedText { @@ -555,16 +545,12 @@ extension RawSyntax { textRange: Range, presence: SourcePresence, tokenDiagnostic: TokenDiagnostic?, - arena: __shared SyntaxArena + arena: __shared ParsingSyntaxArena ) -> RawSyntax { assert( arena.contains(text: wholeText), "token text must be managed by the arena" ) - assert( - arena is ParsingSyntaxArena || textRange == wholeText.indices, - "arena must be able to parse trivia" - ) let payload = RawSyntaxData.ParsedToken( tokenKind: kind, wholeText: wholeText, @@ -649,7 +635,7 @@ extension RawSyntax { return .materializedToken( kind: kind, text: text, - triviaPieces: RawTriviaPieceBuffer(triviaBuffer), + triviaPieces: RawTriviaPieceBuffer(UnsafeBufferPointer(triviaBuffer)), numLeadingTrivia: numericCast(leadingTriviaPieceCount), byteLength: numericCast(byteLength), presence: presence, @@ -711,7 +697,7 @@ extension RawSyntax { return .materializedToken( kind: rawKind, text: rawKind.defaultText ?? "", - triviaPieces: .init(start: nil, count: 0), + triviaPieces: RawTriviaPieceBuffer(), numLeadingTrivia: 0, byteLength: 0, presence: .missing, @@ -792,7 +778,7 @@ extension RawSyntax { } return .layout( kind: kind, - layout: RawSyntaxBuffer(layoutBuffer), + layout: RawSyntaxBuffer(UnsafeBufferPointer(layoutBuffer)), byteLength: byteLength, descendantCount: descendantCount, recursiveFlags: recursiveFlags, @@ -810,7 +796,7 @@ extension RawSyntax { } return .layout( kind: kind, - layout: .init(start: nil, count: 0), + layout: RawSyntaxBuffer(), byteLength: 0, descendantCount: 0, recursiveFlags: recursiveFlags, @@ -931,11 +917,11 @@ extension RawSyntax { } extension RawSyntax: Identifiable { - public struct ID: Hashable { + public struct ID: Hashable, @unchecked Sendable { /// The pointer to the start of the `RawSyntax` node. private var pointer: UnsafeRawPointer fileprivate init(_ raw: RawSyntax) { - self.pointer = UnsafeRawPointer(raw.pointer) + self.pointer = raw.pointer.unsafeRawPointer } } diff --git a/Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift b/Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift index 23779d414c5..e1ef8bd9c5d 100644 --- a/Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift +++ b/Sources/SwiftSyntax/Raw/RawSyntaxNodeProtocol.swift @@ -13,7 +13,7 @@ /// All typed raw syntax nodes conform to this protocol. /// `RawXXXSyntax` is a typed wrappeer of ``RawSyntax``. @_spi(RawSyntax) -public protocol RawSyntaxNodeProtocol: CustomStringConvertible, TextOutputStreamable { +public protocol RawSyntaxNodeProtocol: CustomStringConvertible, TextOutputStreamable, Sendable { /// Returns `true` if `raw` can be cast to this concrete raw syntax type. static func isKindOf(_ raw: RawSyntax) -> Bool @@ -183,7 +183,7 @@ public struct RawTokenSyntax: RawSyntaxNodeProtocol { textRange: Range, presence: SourcePresence, tokenDiagnostic: TokenDiagnostic?, - arena: __shared SyntaxArena + arena: __shared ParsingSyntaxArena ) { let raw = RawSyntax.parsedToken( kind: kind, @@ -205,7 +205,7 @@ public struct RawTokenSyntax: RawSyntaxNodeProtocol { trailingTriviaPieces: [RawTriviaPiece] = [], presence: SourcePresence, tokenDiagnostic: TokenDiagnostic? = nil, - arena: __shared SyntaxArena + arena: __shared ParsingSyntaxArena ) { if leadingTriviaPieces.isEmpty && trailingTriviaPieces.isEmpty { // Create it via `RawSyntax.parsedToken()`. diff --git a/Sources/SwiftSyntax/Raw/RawSyntaxTokenView.swift b/Sources/SwiftSyntax/Raw/RawSyntaxTokenView.swift index 6f732f6aa05..61841ddd55b 100644 --- a/Sources/SwiftSyntax/Raw/RawSyntaxTokenView.swift +++ b/Sources/SwiftSyntax/Raw/RawSyntaxTokenView.swift @@ -26,7 +26,7 @@ extension RawSyntax { /// A view into ``RawSyntax`` that exposes functionality that only applies to tokens. @_spi(RawSyntax) -public struct RawSyntaxTokenView { +public struct RawSyntaxTokenView: Sendable { let raw: RawSyntax fileprivate init(raw: RawSyntax) { @@ -95,8 +95,7 @@ public struct RawSyntaxTokenView { public var leadingRawTriviaPieces: [RawTriviaPiece] { switch raw.rawData.payload { case .parsedToken(let dat): - let arena = raw.arena.parsingArena! - return arena.parseTrivia(source: dat.leadingTriviaText, position: .leading) + return raw.arenaReference.parseTrivia(source: dat.leadingTriviaText, position: .leading) case .materializedToken(let dat): return Array(dat.leadingTrivia) case .layout(_): @@ -108,8 +107,7 @@ public struct RawSyntaxTokenView { public var trailingRawTriviaPieces: [RawTriviaPiece] { switch raw.rawData.payload { case .parsedToken(let dat): - let arena = raw.arena.parsingArena! - return arena.parseTrivia(source: dat.trailingTriviaText, position: .trailing) + return raw.arenaReference.parseTrivia(source: dat.trailingTriviaText, position: .trailing) case .materializedToken(let dat): return Array(dat.trailingTrivia) case .layout(_): @@ -204,8 +202,21 @@ public struct RawSyntaxTokenView { arena.addChild(self.raw.arenaReference) switch raw.rawData.payload { case .parsedToken(var payload): - payload.presence = newValue - return RawSyntax(arena: arena, payload: .parsedToken(payload)) + if arena == self.raw.arenaReference { + payload.presence = newValue + return RawSyntax(arena: arena, payload: .parsedToken(payload)) + } + // If the modified token is allocated in a different arena, it might have + // a different or no `parseTrivia` function. We thus cannot use a + // `parsedToken` anymore. + return .makeMaterializedToken( + kind: formKind(), + leadingTrivia: formLeadingTrivia(), + trailingTrivia: formTrailingTrivia(), + presence: newValue, + tokenDiagnostic: tokenDiagnostic, + arena: arena + ) case .materializedToken(var payload): payload.presence = newValue return RawSyntax(arena: arena, payload: .materializedToken(payload)) @@ -274,8 +285,21 @@ public struct RawSyntaxTokenView { arena.addChild(self.raw.arenaReference) switch raw.rawData.payload { case .parsedToken(var dat): - dat.tokenDiagnostic = tokenDiagnostic - return RawSyntax(arena: arena, payload: .parsedToken(dat)) + if arena == self.raw.arenaReference { + dat.tokenDiagnostic = tokenDiagnostic + return RawSyntax(arena: arena, payload: .parsedToken(dat)) + } + // If the modified token is allocated in a different arena, it might have + // a different or no `parseTrivia` function. We thus cannot use a + // `parsedToken` anymore. + return .makeMaterializedToken( + kind: formKind(), + leadingTrivia: formLeadingTrivia(), + trailingTrivia: formTrailingTrivia(), + presence: presence, + tokenDiagnostic: tokenDiagnostic, + arena: arena + ) case .materializedToken(var dat): dat.tokenDiagnostic = tokenDiagnostic return RawSyntax(arena: arena, payload: .materializedToken(dat)) diff --git a/Sources/SwiftSyntax/SourceEdit.swift b/Sources/SwiftSyntax/SourceEdit.swift index eee47873586..58b7dd03f4f 100644 --- a/Sources/SwiftSyntax/SourceEdit.swift +++ b/Sources/SwiftSyntax/SourceEdit.swift @@ -12,7 +12,7 @@ /// A textual edit to the original source represented by a range and a /// replacement. -public struct SourceEdit: Equatable { +public struct SourceEdit: Equatable, Sendable { /// The half-open range that this edit applies to. public let range: Range /// The text to replace the original range with. Empty for a deletion. diff --git a/Sources/SwiftSyntax/SourceLength.swift b/Sources/SwiftSyntax/SourceLength.swift index 46e88e38521..c5f13b61702 100644 --- a/Sources/SwiftSyntax/SourceLength.swift +++ b/Sources/SwiftSyntax/SourceLength.swift @@ -12,7 +12,7 @@ /// The length a syntax node spans in the source code. From any AbsolutePosition /// you reach a node's end location by adding its UTF-8 length. -public struct SourceLength: Comparable { +public struct SourceLength: Comparable, Sendable { /// The length in bytes when the text is represented as UTF-8. public let utf8Length: Int diff --git a/Sources/SwiftSyntax/SourceLocation.swift b/Sources/SwiftSyntax/SourceLocation.swift index 95a9de62f39..92cd90bbbf1 100644 --- a/Sources/SwiftSyntax/SourceLocation.swift +++ b/Sources/SwiftSyntax/SourceLocation.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// /// Represents a source location in a Swift file. -public struct SourceLocation: Hashable, Codable { +public struct SourceLocation: Hashable, Codable, Sendable { /// The line in the file where this location resides. 1-based. /// @@ -82,7 +82,7 @@ public struct SourceLocation: Hashable, Codable { } /// Represents a half-open range in a Swift file. -public struct SourceRange: Hashable, Codable { +public struct SourceRange: Hashable, Codable, Sendable { /// The beginning location of the source range. /// @@ -235,7 +235,11 @@ public final class SourceLocationConverter { self.fileName = file self.source = Array(source.utf8) (self.lines, endOfFile) = self.source.withUnsafeBufferPointer { buf in - return computeLines(SyntaxText(buffer: buf)) + // Technically, `buf` is not allocated in a `SyntaxArena` but it satisfies + // all the required properties: `buf` will always outlive any references + // to it. + let syntaxArenaBuf = SyntaxArenaAllocatedBufferPointer(buf) + return computeLines(SyntaxText(buffer: syntaxArenaBuf)) } precondition(source.utf8.count == endOfFile.utf8Offset) } diff --git a/Sources/SwiftSyntax/SourcePresence.swift b/Sources/SwiftSyntax/SourcePresence.swift index 9dbf122e25a..2c4c2c4c13a 100644 --- a/Sources/SwiftSyntax/SourcePresence.swift +++ b/Sources/SwiftSyntax/SourcePresence.swift @@ -14,7 +14,7 @@ /// /// A `missing` node does not mean, necessarily, that the source item is /// considered "implicit", but rather that it was not found in the source. -public enum SourcePresence { +public enum SourcePresence: Sendable { /// The syntax was authored by a human and found, or was generated. case present diff --git a/Sources/SwiftSyntax/Syntax.swift b/Sources/SwiftSyntax/Syntax.swift index 1e858f1e02d..4fce60526fe 100644 --- a/Sources/SwiftSyntax/Syntax.swift +++ b/Sources/SwiftSyntax/Syntax.swift @@ -14,17 +14,21 @@ /// Each node has accessors for its known children, and allows efficient /// iteration over the children through its `children` property. public struct Syntax: SyntaxProtocol, SyntaxHashable { - fileprivate enum Info { + fileprivate enum Info: Sendable { case root(Root) indirect case nonRoot(NonRoot) // For root node. - struct Root { - var arena: SyntaxArena + struct Root: Sendable { + private var arena: RetainedSyntaxArena + + init(arena: RetainedSyntaxArena) { + self.arena = arena + } } // For non-root nodes. - struct NonRoot { + struct NonRoot: Sendable { var parent: Syntax var absoluteInfo: AbsoluteSyntaxInfo } @@ -119,11 +123,16 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable { /// - rawNodeArena: The arena in which `raw` is allocated. It is passed to /// make sure the arena doesn’t get de-allocated before the ``Syntax`` /// has a chance to retain it. - static func forRoot(_ raw: RawSyntax, rawNodeArena: SyntaxArena) -> Syntax { - precondition(rawNodeArena === raw.arena) + static func forRoot(_ raw: RawSyntax, rawNodeArena: RetainedSyntaxArena) -> Syntax { + precondition(rawNodeArena == raw.arenaReference) return Syntax(raw, info: .root(.init(arena: rawNodeArena))) } + static func forRoot(_ raw: RawSyntax, rawNodeArena: SyntaxArena) -> Syntax { + precondition(rawNodeArena == raw.arenaReference) + return Syntax(raw, info: .root(.init(arena: RetainedSyntaxArena(rawNodeArena)))) + } + /// Returns the child data at the provided index in this data's layout. /// - Note: This has O(n) performance, prefer using a proper Sequence type /// if applicable, instead of this. @@ -151,8 +160,8 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable { /// - allocationArena: The arena in which new nodes should be allocated /// - Returns: A syntax tree with all parents where this node has been /// replaced by `newRaw` - func replacingSelf(_ newRaw: RawSyntax, rawNodeArena: SyntaxArena, allocationArena: SyntaxArena) -> Syntax { - precondition(newRaw.arena === rawNodeArena) + func replacingSelf(_ newRaw: RawSyntax, rawNodeArena: RetainedSyntaxArena, allocationArena: SyntaxArena) -> Syntax { + precondition(newRaw.arenaReference == rawNodeArena) // If we have a parent already, then ask our current parent to copy itself // recursively up to the root. if let parent { @@ -176,15 +185,20 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable { /// - Returns: The new root node created by this operation, and the new child /// syntax data. /// - SeeAlso: replacingSelf(_:) - func replacingChild(at index: Int, with newChild: RawSyntax?, rawNodeArena: SyntaxArena?, allocationArena: SyntaxArena) -> Syntax { - precondition(newChild?.arena === rawNodeArena || newChild == nil) + func replacingChild(at index: Int, with newChild: RawSyntax?, rawNodeArena: RetainedSyntaxArena?, allocationArena: SyntaxArena) -> Syntax { + precondition(newChild == nil || (rawNodeArena != nil && newChild!.arenaReference == rawNodeArena!)) // After newRaw has been allocated in `allocationArena`, `rawNodeArena` will // be a child arena of `allocationArena` and thus, `allocationArena` will // keep `newChild` alive. let newRaw = withExtendedLifetime(rawNodeArena) { raw.layoutView!.replacingChild(at: index, with: newChild, arena: allocationArena) } - return replacingSelf(newRaw, rawNodeArena: allocationArena, allocationArena: allocationArena) + return replacingSelf(newRaw, rawNodeArena: RetainedSyntaxArena(allocationArena), allocationArena: allocationArena) + } + + /// Same as `replacingChild(at:with:rawNodeArena:allocationArena:)` but takes a `__SyntaxArena` instead of a `RetainedSyntaxArena`. + func replacingChild(at index: Int, with newChild: RawSyntax?, rawNodeArena: SyntaxArena?, allocationArena: SyntaxArena) -> Syntax { + return self.replacingChild(at: index, with: newChild, rawNodeArena: rawNodeArena.map(RetainedSyntaxArena.init), allocationArena: allocationArena) } /// Identical to `replacingChild(at: Int, with: RawSyntax?, arena: SyntaxArena)` @@ -192,13 +206,13 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable { /// `newChild` has been addded to the result. func replacingChild(at index: Int, with newChild: Syntax?, arena: SyntaxArena) -> Syntax { return withExtendedLifetime(newChild) { - return replacingChild(at: index, with: newChild?.raw, rawNodeArena: newChild?.raw.arena, allocationArena: arena) + return replacingChild(at: index, with: newChild?.raw, rawNodeArena: newChild?.raw.arenaReference.retained, allocationArena: arena) } } func withLeadingTrivia(_ leadingTrivia: Trivia, arena: SyntaxArena) -> Syntax { if let raw = raw.withLeadingTrivia(leadingTrivia, arena: arena) { - return replacingSelf(raw, rawNodeArena: arena, allocationArena: arena) + return replacingSelf(raw, rawNodeArena: RetainedSyntaxArena(arena), allocationArena: arena) } else { return self } @@ -206,7 +220,7 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable { func withTrailingTrivia(_ trailingTrivia: Trivia, arena: SyntaxArena) -> Syntax { if let raw = raw.withTrailingTrivia(trailingTrivia, arena: arena) { - return replacingSelf(raw, rawNodeArena: arena, allocationArena: arena) + return replacingSelf(raw, rawNodeArena: RetainedSyntaxArena(arena), allocationArena: arena) } else { return self } @@ -214,7 +228,7 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable { func withPresence(_ presence: SourcePresence, arena: SyntaxArena) -> Syntax { if let raw = raw.tokenView?.withPresence(presence, arena: arena) { - return replacingSelf(raw, rawNodeArena: arena, allocationArena: arena) + return replacingSelf(raw, rawNodeArena: RetainedSyntaxArena(arena), allocationArena: arena) } else { return self } @@ -228,10 +242,15 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable { } @_spi(RawSyntax) - public init(raw: RawSyntax, rawNodeArena: __shared SyntaxArena) { + public init(raw: RawSyntax, rawNodeArena: __shared RetainedSyntaxArena) { self = .forRoot(raw, rawNodeArena: rawNodeArena) } + @_spi(RawSyntax) + public init(raw: RawSyntax, rawNodeArena: __shared SyntaxArena) { + self = .forRoot(raw, rawNodeArena: RetainedSyntaxArena(rawNodeArena)) + } + /// Create a ``Syntax`` node from a specialized syntax node. public init(_ syntax: some SyntaxProtocol) { self = syntax._syntaxNode diff --git a/Sources/SwiftSyntax/SyntaxArena.swift b/Sources/SwiftSyntax/SyntaxArena.swift index 015b562eba4..778df2e1e13 100644 --- a/Sources/SwiftSyntax/SyntaxArena.swift +++ b/Sources/SwiftSyntax/SyntaxArena.swift @@ -49,7 +49,10 @@ public class SyntaxArena { #if DEBUG || SWIFTSYNTAX_ENABLE_ASSERTIONS /// Whether or not this arena has been added to other arenas as a child. /// Used to make sure we don’t introduce retain cycles between arenas. - private var hasParent: Bool + /// + /// - Important: This is only intended to be used for assertions to catch + /// retain cycles in syntax arenas. + var hasParent: Bool #endif /// Construct a new ``SyntaxArena`` in which syntax nodes can be allocated. @@ -71,33 +74,6 @@ public class SyntaxArena { } } - /// If this arena or any of its child arenas is a ``ParsingSyntaxArena`` - /// return one of these arenas, otherwise return `nil`. - /// - /// If the arena has multiple child nodes that are ``ParsingSyntaxArena``s, it - /// is undefined which one will be returned. - /// - /// The use case for this is to get the trivia parsing function of the arena. - /// Parsed tokens created by `SwiftParser` automatically reside in a - /// ``ParsingSyntaxArena`` but if they are modified (e.g. using the `with` - /// functions), they might reside in a new arena. But we still want to be able - /// to retrieve trivia from those modified tokens, which requires calling into - /// the `parseTrivia` function of the ``ParsingSyntaxArena`` that created the - /// token. Since the modified syntax arena needs to keep the original - /// ``ParsingSyntaxArena`` alive, we can search this arena’s `childRefs` for - /// the ``ParsingSyntaxArena`` that created the token. - var parsingArena: ParsingSyntaxArena? { - if let parsingArena = self as? ParsingSyntaxArena { - return parsingArena - } - for child in childRefs { - if let parsingArena = child.value.parsingArena { - return parsingArena - } - } - return nil - } - /// Allocates a buffer of `RawSyntax?` with the given count, then returns the /// uninitialized memory range as a `UnsafeMutableBufferPointer`. func allocateRawSyntaxBuffer(count: Int) -> UnsafeMutableBufferPointer { @@ -173,20 +149,11 @@ public class SyntaxArena { if childRefs.insert(otherRef).inserted { otherRef.retain() #if DEBUG || SWIFTSYNTAX_ENABLE_ASSERTIONS - // FIXME: This may trigger a data race warning in Thread Sanitizer. - // Can we use atomic bool here? - otherRef.value.hasParent = true + otherRef.setHasParent(true) #endif } } - /// Recursively checks if this arena contains given `arenaRef` as a descendant. - func contains(arenaRef: SyntaxArenaRef) -> Bool { - childRefs.contains { childRef in - childRef == arenaRef || childRef.value.contains(arenaRef: arenaRef) - } - } - /// Checks if the given syntax text is managed by this arena. /// /// "managed" means it's empty, a part of "source buffer", or in the memory @@ -205,7 +172,9 @@ public class ParsingSyntaxArena: SyntaxArena { private var sourceBuffer: UnsafeBufferPointer /// Function to parse trivia. - private var parseTriviaFunction: ParseTriviaFunction + /// + /// - Important: Must never be changed to a mutable value. See `SyntaxArenaRef.parseTrivia`. + private let parseTriviaFunction: ParseTriviaFunction @_spi(RawSyntax) public init(parseTriviaFunction: @escaping ParseTriviaFunction) { @@ -254,17 +223,34 @@ public class ParsingSyntaxArena: SyntaxArena { /// Parse `source` into a list of ``RawTriviaPiece`` using `parseTriviaFunction`. @_spi(RawSyntax) public func parseTrivia(source: SyntaxText, position: TriviaPosition) -> [RawTriviaPiece] { + // Must never access mutable state. See `SyntaxArenaRef.parseTrivia`. return self.parseTriviaFunction(source, position) } } +/// An opaque wrapper around `SyntaxArena` that keeps the arena alive. +@_spi(RawSyntax) +public struct RetainedSyntaxArena: @unchecked Sendable { + // Unchecked conformance to sendable is fine because `arena` is not + // accessible. It is just used to keep the arena alive. + private let arena: SyntaxArena + + init(_ arena: SyntaxArena) { + self.arena = arena + } + + fileprivate func arenaRef() -> SyntaxArenaRef { + return SyntaxArenaRef(arena) + } +} + /// Unsafely unowned reference to ``SyntaxArena``. The user is responsible to /// maintain the lifetime of the ``SyntaxArena``. /// /// `RawSyntaxData` holds its ``SyntaxArena`` in this form to prevent their cyclic /// strong references. Also, passing around ``SyntaxArena`` in this form doesn't /// cause any ref-counting traffic. -struct SyntaxArenaRef: Hashable { +struct SyntaxArenaRef: Hashable, @unchecked Sendable { private let _value: Unmanaged init(_ value: __shared SyntaxArena) { @@ -272,10 +258,17 @@ struct SyntaxArenaRef: Hashable { } /// Returns the ``SyntaxArena`` - var value: SyntaxArena { + private var value: SyntaxArena { get { self._value.takeUnretainedValue() } } + /// Assuming that this references a `ParsingSyntaxArena`, + func parseTrivia(source: SyntaxText, position: TriviaPosition) -> [RawTriviaPiece] { + // It is safe to access `_value` here because `parseTrivia` only accesses + // `parseTriviaFunction`, which is a constant. + (value as! ParsingSyntaxArena).parseTrivia(source: source, position: position) + } + func retain() { _ = self._value.retain() } @@ -284,6 +277,27 @@ struct SyntaxArenaRef: Hashable { self._value.release() } + /// Get an opaque wrapper that keeps the syntax arena alive. + var retained: RetainedSyntaxArena { + return RetainedSyntaxArena(value) + } + + #if DEBUG || SWIFTSYNTAX_ENABLE_ASSERTIONS + /// Accessor for ther underlying's `SyntaxArena.hasParent` + var hasParent: Bool { + // FIXME: This accesses mutable state across actor boundaries and is thus not concurrency-safe. + // We should use an atomic for `hasParent` to make it concurrency safe. + value.hasParent + } + + /// Sets the `SyntaxArena.hasParent` on the referenced arena. + func setHasParent(_ newValue: Bool) { + // FIXME: This modifies mutable state across actor boundaries and is thus not concurrency-safe. + // We should use an atomic for `hasParent` to make it concurrency safe. + value.hasParent = newValue + } + #endif + func hash(into hasher: inout Hasher) { hasher.combine(_value.toOpaque()) } @@ -291,4 +305,20 @@ struct SyntaxArenaRef: Hashable { static func == (lhs: SyntaxArenaRef, rhs: SyntaxArenaRef) -> Bool { return lhs._value.toOpaque() == rhs._value.toOpaque() } + + static func == (lhs: SyntaxArenaRef, rhs: __shared SyntaxArena) -> Bool { + return lhs == SyntaxArenaRef(rhs) + } + + static func == (lhs: __shared SyntaxArena, rhs: SyntaxArenaRef) -> Bool { + return rhs == lhs + } + + static func == (lhs: SyntaxArenaRef, rhs: RetainedSyntaxArena) -> Bool { + return lhs == rhs.arenaRef() + } + + static func == (lhs: RetainedSyntaxArena, rhs: SyntaxArenaRef) -> Bool { + return rhs == lhs + } } diff --git a/Sources/SwiftSyntax/SyntaxArenaAllocatedBuffer.swift b/Sources/SwiftSyntax/SyntaxArenaAllocatedBuffer.swift new file mode 100644 index 00000000000..832763c137e --- /dev/null +++ b/Sources/SwiftSyntax/SyntaxArenaAllocatedBuffer.swift @@ -0,0 +1,94 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +/// An `UnsafePointer` pointing into memory that was allocated by a +/// ``SyntaxArena``. +/// +/// Because the syntax arena will always outlive any syntax nodes that +/// reference its contents, we know that the pointer's contents won't get +/// deallocated while being accessed and thus we can add an unchecked `Sendable` +/// conformance. +@_spi(RawSyntax) public struct SyntaxArenaAllocatedPointer: @unchecked Sendable { + private let pointer: UnsafePointer + + /// Create a pointer from an `UnsafePointer` that was allocated inside a + /// ``SyntaxArena``. + /// + /// - Important: The client needs to ensure sure that the buffer is indeed + /// allocated by a ``SyntaxArena`` and that the ``SyntaxArena`` will outlive + /// any users of this ``SyntaxArenaAllocatedBufferPointer``. + init(_ buffer: UnsafePointer) { + self.pointer = buffer + } + + var pointee: Element { + return pointer.pointee + } + + var unsafeRawPointer: UnsafeRawPointer { + return UnsafeRawPointer(pointer) + } +} + +/// An `UnsafeBufferPointer` pointing into memory that was allocated by a +/// ``SyntaxArena``. +/// +/// Because the syntax arena will always outlive any syntax nodes that +/// reference its contents, we know that the buffer's contents won't get +/// deallocated while being accessed and thus we can add an unchecked `Sendable` +/// conformance. +@_spi(RawSyntax) public struct SyntaxArenaAllocatedBufferPointer: Collection, @unchecked Sendable { + private let buffer: UnsafeBufferPointer + + /// Create an empty buffer with no elements. + init() { + self.buffer = UnsafeBufferPointer(start: nil, count: 0) + } + + /// Create a buffer pointer from an `UnsafeBufferPointer` that was allocated + /// inside a ``SyntaxArena``. + /// + /// - Important: The client needs to ensure sure that the buffer is indeed + /// allocated by a ``SyntaxArena`` and that the ``SyntaxArena`` will outlive + /// any users of this ``SyntaxArenaAllocatedBufferPointer``. + @_spi(RawSyntax) public init(_ buffer: UnsafeBufferPointer) { + self.buffer = buffer + } + + @_spi(RawSyntax) public subscript>(range: RangeType) -> SyntaxArenaAllocatedBufferPointer { + return SyntaxArenaAllocatedBufferPointer(UnsafeBufferPointer(rebasing: self.buffer[range])) + } + + @_spi(RawSyntax) public subscript(_ index: Int) -> Element { + return self.buffer[index] + } + + @_spi(RawSyntax) public func makeIterator() -> UnsafeBufferPointer.Iterator { + return buffer.makeIterator() + } + + public var startIndex: Int { buffer.startIndex } + + public var endIndex: Int { buffer.endIndex } + + public func index(after i: Int) -> Int { + return buffer.index(after: i) + } + + var baseAddress: UnsafePointer? { + return buffer.baseAddress + } + + var unsafeRawBufferPointer: UnsafeRawBufferPointer { + return UnsafeRawBufferPointer(buffer) + } +} diff --git a/Sources/SwiftSyntax/SyntaxChildren.swift b/Sources/SwiftSyntax/SyntaxChildren.swift index 893ccb56fe0..bd78e0e1324 100644 --- a/Sources/SwiftSyntax/SyntaxChildren.swift +++ b/Sources/SwiftSyntax/SyntaxChildren.swift @@ -119,7 +119,7 @@ fileprivate extension AbsoluteSyntaxInfo { /// Collection that contains the child nodes of a raw node (including missing /// nodes), along with their associated `AbsoluteSyntaxInfo`. -struct RawSyntaxChildren: BidirectionalCollection { +struct RawSyntaxChildren: BidirectionalCollection, Sendable { typealias Element = (raw: RawSyntax?, syntaxInfo: AbsoluteSyntaxInfo) typealias Index = SyntaxChildrenIndex @@ -281,7 +281,7 @@ struct RawSyntaxChildren: BidirectionalCollection { /// Collection that contains the `present` child nodes of an /// `AbsoluteRawSyntax` node. -struct NonNilRawSyntaxChildren: BidirectionalCollection { +struct NonNilRawSyntaxChildren: BidirectionalCollection, Sendable { typealias Element = AbsoluteRawSyntax typealias Index = SyntaxChildrenIndex @@ -417,7 +417,7 @@ struct NonNilRawSyntaxChildren: BidirectionalCollection { } /// Collection that contains the present child ``Syntax`` nodes of the given node. -public struct SyntaxChildren: BidirectionalCollection { +public struct SyntaxChildren: BidirectionalCollection, Sendable { /// ``SyntaxChildren`` is indexed by ``SyntaxChildrenIndex``. public typealias Index = SyntaxChildrenIndex diff --git a/Sources/SwiftSyntax/SyntaxCollection.swift b/Sources/SwiftSyntax/SyntaxCollection.swift index a0717bce757..d1c3f5e0f74 100644 --- a/Sources/SwiftSyntax/SyntaxCollection.swift +++ b/Sources/SwiftSyntax/SyntaxCollection.swift @@ -49,7 +49,7 @@ extension SyntaxCollection { arena: arena ) } - self = Syntax.forRoot(raw, rawNodeArena: arena).cast(Self.self) + self = Syntax.forRoot(raw, rawNodeArena: RetainedSyntaxArena(arena)).cast(Self.self) } public init(arrayLiteral elements: Element...) { @@ -70,7 +70,7 @@ extension SyntaxCollection { internal func replacingLayout(_ layout: [RawSyntax?]) -> Self { let arena = SyntaxArena() let newRaw = layoutView.replacingLayout(with: layout, arena: arena) - return Syntax(self).replacingSelf(newRaw, rawNodeArena: arena, allocationArena: arena).cast(Self.self) + return Syntax(self).replacingSelf(newRaw, rawNodeArena: RetainedSyntaxArena(arena), allocationArena: arena).cast(Self.self) } /// Return the index of `node` within this collection. diff --git a/Sources/SwiftSyntax/SyntaxIdentifier.swift b/Sources/SwiftSyntax/SyntaxIdentifier.swift index 24583b4464f..b4cd7a2e393 100644 --- a/Sources/SwiftSyntax/SyntaxIdentifier.swift +++ b/Sources/SwiftSyntax/SyntaxIdentifier.swift @@ -57,7 +57,7 @@ public struct SyntaxIndexInTree: Comparable, Hashable, Sendable { /// different syntax tree. Modifying any node in the syntax tree a node is /// contained in generates a copy of that tree and thus changes the IDs of all /// nodes in the tree, not just the modified node's children. -public struct SyntaxIdentifier: Hashable { +public struct SyntaxIdentifier: Hashable, Sendable { /// Unique value for the root node. /// /// Multiple trees may have the same 'rootId' if their root RawSyntax is the @@ -82,7 +82,7 @@ public struct SyntaxIdentifier: Hashable { static func forRoot(_ raw: RawSyntax) -> SyntaxIdentifier { return .init( - rootId: UInt(bitPattern: raw.pointer), + rootId: UInt(bitPattern: raw.pointer.unsafeRawPointer), indexInTree: .zero ) } diff --git a/Sources/SwiftSyntax/SyntaxNodeStructure.swift b/Sources/SwiftSyntax/SyntaxNodeStructure.swift index 517daa533d9..4c6de9f9897 100644 --- a/Sources/SwiftSyntax/SyntaxNodeStructure.swift +++ b/Sources/SwiftSyntax/SyntaxNodeStructure.swift @@ -11,14 +11,14 @@ //===----------------------------------------------------------------------===// /// Describes the statically allowed structure of a syntax tree node. -public enum SyntaxNodeStructure { - public enum SyntaxChoice { +public enum SyntaxNodeStructure: Sendable { + public enum SyntaxChoice: Sendable { case node(SyntaxProtocol.Type) case token(TokenKind) } /// The node contains a fixed number of children which can be accessed by these key paths. - case layout([AnyKeyPath]) + case layout([AnyKeyPath & Sendable]) /// The node is a `SyntaxCollection` of the given type. case collection(SyntaxProtocol.Type) diff --git a/Sources/SwiftSyntax/SyntaxProtocol.swift b/Sources/SwiftSyntax/SyntaxProtocol.swift index b96c2508079..2afa57e1fb8 100644 --- a/Sources/SwiftSyntax/SyntaxProtocol.swift +++ b/Sources/SwiftSyntax/SyntaxProtocol.swift @@ -15,7 +15,7 @@ /// /// - Important: Do not conform to this protocol yourself. public protocol SyntaxProtocol: CustomStringConvertible, - CustomDebugStringConvertible, TextOutputStreamable, CustomReflectable + CustomDebugStringConvertible, TextOutputStreamable, CustomReflectable, Sendable { /// Retrieve the generic syntax node that is represented by this node. @@ -159,7 +159,7 @@ public extension SyntaxProtocol { // Make sure `self` (and thus the arena of `self.raw`) can’t get deallocated // before the detached node can be created. return withExtendedLifetime(self) { - return Syntax(raw: self.raw, rawNodeArena: self.raw.arena).cast(Self.self) + return Syntax(raw: self.raw, rawNodeArena: self.raw.arenaReference.retained).cast(Self.self) } } } diff --git a/Sources/SwiftSyntax/SyntaxText.swift b/Sources/SwiftSyntax/SyntaxText.swift index c5068a76aa4..bdb3f6d26a2 100644 --- a/Sources/SwiftSyntax/SyntaxText.swift +++ b/Sources/SwiftSyntax/SyntaxText.swift @@ -34,11 +34,11 @@ /// `Swift.String`, ill-formed UTF8 sequences are replaced with the Unicode /// replacement character (`\u{FFFD}`). @_spi(RawSyntax) -public struct SyntaxText { - var buffer: UnsafeBufferPointer +public struct SyntaxText: Sendable { + var buffer: SyntaxArenaAllocatedBufferPointer /// Construct a ``SyntaxText`` whose text is represented by the given `buffer`. - public init(buffer: UnsafeBufferPointer) { + public init(buffer: SyntaxArenaAllocatedBufferPointer) { self.buffer = buffer } @@ -51,7 +51,7 @@ public struct SyntaxText { count == 0 || baseAddress != nil, "If count is not zero, base address must be exist" ) - buffer = .init(start: baseAddress, count: count) + buffer = .init(UnsafeBufferPointer(start: baseAddress, count: count)) } /// Creates an empty ``SyntaxText`` @@ -193,7 +193,7 @@ extension SyntaxText: Hashable { /// Hash the contents of this ``SyntaxText`` into `hasher`. public func hash(into hasher: inout Hasher) { - hasher.combine(bytes: .init(buffer)) + hasher.combine(bytes: buffer.unsafeRawBufferPointer) } } diff --git a/Sources/SwiftSyntax/SyntaxTreeViewMode.swift b/Sources/SwiftSyntax/SyntaxTreeViewMode.swift index f5a6c06b019..485e535d8db 100644 --- a/Sources/SwiftSyntax/SyntaxTreeViewMode.swift +++ b/Sources/SwiftSyntax/SyntaxTreeViewMode.swift @@ -12,7 +12,7 @@ /// Specifies how missing and unexpected nodes should be handled when traversing /// a syntax tree. -public enum SyntaxTreeViewMode { +public enum SyntaxTreeViewMode: Sendable { /// Visit the tree in a way that reproduces the original source code. /// Missing nodes will not be visited, unexpected nodes will be visited. /// This mode is useful for source code transformations like a formatter. diff --git a/Sources/SwiftSyntax/TokenDiagnostic.swift b/Sources/SwiftSyntax/TokenDiagnostic.swift index 0a48338c265..c74f24dae25 100644 --- a/Sources/SwiftSyntax/TokenDiagnostic.swift +++ b/Sources/SwiftSyntax/TokenDiagnostic.swift @@ -13,14 +13,14 @@ /// If the token has an error that's inherent to the token itself and not its /// surrounding structure, this defines the type of the error. /// `byteOffset` specifies at which offset the error occurred. -public struct TokenDiagnostic: Hashable { - public enum Severity: Comparable { +public struct TokenDiagnostic: Hashable, Sendable { + public enum Severity: Comparable, Sendable { case warning case error } /// Each diagnostic kind is uniquely represented by a value in this enum. - public enum Kind { + public enum Kind: Sendable { // Please order these alphabetically case editorPlaceholder diff --git a/Sources/SwiftSyntax/TokenSequence.swift b/Sources/SwiftSyntax/TokenSequence.swift index 7c78340a561..5cb2efecd5a 100644 --- a/Sources/SwiftSyntax/TokenSequence.swift +++ b/Sources/SwiftSyntax/TokenSequence.swift @@ -11,7 +11,7 @@ //===----------------------------------------------------------------------===// /// Sequence of tokens that are part of the provided Syntax node. -public struct TokenSequence: Sequence { +public struct TokenSequence: Sequence, Sendable { /// Iterates over a ``TokenSequence``. public struct Iterator: IteratorProtocol { var nextToken: TokenSyntax? @@ -68,7 +68,7 @@ extension TokenSequence: CustomReflectable { } /// Reverse sequence of tokens that are part of the provided Syntax node. -public struct ReversedTokenSequence: Sequence { +public struct ReversedTokenSequence: Sequence, Sendable { /// Iterates over a ``ReversedTokenSequence``. public struct Iterator: IteratorProtocol { var nextToken: TokenSyntax? diff --git a/Sources/SwiftSyntax/TokenSyntax.swift b/Sources/SwiftSyntax/TokenSyntax.swift index 42cc949780c..d7fd7ae213d 100644 --- a/Sources/SwiftSyntax/TokenSyntax.swift +++ b/Sources/SwiftSyntax/TokenSyntax.swift @@ -57,7 +57,7 @@ public struct TokenSyntax: SyntaxProtocol, SyntaxHashable { tokenDiagnostic: nil, arena: arena ) - self = Syntax.forRoot(raw, rawNodeArena: arena).cast(TokenSyntax.self) + self = Syntax.forRoot(raw, rawNodeArena: RetainedSyntaxArena(arena)).cast(TokenSyntax.self) } /// Whether the token is present or missing. @@ -111,7 +111,7 @@ public struct TokenSyntax: SyntaxProtocol, SyntaxHashable { } let arena = SyntaxArena() let newRaw = tokenView.withKind(newValue, arena: arena) - self = Syntax(self).replacingSelf(newRaw, rawNodeArena: arena, allocationArena: arena).cast(TokenSyntax.self) + self = Syntax(self).replacingSelf(newRaw, rawNodeArena: RetainedSyntaxArena(arena), allocationArena: arena).cast(TokenSyntax.self) } } diff --git a/Sources/SwiftSyntax/Trivia.swift b/Sources/SwiftSyntax/Trivia.swift index 2cf9b27ce58..07e3925ee9e 100644 --- a/Sources/SwiftSyntax/Trivia.swift +++ b/Sources/SwiftSyntax/Trivia.swift @@ -27,7 +27,7 @@ public enum TriviaPosition { /// Each ``TokenSyntax`` can have multiple ``TriviaPiece``s as either leading or /// trailing trivia, which occur before or after the token’s content, respectively. /// ``Trivia`` represents a collection of these ``TriviaPiece``s -public struct Trivia { +public struct Trivia: Sendable { /// The pieces this trivia consists of. Each ``TriviaPiece`` can represent /// multiple characters, such as an entire comment or 4 spaces. public let pieces: [TriviaPiece] diff --git a/Sources/SwiftSyntax/Utils.swift b/Sources/SwiftSyntax/Utils.swift index 856bac5ab6f..eebf805b5ab 100644 --- a/Sources/SwiftSyntax/Utils.swift +++ b/Sources/SwiftSyntax/Utils.swift @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -public struct ByteSourceRange: Equatable { +public struct ByteSourceRange: Equatable, Sendable { public var offset: Int public var length: Int @@ -47,7 +47,7 @@ public struct ByteSourceRange: Equatable { } } -public struct IncrementalEdit: Equatable { +public struct IncrementalEdit: Equatable, Sendable { /// The byte range of the original source buffer that the edit applies to. public let range: ByteSourceRange /// The length of the edit replacement in UTF8 bytes. diff --git a/Sources/SwiftSyntax/generated/Keyword.swift b/Sources/SwiftSyntax/generated/Keyword.swift index 4d5bcc328d6..3988312544b 100644 --- a/Sources/SwiftSyntax/generated/Keyword.swift +++ b/Sources/SwiftSyntax/generated/Keyword.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -public enum Keyword: UInt8, Hashable { +public enum Keyword: UInt8, Hashable, Sendable { case __consuming case __owned case __setter_access diff --git a/Sources/SwiftSyntax/generated/SyntaxEnum.swift b/Sources/SwiftSyntax/generated/SyntaxEnum.swift index e8b8993a98f..408b25f9e60 100644 --- a/Sources/SwiftSyntax/generated/SyntaxEnum.swift +++ b/Sources/SwiftSyntax/generated/SyntaxEnum.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// /// Enum to exhaustively switch over all different syntax nodes. -public enum SyntaxEnum { +public enum SyntaxEnum: Sendable { case token(TokenSyntax) case accessorBlock(AccessorBlockSyntax) case accessorDeclList(AccessorDeclListSyntax) diff --git a/Sources/SwiftSyntax/generated/SyntaxKind.swift b/Sources/SwiftSyntax/generated/SyntaxKind.swift index 9910c763f45..8588c2a632c 100644 --- a/Sources/SwiftSyntax/generated/SyntaxKind.swift +++ b/Sources/SwiftSyntax/generated/SyntaxKind.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// /// Enumerates the known kinds of Syntax represented in the Syntax tree. -public enum SyntaxKind { +public enum SyntaxKind: Sendable { case token case accessorBlock case accessorDeclList diff --git a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift index 1c4438990b6..cd4a81d4f28 100644 --- a/Sources/SwiftSyntax/generated/SyntaxRewriter.swift +++ b/Sources/SwiftSyntax/generated/SyntaxRewriter.swift @@ -36,7 +36,7 @@ open class SyntaxRewriter { } return withExtendedLifetime(rewritten) { - return Syntax(node).replacingSelf(rewritten.raw, rawNodeArena: rewritten.raw.arena, allocationArena: SyntaxArena()) + return Syntax(node).replacingSelf(rewritten.raw, rawNodeArena: rewritten.raw.arenaReference.retained, allocationArena: SyntaxArena()) } } diff --git a/Sources/SwiftSyntax/generated/TokenKind.swift b/Sources/SwiftSyntax/generated/TokenKind.swift index d4c0e30a8e8..4f1460e1093 100644 --- a/Sources/SwiftSyntax/generated/TokenKind.swift +++ b/Sources/SwiftSyntax/generated/TokenKind.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// /// Enumerates the kinds of tokens in the Swift language. -public enum TokenKind: Hashable { +public enum TokenKind: Hashable, Sendable { case arrow case atSign case backslash diff --git a/Sources/SwiftSyntax/generated/TriviaPieces.swift b/Sources/SwiftSyntax/generated/TriviaPieces.swift index 44d83e69265..eeb20be0013 100644 --- a/Sources/SwiftSyntax/generated/TriviaPieces.swift +++ b/Sources/SwiftSyntax/generated/TriviaPieces.swift @@ -20,7 +20,7 @@ /// /// In general, you should deal with the actual Trivia collection instead /// of individual pieces whenever possible. -public enum TriviaPiece { +public enum TriviaPiece: Sendable { /// A backslash that is at the end of a line in a multi-line string literal to escape the newline. case backslashes(Int) /// A developer block comment, starting with '/*' and ending with '*/'. @@ -289,7 +289,7 @@ extension TriviaPiece { /// In contrast to ``TriviaPiece``, a ``RawTriviaPiece`` does not own the source /// text of the trivia. @_spi(RawSyntax) -public enum RawTriviaPiece: Equatable { +public enum RawTriviaPiece: Equatable, Sendable { case backslashes(Int) case blockComment(SyntaxText) case carriageReturns(Int) diff --git a/Sources/SwiftSyntaxBuilder/Syntax+StringInterpolation.swift b/Sources/SwiftSyntaxBuilder/Syntax+StringInterpolation.swift index d5eb04e9f63..67402283ebc 100644 --- a/Sources/SwiftSyntaxBuilder/Syntax+StringInterpolation.swift +++ b/Sources/SwiftSyntaxBuilder/Syntax+StringInterpolation.swift @@ -446,7 +446,11 @@ extension Optional: ExpressibleByLiteralSyntax where Wrapped: ExpressibleByLiter extension TokenSyntax: SyntaxExpressibleByStringInterpolation { public init(stringInterpolation: SyntaxStringInterpolation) { let string = stringInterpolation.sourceText.withUnsafeBufferPointer { buf in - return String(syntaxText: SyntaxText(buffer: buf)) + // Technically, `buf` is not allocated in a `SyntaxArena` but it satisfies + // all the required properties: `buf` will always outlive any references + // to it. + let syntaxArenaBuf = SyntaxArenaAllocatedBufferPointer(buf) + return String(syntaxText: SyntaxText(buffer: syntaxArenaBuf)) } self = .identifier(string) } @@ -478,8 +482,12 @@ extension Trivia: ExpressibleByStringInterpolation { public init(stringInterpolation: String.StringInterpolation) { var text = String(stringInterpolation: stringInterpolation) let pieces = text.withUTF8 { (buf) -> [TriviaPiece] in + // Technically, `buf` is not allocated in a `SyntaxArena` but it satisfies + // all the required properties: `buf` will always outlive any references + // to it. + let syntaxArenaBuf = SyntaxArenaAllocatedBufferPointer(buf) // The leading trivia position is a little bit less restrictive (it allows a shebang), so let's use it. - let rawPieces = TriviaParser.parseTrivia(SyntaxText(buffer: buf), position: .leading) + let rawPieces = TriviaParser.parseTrivia(SyntaxText(buffer: syntaxArenaBuf), position: .leading) return rawPieces.map { TriviaPiece.init(raw: $0) } } diff --git a/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift b/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift index 60e02a0ef93..46762399dfb 100644 --- a/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift +++ b/Sources/SwiftSyntaxMacroExpansion/MacroExpansion.swift @@ -14,7 +14,7 @@ import SwiftBasicFormat import SwiftSyntax @_spi(MacroExpansion) @_spi(ExperimentalLanguageFeature) import SwiftSyntaxMacros -public enum MacroRole { +public enum MacroRole: Sendable { case expression case declaration case accessor diff --git a/Sources/SwiftSyntaxMacros/AbstractSourceLocation.swift b/Sources/SwiftSyntaxMacros/AbstractSourceLocation.swift index 51ead77e0fc..28c351afe72 100644 --- a/Sources/SwiftSyntaxMacros/AbstractSourceLocation.swift +++ b/Sources/SwiftSyntaxMacros/AbstractSourceLocation.swift @@ -14,7 +14,7 @@ import SwiftSyntax import SwiftSyntaxBuilder /// Abstractly represents a source location in the macro. -public struct AbstractSourceLocation { +public struct AbstractSourceLocation: Sendable { /// A primary expression that represents the file and is `ExpressibleByStringLiteral`. public let file: ExprSyntax diff --git a/Tests/SwiftParserTest/LexerTests.swift b/Tests/SwiftParserTest/LexerTests.swift index 284ea7e1c07..688c5e65108 100644 --- a/Tests/SwiftParserTest/LexerTests.swift +++ b/Tests/SwiftParserTest/LexerTests.swift @@ -49,13 +49,16 @@ fileprivate func assertRawBytesLexeme( ) { XCTAssertEqual(lexeme.rawTokenKind, kind, file: file, line: line) leadingTrivia.withUnsafeBufferPointer { leadingTrivia in - XCTAssertEqual(lexeme.leadingTriviaText, SyntaxText(buffer: leadingTrivia), file: file, line: line) + let leadingTriviaText = SyntaxText(buffer: SyntaxArenaAllocatedBufferPointer(leadingTrivia)) + XCTAssertEqual(lexeme.leadingTriviaText, leadingTriviaText, file: file, line: line) } - text.withUnsafeBufferPointer { text in - XCTAssertEqual(lexeme.tokenText, SyntaxText(buffer: text), file: file, line: line) + text.withUnsafeBufferPointer { textBuffer in + let text = SyntaxText(buffer: SyntaxArenaAllocatedBufferPointer(textBuffer)) + XCTAssertEqual(lexeme.tokenText, text, file: file, line: line) } trailingTrivia.withUnsafeBufferPointer { trailingTrivia in - XCTAssertEqual(lexeme.trailingTriviaText, SyntaxText(buffer: trailingTrivia), file: file, line: line) + let trailingTriviaText = SyntaxText(buffer: SyntaxArenaAllocatedBufferPointer(trailingTrivia)) + XCTAssertEqual(lexeme.trailingTriviaText, trailingTriviaText, file: file, line: line) } XCTAssertEqual(lexeme.diagnostic, error, file: file, line: line) } diff --git a/Tests/SwiftSyntaxTest/DummyParseToken.swift b/Tests/SwiftSyntaxTest/DummyParseToken.swift new file mode 100644 index 00000000000..80019e7eed4 --- /dev/null +++ b/Tests/SwiftSyntaxTest/DummyParseToken.swift @@ -0,0 +1,19 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +@_spi(RawSyntax) import SwiftSyntax + +/// Dummy trivia parsing function. +func dummyParseToken(source: SyntaxText, position: TriviaPosition) -> [RawTriviaPiece] { + // Emit a single `unexpectedText` trivia of the whole trivia text. + return [.unexpectedText(source)] +} diff --git a/Tests/SwiftSyntaxTest/RawSyntaxTests.swift b/Tests/SwiftSyntaxTest/RawSyntaxTests.swift index dcec2884bd6..67a6074dc1a 100644 --- a/Tests/SwiftSyntaxTest/RawSyntaxTests.swift +++ b/Tests/SwiftSyntaxTest/RawSyntaxTests.swift @@ -13,7 +13,7 @@ @_spi(RawSyntax) import SwiftSyntax import XCTest -fileprivate func cannedStructDecl(arena: SyntaxArena) -> RawStructDeclSyntax { +fileprivate func cannedStructDecl(arena: ParsingSyntaxArena) -> RawStructDeclSyntax { let structKW = RawTokenSyntax( kind: .keyword, text: arena.intern("struct"), @@ -68,7 +68,7 @@ fileprivate func cannedStructDecl(arena: SyntaxArena) -> RawStructDeclSyntax { final class RawSyntaxTests: XCTestCase { func testFactory() { - withExtendedLifetime(SyntaxArena()) { arena in + withExtendedLifetime(ParsingSyntaxArena(parseTriviaFunction: dummyParseToken)) { arena in let structDecl = cannedStructDecl(arena: arena) XCTAssertEqual( "\(structDecl.raw)", @@ -81,7 +81,7 @@ final class RawSyntaxTests: XCTestCase { } func testAccessor() { - withExtendedLifetime(SyntaxArena()) { arena in + withExtendedLifetime(ParsingSyntaxArena(parseTriviaFunction: dummyParseToken)) { arena in let structDecl = cannedStructDecl(arena: arena) XCTAssertEqual(structDecl.name.tokenKind, .identifier) XCTAssertEqual(structDecl.structKeyword.tokenText, "struct") @@ -96,7 +96,7 @@ final class RawSyntaxTests: XCTestCase { } func testMaterializedToken() { - withExtendedLifetime(SyntaxArena()) { arena in + withExtendedLifetime(ParsingSyntaxArena(parseTriviaFunction: dummyParseToken)) { arena in let ident = RawTokenSyntax( kind: .identifier, text: arena.intern("foo"), @@ -118,12 +118,6 @@ final class RawSyntaxTests: XCTestCase { } func testParsedToken() { - // Dummy trivia parsing function. - func dummyParseToken(source: SyntaxText, position: TriviaPosition) -> [RawTriviaPiece] { - // Emit a single `unexpectedText` trivia of the whole trivia text. - return [.unexpectedText(source)] - } - withExtendedLifetime(ParsingSyntaxArena(parseTriviaFunction: dummyParseToken)) { arena in let ident = RawTokenSyntax( kind: .identifier, diff --git a/Tests/SwiftSyntaxTest/SourceLocationConverterTests.swift b/Tests/SwiftSyntaxTest/SourceLocationConverterTests.swift index 6c024c24940..c946fac8d6a 100644 --- a/Tests/SwiftSyntaxTest/SourceLocationConverterTests.swift +++ b/Tests/SwiftSyntaxTest/SourceLocationConverterTests.swift @@ -36,9 +36,9 @@ fileprivate func assertPresumedSourceLocation( final class SourceLocationConverterTests: XCTestCase { func testInvalidUtf8() { - let eofToken = withExtendedLifetime(SyntaxArena()) { arena in + let eofToken = withExtendedLifetime(ParsingSyntaxArena(parseTriviaFunction: dummyParseToken)) { arena in let leadingTriviaText = [UInt8(0xfd)].withUnsafeBufferPointer { buf in - arena.intern(SyntaxText(buffer: buf)) + arena.intern(SyntaxText(buffer: SyntaxArenaAllocatedBufferPointer(buf))) } let nodeWithInvalidUtf8 = RawTokenSyntax(