diff --git a/Package.swift b/Package.swift index 526d1d0e1..1116d4a00 100644 --- a/Package.swift +++ b/Package.swift @@ -62,7 +62,10 @@ let package = Package( dependencies: []), .target( name: "_StringProcessing", - dependencies: ["_RegexParser", "_CUnicode"], + dependencies: [ + "_RegexParser", + "_CUnicode", + ], swiftSettings: publicStdlibSettings), .target( name: "RegexBuilder", diff --git a/Sources/_RegexParser/Regex/AST/AST.swift b/Sources/_RegexParser/Regex/AST/AST.swift index 44bc10828..5a5c53849 100644 --- a/Sources/_RegexParser/Regex/AST/AST.swift +++ b/Sources/_RegexParser/Regex/AST/AST.swift @@ -30,7 +30,7 @@ extension AST { extension AST { /// A node in the regex AST. public indirect enum Node: - Hashable, _TreeNode //, _ASTPrintable ASTValue, ASTAction + Hashable, _TreeNode, Sendable //, _ASTPrintable ASTValue, ASTAction { /// ... | ... | ... case alternation(Alternation) @@ -143,7 +143,7 @@ extension AST.Node { extension AST { - public struct Alternation: Hashable, _ASTNode { + public struct Alternation: Hashable, Sendable, _ASTNode { public let children: [AST.Node] public let pipes: [SourceLocation] @@ -162,7 +162,7 @@ extension AST { } } - public struct Concatenation: Hashable, _ASTNode { + public struct Concatenation: Hashable, Sendable, _ASTNode { public let children: [AST.Node] public let location: SourceLocation @@ -172,7 +172,7 @@ extension AST { } } - public struct Quote: Hashable, _ASTNode { + public struct Quote: Hashable, Sendable, _ASTNode { public let literal: String public let location: SourceLocation @@ -182,7 +182,7 @@ extension AST { } } - public struct Trivia: Hashable, _ASTNode { + public struct Trivia: Hashable, Sendable, _ASTNode { public let contents: String public let location: SourceLocation @@ -197,7 +197,7 @@ extension AST { } } - public struct Interpolation: Hashable, _ASTNode { + public struct Interpolation: Hashable, Sendable, _ASTNode { public let contents: String public let location: SourceLocation @@ -207,7 +207,7 @@ extension AST { } } - public struct Empty: Hashable, _ASTNode { + public struct Empty: Hashable, Sendable, _ASTNode { public let location: SourceLocation public init(_ location: SourceLocation) { @@ -219,15 +219,15 @@ extension AST { /// /// This is used to model a pattern which should /// not be matched against across varying scopes. - public struct AbsentFunction: Hashable, _ASTNode { - public enum Start: Hashable { + public struct AbsentFunction: Hashable, Sendable, _ASTNode { + public enum Start: Hashable, Sendable { /// `(?~|` case withPipe /// `(?~` case withoutPipe } - public enum Kind: Hashable { + public enum Kind: Hashable, Sendable { /// An absent repeater `(?~absent)`. This is equivalent to `(?~|absent|.*)` /// and therefore matches as long as the pattern `absent` is not matched. case repeater(AST.Node) @@ -261,8 +261,8 @@ extension AST { } } - public struct Reference: Hashable { - public enum Kind: Hashable { + public struct Reference: Hashable, Sendable { + public enum Kind: Hashable, Sendable { // \n \gn \g{n} \g \g'n' (?n) (?(n)... // Oniguruma: \k, \k'n' case absolute(Int) @@ -304,7 +304,7 @@ extension AST { } /// A set of global matching options in a regular expression literal. - public struct GlobalMatchingOptionSequence: Hashable { + public struct GlobalMatchingOptionSequence: Hashable, Sendable { public var options: [AST.GlobalMatchingOption] public init?(_ options: [AST.GlobalMatchingOption]) { diff --git a/Sources/_RegexParser/Regex/AST/Atom.swift b/Sources/_RegexParser/Regex/AST/Atom.swift index 992604852..c4074d711 100644 --- a/Sources/_RegexParser/Regex/AST/Atom.swift +++ b/Sources/_RegexParser/Regex/AST/Atom.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// extension AST { - public struct Atom: Hashable, _ASTNode { + public struct Atom: Hashable, Sendable, _ASTNode { public let kind: Kind public let location: SourceLocation @@ -19,7 +19,7 @@ extension AST { self.location = loc } - public enum Kind: Hashable { + public enum Kind: Hashable, Sendable { /// Just a character /// /// A, \*, \\, ... @@ -113,7 +113,7 @@ extension AST.Atom { } extension AST.Atom { - public struct Scalar: Hashable { + public struct Scalar: Hashable, Sendable { public var value: UnicodeScalar public var location: SourceLocation @@ -123,7 +123,7 @@ extension AST.Atom { } } - public struct ScalarSequence: Hashable { + public struct ScalarSequence: Hashable, Sendable { public var scalars: [Scalar] public var trivia: [AST.Trivia] @@ -145,7 +145,7 @@ extension AST.Atom { // Characters, character types, literals, etc., derived from // an escape sequence. - public enum EscapedBuiltin: Hashable { + public enum EscapedBuiltin: Hashable, Sendable { // TODO: better doc comments // Literal single characters @@ -374,7 +374,7 @@ extension AST.Atom.EscapedBuiltin { } extension AST.Atom { - public struct CharacterProperty: Hashable { + public struct CharacterProperty: Hashable, Sendable { public var kind: Kind /// Whether this is an inverted property e.g '\P{Ll}', '[:^ascii:]'. @@ -397,7 +397,7 @@ extension AST.Atom { } extension AST.Atom.CharacterProperty { - public enum Kind: Hashable { + public enum Kind: Hashable, Sendable { /// Matches any character, equivalent to Oniguruma's '\O'. case any @@ -453,14 +453,14 @@ extension AST.Atom.CharacterProperty { /// Some special properties implemented by Java. case javaSpecial(JavaSpecial) - public enum MapKind: Hashable { + public enum MapKind: Hashable, Sendable { case lowercase case uppercase case titlecase } } - public enum PCRESpecialCategory: String, Hashable { + public enum PCRESpecialCategory: String, Hashable, Sendable { case alphanumeric = "Xan" case posixSpace = "Xps" case perlSpace = "Xsp" @@ -470,7 +470,7 @@ extension AST.Atom.CharacterProperty { /// Special Java properties that correspond to methods on /// `java.lang.Character`, with the `java` prefix replaced by `is`. - public enum JavaSpecial: String, Hashable, CaseIterable { + public enum JavaSpecial: String, Hashable, CaseIterable, Sendable { case alphabetic = "javaAlphabetic" case defined = "javaDefined" case digit = "javaDigit" @@ -494,7 +494,7 @@ extension AST.Atom.CharacterProperty { extension AST.Atom { /// Anchors and other built-in zero-width assertions. - public enum AssertionKind: String { + public enum AssertionKind: String, Hashable, Sendable { /// \A case startOfSubject = #"\A"# @@ -554,10 +554,10 @@ extension AST.Atom { } extension AST.Atom { - public enum Callout: Hashable { + public enum Callout: Hashable, Sendable { /// A PCRE callout written `(?C...)` - public struct PCRE: Hashable { - public enum Argument: Hashable { + public struct PCRE: Hashable, Sendable { + public enum Argument: Hashable, Sendable { case number(Int) case string(String) } @@ -573,8 +573,8 @@ extension AST.Atom { } /// A named Oniguruma callout written `(*name[tag]{args, ...})` - public struct OnigurumaNamed: Hashable { - public struct ArgList: Hashable { + public struct OnigurumaNamed: Hashable, Sendable { + public struct ArgList: Hashable, Sendable { public var leftBrace: SourceLocation public var args: [AST.Located] public var rightBrace: SourceLocation @@ -604,8 +604,8 @@ extension AST.Atom { } /// An Oniguruma callout 'of contents', written `(?{...}[tag]D)` - public struct OnigurumaOfContents: Hashable { - public enum Direction: Hashable { + public struct OnigurumaOfContents: Hashable, Sendable { + public enum Direction: Hashable, Sendable { case inProgress // > (the default) case inRetraction // < case both // X @@ -652,7 +652,7 @@ extension AST.Atom { extension AST.Atom.Callout { /// A tag specifier `[...]` that can appear in an Oniguruma callout. - public struct OnigurumaTag: Hashable { + public struct OnigurumaTag: Hashable, Sendable { public var leftBracket: SourceLocation public var name: AST.Located public var rightBracket: SourceLocation @@ -670,8 +670,8 @@ extension AST.Atom.Callout { } extension AST.Atom { - public struct BacktrackingDirective: Hashable { - public enum Kind: Hashable { + public struct BacktrackingDirective: Hashable, Sendable { + public enum Kind: Hashable, Sendable { /// (*ACCEPT) case accept diff --git a/Sources/_RegexParser/Regex/AST/Conditional.swift b/Sources/_RegexParser/Regex/AST/Conditional.swift index c382a25b6..987629732 100644 --- a/Sources/_RegexParser/Regex/AST/Conditional.swift +++ b/Sources/_RegexParser/Regex/AST/Conditional.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// extension AST { - public struct Conditional: Hashable, _ASTNode { + public struct Conditional: Hashable, Sendable, _ASTNode { public var location: SourceLocation public var condition: Condition @@ -32,8 +32,8 @@ extension AST { } extension AST.Conditional { - public struct Condition: Hashable { - public enum Kind: Hashable { + public struct Condition: Hashable, Sendable { + public enum Kind: Hashable, Sendable { /// Check to see if a certain group was matched. case groupMatched(AST.Reference) @@ -65,7 +65,7 @@ extension AST.Conditional { } extension AST.Conditional.Condition { - public struct PCREVersionNumber: Hashable { + public struct PCREVersionNumber: Hashable, Sendable { public var major: Int public var minor: Int public var location: SourceLocation @@ -76,8 +76,8 @@ extension AST.Conditional.Condition { self.location = location } } - public struct PCREVersionCheck: Hashable { - public enum Kind: Hashable { + public struct PCREVersionCheck: Hashable, Sendable { + public enum Kind: Hashable, Sendable { case equal, greaterThanOrEqual } public var kind: AST.Located diff --git a/Sources/_RegexParser/Regex/AST/CustomCharClass.swift b/Sources/_RegexParser/Regex/AST/CustomCharClass.swift index 087387c1e..b95a6b97b 100644 --- a/Sources/_RegexParser/Regex/AST/CustomCharClass.swift +++ b/Sources/_RegexParser/Regex/AST/CustomCharClass.swift @@ -11,7 +11,7 @@ extension AST { - public struct CustomCharacterClass: Hashable { + public struct CustomCharacterClass: Hashable, Sendable { public var start: Located public var members: [Member] @@ -27,7 +27,7 @@ extension AST { self.location = sr } - public enum Member: Hashable { + public enum Member: Hashable, Sendable { /// A nested custom character class `[[ab][cd]]` case custom(CustomCharacterClass) @@ -47,7 +47,7 @@ extension AST { /// A binary operator applied to sets of members `abc&&def` case setOperation([Member], Located, [Member]) } - public struct Range: Hashable { + public struct Range: Hashable, Sendable { public var lhs: Atom public var dashLoc: SourceLocation public var rhs: Atom @@ -63,12 +63,12 @@ extension AST { self.trivia = trivia } } - public enum SetOp: String, Hashable { + public enum SetOp: String, Hashable, Sendable { case subtraction = "--" case intersection = "&&" case symmetricDifference = "~~" } - public enum Start: String { + public enum Start: String, Hashable, Sendable { case normal = "[" case inverted = "[^" } diff --git a/Sources/_RegexParser/Regex/AST/Group.swift b/Sources/_RegexParser/Regex/AST/Group.swift index 6fd46abe7..fb8105047 100644 --- a/Sources/_RegexParser/Regex/AST/Group.swift +++ b/Sources/_RegexParser/Regex/AST/Group.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// extension AST { - public struct Group: Hashable { + public struct Group: Hashable, Sendable { public let kind: Located public let child: AST.Node @@ -24,7 +24,7 @@ extension AST { self.location = r } - public enum Kind: Hashable { + public enum Kind: Hashable, Sendable { // (...) case capture @@ -116,7 +116,7 @@ extension AST.Group.Kind { } extension AST.Group { - public struct BalancedCapture: Hashable { + public struct BalancedCapture: Hashable, Sendable { /// The name of the group, or nil if the group has no name. public var name: AST.Located? diff --git a/Sources/_RegexParser/Regex/AST/MatchingOptions.swift b/Sources/_RegexParser/Regex/AST/MatchingOptions.swift index d3dbc1666..072d6cfaa 100644 --- a/Sources/_RegexParser/Regex/AST/MatchingOptions.swift +++ b/Sources/_RegexParser/Regex/AST/MatchingOptions.swift @@ -11,8 +11,8 @@ extension AST { /// An option, written in source, that changes matching semantics. - public struct MatchingOption: Hashable { - public enum Kind { + public struct MatchingOption: Hashable, Sendable { + public enum Kind: Hashable, Sendable { // PCRE options case caseInsensitive // i case allowDuplicateGroupNames // J @@ -84,7 +84,7 @@ extension AST { } /// A sequence of matching options, written in source. - public struct MatchingOptionSequence: Hashable { + public struct MatchingOptionSequence: Hashable, Sendable { /// If the sequence starts with a caret '^', its source location, or nil /// otherwise. If this is set, it indicates that all the matching options /// are unset, except the ones in `adding`. @@ -143,10 +143,10 @@ extension AST { /// Unlike `MatchingOptionSequence`, /// these options must appear at the start of the pattern, /// and they apply to the entire pattern. - public struct GlobalMatchingOption: _ASTNode, Hashable { + public struct GlobalMatchingOption: _ASTNode, Hashable, Sendable { /// Determines the definition of a newline for the '.' character class and /// when parsing end-of-line comments. - public enum NewlineMatching: Hashable { + public enum NewlineMatching: Hashable, Sendable { /// (*CR*) case carriageReturnOnly @@ -166,14 +166,14 @@ extension AST { case nulCharacter } /// Determines what `\R` matches. - public enum NewlineSequenceMatching: Hashable { + public enum NewlineSequenceMatching: Hashable, Sendable { /// (*BSR_ANYCRLF) case anyCarriageReturnOrLinefeed /// (*BSR_UNICODE) case anyUnicode } - public enum Kind: Hashable { + public enum Kind: Hashable, Sendable { /// (*LIMIT_DEPTH=d) case limitDepth(Located) diff --git a/Sources/_RegexParser/Regex/AST/Quantification.swift b/Sources/_RegexParser/Regex/AST/Quantification.swift index c6d4f0101..3d2835b0c 100644 --- a/Sources/_RegexParser/Regex/AST/Quantification.swift +++ b/Sources/_RegexParser/Regex/AST/Quantification.swift @@ -10,7 +10,7 @@ //===----------------------------------------------------------------------===// extension AST { - public struct Quantification: Hashable { + public struct Quantification: Hashable, Sendable { public let amount: Located public let kind: Located @@ -36,7 +36,7 @@ extension AST { self.trivia = trivia } - public enum Amount: Hashable { + public enum Amount: Hashable, Sendable { case zeroOrMore // * case oneOrMore // + case zeroOrOne // ? @@ -46,7 +46,7 @@ extension AST { case range(Located, Located) // {n,m} } - public enum Kind: String, Hashable { + public enum Kind: String, Hashable, Sendable { case eager = "" case reluctant = "?" case possessive = "+" diff --git a/Sources/_RegexParser/Regex/Parse/SourceLocation.swift b/Sources/_RegexParser/Regex/Parse/SourceLocation.swift index eb51643bd..ab3aba0e8 100644 --- a/Sources/_RegexParser/Regex/Parse/SourceLocation.swift +++ b/Sources/_RegexParser/Regex/Parse/SourceLocation.swift @@ -11,7 +11,7 @@ extension Source { /// The location in the input of a parsed entity, presented as a region over the input - public struct Location: Hashable { + public struct Location: Hashable, Sendable { public var range: Range public var start: Source.Position { range.lowerBound } @@ -114,6 +114,7 @@ extension AST { } extension AST.Located: Equatable where T: Equatable {} extension AST.Located: Hashable where T: Hashable {} +extension AST.Located: Sendable where T: Sendable {} extension Source.LocatedError: CustomStringConvertible { public var description: String { diff --git a/Sources/_RegexParser/Utility/MissingUnicode.swift b/Sources/_RegexParser/Utility/MissingUnicode.swift index c61c78c46..9010b1710 100644 --- a/Sources/_RegexParser/Utility/MissingUnicode.swift +++ b/Sources/_RegexParser/Utility/MissingUnicode.swift @@ -19,7 +19,7 @@ extension Unicode { // other script types. /// Character script types. - public enum Script: String, Hashable, CaseIterable { + public enum Script: String, Hashable, CaseIterable, Sendable { case adlam = "Adlam" case ahom = "Ahom" case anatolianHieroglyphs = "Anatolian_Hieroglyphs" @@ -187,7 +187,7 @@ extension Unicode { /// POSIX character properties not already covered by general categories or /// binary properties. - public enum POSIXProperty: String, Hashable, CaseIterable { + public enum POSIXProperty: String, Hashable, CaseIterable, Sendable { case alnum = "alnum" case blank = "blank" case graph = "graph" @@ -204,7 +204,7 @@ extension Unicode { /// Unicode.GeneralCategory + cases for "meta categories" such as "L", which /// encompasses Lu | Ll | Lt | Lm | Lo. - public enum ExtendedGeneralCategory: String, Hashable, CaseIterable { + public enum ExtendedGeneralCategory: String, Hashable, CaseIterable, Sendable { case other = "C" case control = "Cc" case format = "Cf" @@ -254,7 +254,7 @@ extension Unicode { /// A list of Unicode properties that can either be true or false. /// /// https://www.unicode.org/Public/UCD/latest/ucd/PropertyAliases.txt - public enum BinaryProperty: String, Hashable, CaseIterable { + public enum BinaryProperty: String, Hashable, CaseIterable, Sendable { case asciiHexDigit = "ASCII_Hex_Digit" case alphabetic = "Alphabetic" case bidiControl = "Bidi_Control" @@ -326,7 +326,7 @@ extension Unicode { /// A list of unicode character blocks, including `No_Block`. /// https://www.unicode.org/Public/UCD/latest/ucd/Blocks.txt - public enum Block: String, Hashable, CaseIterable { + public enum Block: String, Hashable, CaseIterable, Sendable { /// 0000..007F; Basic Latin case basicLatin = "Basic_Latin" /// 0080..00FF; Latin-1 Supplement diff --git a/Sources/_StringProcessing/Regex/Core.swift b/Sources/_StringProcessing/Regex/Core.swift index 5c99e2ccd..5b975db1c 100644 --- a/Sources/_StringProcessing/Regex/Core.swift +++ b/Sources/_StringProcessing/Regex/Core.swift @@ -11,10 +11,9 @@ @_implementationOnly import _RegexParser - /// A type that represents a regular expression. @available(SwiftStdlib 5.7, *) -public protocol RegexComponent { +public protocol RegexComponent: Sendable { associatedtype RegexOutput var regex: Regex { get } } @@ -68,11 +67,12 @@ extension Regex { } } + @available(SwiftStdlib 5.7, *) extension Regex { /// A program representation that caches any lowered representation for /// execution. - internal class Program { + internal final class Program: @unchecked Sendable { /// The underlying IR. /// /// FIXME: If Regex is the unit of composition, then it should be a Node instead, @@ -80,8 +80,23 @@ extension Regex { /// likely, compilation/caching. let tree: DSLTree + private final class ProgramBox { + let value: MEProgram + init(_ value: MEProgram) { self.value = value } + } + + /// Do not use directly - all accesses must go through `loweredProgram`. + private var _loweredProgramStorage: AnyObject? = nil + /// The program for execution with the matching engine. - lazy private(set) var loweredProgram = try! Compiler(tree: tree).emit() + var loweredProgram: MEProgram { + if let loweredObject = _loweredProgramStorage as? ProgramBox { + return loweredObject.value + } + let lowered = try! Compiler(tree: tree).emit() + _stdlib_atomicInitializeARCRef(object: &_loweredProgramStorage, desired: ProgramBox(lowered)) + return lowered + } init(ast: AST) { self.tree = ast.dslTree diff --git a/Sources/_StringProcessing/Regex/DSLTree.swift b/Sources/_StringProcessing/Regex/DSLTree.swift index 8ca6dce8d..c3842a7b9 100644 --- a/Sources/_StringProcessing/Regex/DSLTree.swift +++ b/Sources/_StringProcessing/Regex/DSLTree.swift @@ -12,7 +12,7 @@ @_implementationOnly import _RegexParser @_spi(RegexBuilder) -public struct DSLTree { +public struct DSLTree: Sendable { var root: Node init(_ r: Node) { @@ -22,7 +22,7 @@ public struct DSLTree { extension DSLTree { @_spi(RegexBuilder) - public indirect enum Node { + public indirect enum Node: Sendable { /// Matches each node in order. /// /// ... | ... | ... @@ -102,7 +102,7 @@ extension DSLTree { extension DSLTree { @_spi(RegexBuilder) - public enum QuantificationKind { + public enum QuantificationKind: Sendable { /// The default quantification kind, as set by options. case `default` /// An explicitly chosen kind, overriding any options. @@ -120,7 +120,7 @@ extension DSLTree { } @_spi(RegexBuilder) - public struct CustomCharacterClass { + public struct CustomCharacterClass: Sendable { var members: [Member] var isInverted: Bool @@ -153,7 +153,7 @@ extension DSLTree { } @_spi(RegexBuilder) - public enum Member { + public enum Member: Sendable { case atom(Atom) case range(Atom, Atom) case custom(CustomCharacterClass) @@ -169,7 +169,7 @@ extension DSLTree { } @_spi(RegexBuilder) - public enum Atom { + public enum Atom: Sendable { case char(Character) case scalar(Unicode.Scalar) case any @@ -224,21 +224,21 @@ extension Unicode.GeneralCategory { // CollectionConsumer @_spi(RegexBuilder) -public typealias _ConsumerInterface = ( +public typealias _ConsumerInterface = @Sendable ( String, Range ) throws -> String.Index? // Type producing consume // TODO: better name @_spi(RegexBuilder) -public typealias _MatcherInterface = ( +public typealias _MatcherInterface = @Sendable ( String, String.Index, Range ) throws -> (String.Index, Any)? // Character-set (post grapheme segmentation) @_spi(RegexBuilder) public typealias _CharacterPredicateInterface = ( - (Character) -> Bool + @Sendable (Character) -> Bool ) /* @@ -375,7 +375,7 @@ extension DSLTree.Node { } @_spi(RegexBuilder) -public struct ReferenceID: Hashable, Equatable { +public struct ReferenceID: Hashable, Sendable { private static var counter: Int = 0 var base: Int @@ -386,10 +386,10 @@ public struct ReferenceID: Hashable, Equatable { } @_spi(RegexBuilder) -public struct CaptureTransform: Hashable, CustomStringConvertible { - public enum Closure { - case failable((Substring) throws -> Any?) - case nonfailable((Substring) throws -> Any) +public struct CaptureTransform: Hashable, CustomStringConvertible, Sendable { + public enum Closure: Sendable { + case failable(@Sendable (Substring) throws -> Any?) + case nonfailable(@Sendable (Substring) throws -> Any) } public let resultType: Any.Type public let closure: Closure @@ -401,14 +401,14 @@ public struct CaptureTransform: Hashable, CustomStringConvertible { public init( resultType: Any.Type, - _ closure: @escaping (Substring) throws -> Any + _ closure: @Sendable @escaping (Substring) throws -> Any ) { self.init(resultType: resultType, closure: .nonfailable(closure)) } public init( resultType: Any.Type, - _ closure: @escaping (Substring) throws -> Any? + _ closure: @Sendable @escaping (Substring) throws -> Any? ) { self.init(resultType: resultType, closure: .failable(closure)) } @@ -570,7 +570,7 @@ extension DSLTree { @_spi(RegexBuilder) public enum _AST { @_spi(RegexBuilder) - public struct GroupKind { + public struct GroupKind: Sendable { internal var ast: AST.Group.Kind public static var atomicNonCapturing: Self { @@ -585,12 +585,12 @@ extension DSLTree { } @_spi(RegexBuilder) - public struct ConditionKind { + public struct ConditionKind: Sendable { internal var ast: AST.Conditional.Condition.Kind } @_spi(RegexBuilder) - public struct QuantificationKind { + public struct QuantificationKind: Sendable { internal var ast: AST.Quantification.Kind public static var eager: Self { @@ -605,7 +605,7 @@ extension DSLTree { } @_spi(RegexBuilder) - public struct QuantificationAmount { + public struct QuantificationAmount: Sendable { internal var ast: AST.Quantification.Amount public static var zeroOrMore: Self { @@ -632,17 +632,17 @@ extension DSLTree { } @_spi(RegexBuilder) - public struct ASTNode { + public struct ASTNode: Sendable { internal var ast: AST.Node } @_spi(RegexBuilder) - public struct AbsentFunction { + public struct AbsentFunction: Sendable { internal var ast: AST.AbsentFunction } @_spi(RegexBuilder) - public struct AssertionKind { + public struct AssertionKind: Sendable { internal var ast: AST.Atom.AssertionKind public static func startOfSubject(_ inverted: Bool = false) -> Self { @@ -676,17 +676,17 @@ extension DSLTree { } @_spi(RegexBuilder) - public struct Reference { + public struct Reference: Sendable { internal var ast: AST.Reference } @_spi(RegexBuilder) - public struct MatchingOptionSequence { + public struct MatchingOptionSequence: Sendable { internal var ast: AST.MatchingOptionSequence } @_spi(RegexBuilder) - public struct Atom { + public struct Atom: Sendable { internal var ast: AST.Atom } } diff --git a/Sources/_StringProcessing/_CharacterClassModel.swift b/Sources/_StringProcessing/_CharacterClassModel.swift index 85dd1ca37..e026c4b11 100644 --- a/Sources/_StringProcessing/_CharacterClassModel.swift +++ b/Sources/_StringProcessing/_CharacterClassModel.swift @@ -16,7 +16,7 @@ // of parsing or to store in an AST @_spi(RegexBuilder) -public struct _CharacterClassModel: Hashable { +public struct _CharacterClassModel: Hashable, Sendable { /// The actual character class to match. var cc: Representation @@ -28,7 +28,7 @@ public struct _CharacterClassModel: Hashable { var isInverted: Bool = false // TODO: Split out builtin character classes into their own type? - public enum Representation: Hashable { + public enum Representation: Hashable, Sendable { /// Any character case any /// Any grapheme cluster @@ -54,14 +54,14 @@ public struct _CharacterClassModel: Hashable { case custom([CharacterSetComponent]) } - public enum SetOperator: Hashable { + public enum SetOperator: Hashable, Sendable { case subtraction case intersection case symmetricDifference } /// A binary set operation that forms a character class component. - public struct SetOperation: Hashable { + public struct SetOperation: Hashable, Sendable { var lhs: CharacterSetComponent var op: SetOperator var rhs: CharacterSetComponent @@ -78,7 +78,7 @@ public struct _CharacterClassModel: Hashable { } } - public enum CharacterSetComponent: Hashable { + public enum CharacterSetComponent: Hashable, Sendable { case character(Character) case range(ClosedRange) @@ -120,7 +120,7 @@ public struct _CharacterClassModel: Hashable { } } - enum MatchLevel { + enum MatchLevel: Hashable, Sendable { /// Match at the extended grapheme cluster level. case graphemeCluster /// Match at the Unicode scalar level. diff --git a/Tests/RegexTests/MatchTests.swift b/Tests/RegexTests/MatchTests.swift index ed16905b8..8cecd26c8 100644 --- a/Tests/RegexTests/MatchTests.swift +++ b/Tests/RegexTests/MatchTests.swift @@ -1645,5 +1645,35 @@ extension RegexTests { let scalarExpected: [Substring] = ["\u{FE0F}💖🧠", "🧠💖☕"] XCTAssertEqual(scalarMatches.map { $0.0 }, scalarExpected) } + + func testConcurrentAccess() async throws { + for _ in 0..<1000 { + let regex = try Regex(#"abc+d*e?"#) + let strings = [ + "abc", + "abccccccccdddddddddde", + "abcccce", + "abddddde", + ] + let matches = await withTaskGroup(of: Optional.Match>.self) { group -> [Regex.Match] in + var result: [Regex.Match] = [] + + for str in strings { + group.addTask { + str.firstMatch(of: regex) + } + } + + for await match in group { + guard let match = match else { continue } + result.append(match) + } + + return result + } + + XCTAssertEqual(matches.count, 3) + } + } }