@@ -446,6 +446,39 @@ func AssertDiagnostic<T: SyntaxProtocol>(
446446 }
447447}
448448
449+ class MutatedTreePrinter : SyntaxVisitor {
450+ private var mutations : [ Int : TokenSpec ] = [ : ]
451+ private var printedSource : [ UInt8 ] = [ ]
452+
453+ /// Prints `tree` by replacing the tokens whose offset is in `mutations` by
454+ /// a token that matches the corresponding `TokenSpec`.
455+ static func print( tree: Syntax , mutations: [ Int : TokenSpec ] ) -> [ UInt8 ] {
456+ let printer = MutatedTreePrinter ( mutations: mutations)
457+ printer. walk ( tree)
458+ return printer. printedSource
459+ }
460+
461+ private init ( mutations: [ Int : TokenSpec ] ) {
462+ self . mutations = mutations
463+ super. init ( viewMode: . sourceAccurate)
464+ }
465+
466+ override func visit( _ node: TokenSyntax ) -> SyntaxVisitorContinueKind {
467+ if let mutation = mutations [ node. positionAfterSkippingLeadingTrivia. utf8Offset] {
468+ let token = TokenSyntax (
469+ mutation. synthesizedTokenKind,
470+ leadingTrivia: node. leadingTrivia,
471+ trailingTrivia: node. trailingTrivia,
472+ presence: . present
473+ )
474+ printedSource. append ( contentsOf: token. syntaxTextBytes)
475+ return . skipChildren
476+ }
477+ printedSource. append ( contentsOf: node. syntaxTextBytes)
478+ return . skipChildren
479+ }
480+ }
481+
449482public struct AssertParseOptions : OptionSet {
450483 public var rawValue : UInt8
451484
@@ -489,38 +522,6 @@ func AssertParse(
489522 )
490523}
491524
492- /// Same as `AssertParse` overload with a `(String) -> S` `parse`,
493- /// constructing a `Parser` from the given `String` and passing that to
494- /// `parse` instead.
495- func AssertParse< S: SyntaxProtocol > (
496- _ markedSource: String ,
497- _ parse: ( inout Parser ) -> S ,
498- substructure expectedSubstructure: Syntax ? = nil ,
499- substructureAfterMarker: String = " START " ,
500- diagnostics expectedDiagnostics: [ DiagnosticSpec ] = [ ] ,
501- applyFixIts: [ String ] ? = nil ,
502- fixedSource expectedFixedSource: String ? = nil ,
503- options: AssertParseOptions = [ ] ,
504- file: StaticString = #file,
505- line: UInt = #line
506- ) {
507- AssertParse (
508- markedSource,
509- { ( source: String ) -> S in
510- var parser = Parser ( source)
511- return parse ( & parser)
512- } ,
513- substructure: expectedSubstructure,
514- substructureAfterMarker: substructureAfterMarker,
515- diagnostics: expectedDiagnostics,
516- applyFixIts: applyFixIts,
517- fixedSource: expectedFixedSource,
518- options: options,
519- file: file,
520- line: line
521- )
522- }
523-
524525/// Removes any test markers from `markedSource` (1) and parses the result
525526/// using `parse`. By default it only checks if the parsed syntax tree is
526527/// printable back to the origin source, ie. it round trips.
@@ -541,7 +542,7 @@ func AssertParse<S: SyntaxProtocol>(
541542/// this string.
542543func AssertParse< S: SyntaxProtocol > (
543544 _ markedSource: String ,
544- _ parse: ( String ) -> S ,
545+ _ parse: ( inout Parser ) -> S ,
545546 substructure expectedSubstructure: Syntax ? = nil ,
546547 substructureAfterMarker: String = " START " ,
547548 diagnostics expectedDiagnostics: [ DiagnosticSpec ] = [ ] ,
@@ -555,7 +556,18 @@ func AssertParse<S: SyntaxProtocol>(
555556 var ( markerLocations, source) = extractMarkers ( markedSource)
556557 markerLocations [ " START " ] = 0
557558
558- let tree : S = parse ( source)
559+ let enableTestCaseMutation = false
560+ // FIXME: Currently, tests are failing when we enable test case mutation.
561+ // Once all of those issues are fixed, this should become
562+ // let enableTestCaseMutation = ProcessInfo.processInfo.environment["SKIP_LONG_TESTS"] != "1"
563+
564+ var parser = Parser ( source)
565+ #if PARSER_ALTERNATE_TOKEN_INTROSPECTION
566+ if enableTestCaseMutation {
567+ parser. enableAlternativeTokenChoices ( )
568+ }
569+ #endif
570+ let tree : S = parse ( & parser)
559571
560572 // Round-trip
561573 AssertStringsEqualWithDiff (
@@ -615,4 +627,38 @@ func AssertParse<S: SyntaxProtocol>(
615627 line: line
616628 )
617629 }
630+
631+ #if PARSER_ALTERNATE_TOKEN_INTROSPECTION
632+ if enableTestCaseMutation {
633+ let mutations : [ ( offset: Int , replacement: TokenSpec ) ] = parser. alternativeTokenChoices. flatMap { offset, replacements in
634+ return replacements. map { ( offset, $0) }
635+ }
636+ DispatchQueue . concurrentPerform ( iterations: mutations. count) { index in
637+ let mutation = mutations [ index]
638+ let alternateSource = MutatedTreePrinter . print ( tree: Syntax ( tree) , mutations: [ mutation. offset: mutation. replacement] )
639+ alternateSource. withUnsafeBufferPointer { buf in
640+ let mutatedSource = String ( decoding: buf, as: UTF8 . self)
641+ // Check that we don't hit any assertions in the parser while parsing
642+ // the mutated source and that it round-trips
643+ var mutatedParser = Parser ( buf)
644+ let mutatedTree = parse ( & mutatedParser)
645+ AssertStringsEqualWithDiff (
646+ " \( mutatedTree) " ,
647+ mutatedSource,
648+ additionalInfo: """
649+ Mutated source failed to round-trip.
650+
651+ Mutated source:
652+ \( mutatedSource)
653+
654+ Actual syntax tree:
655+ \( mutatedTree. recursiveDescription)
656+ """ ,
657+ file: file,
658+ line: line
659+ )
660+ }
661+ }
662+ }
663+ #endif
618664}
0 commit comments