@@ -25,30 +25,26 @@ private LiteralExpressionSyntax ParseRawStringToken()
2525 // raw string as an interpolated string with no $'s and no holes, and then extract out the content token
2626 // from that.
2727
28- var originalText = originalToken . Text ;
29- Debug . Assert ( originalText is [ '"' , '"' , '"' , ..] ) ;
28+ Debug . Assert ( originalToken . Text is [ '"' , '"' , '"' , ..] ) ;
3029
31- var interpolatedString = ParseInterpolatedOrRawStringToken (
32- originalToken , originalText , originalText . AsSpan ( ) , isInterpolatedString : false ) ;
30+ var interpolatedString = ParseInterpolatedOrRawStringToken ( originalToken , isInterpolatedString : false ) ;
3331
3432 // Because there are no actual interpolations, we expect to only see a single text content node containing
3533 // the interpreted value of the raw string.
3634 Debug . Assert ( interpolatedString . StringStartToken . Kind is SyntaxKind . InterpolatedSingleLineRawStringStartToken or SyntaxKind . InterpolatedMultiLineRawStringStartToken ) ;
3735 Debug . Assert ( interpolatedString . Contents is [ InterpolatedStringTextSyntax ] ) ;
3836
3937 var interpolatedText = ( InterpolatedStringTextSyntax ) interpolatedString . Contents [ 0 ] ! ;
40- var textToken = interpolatedText . TextToken ;
4138
4239 // Based on how ParseInterpolatedOrRawStringToken works we should never get a diagnostic on the actual
4340 // interpolated string text token (since we create it, and immediately add it to the InterpolatedStringText
44- // node).
45- Debug . Assert ( ! textToken . ContainsDiagnostics ) ;
41+ // node). Instead,
42+ Debug . Assert ( ! interpolatedText . TextToken . ContainsDiagnostics ) ;
4643
4744 var diagnosticsBuilder = ArrayBuilder < DiagnosticInfo > . GetInstance ( ) ;
48- // Move any diagnostics on the original token to the new token.
49- // diagnosticsBuilder.AddRange(token.GetDiagnostics());
5045 // And any diagnostics from the interpolated string as a whole.
5146 diagnosticsBuilder . AddRange ( interpolatedString . GetDiagnostics ( ) ) ;
47+
5248 // If there are any diagnostics on the interpolated text node, move those over too. However, move them as
5349 // they are relative to the text token, and now need to be relative to the start of the token as a whole.
5450 var textTokenDiagnostics = MoveDiagnostics ( interpolatedText . GetDiagnostics ( ) , interpolatedString . StringStartToken . Width ) ;
@@ -72,19 +68,20 @@ private LiteralExpressionSyntax ParseRawStringToken()
7268 string getTokenValue ( )
7369 {
7470 if ( diagnosticsBuilder . Count == 0 )
75- return textToken . GetValueText ( ) ;
71+ return interpolatedText . TextToken . GetValueText ( ) ;
7672
7773 // Preserve what the lexer used to do here. In the presence of any diagnostics, the text of the raw
7874 // string minus the starting quotes is used as the value.
7975 var startIndex = 0 ;
76+ var originalText = originalToken . Text ;
8077 while ( startIndex < originalText . Length && originalText [ startIndex ] is '"' )
8178 startIndex ++ ;
8279
8380 return originalText [ startIndex ..] ;
8481 }
8582 }
8683
87- private ExpressionSyntax ParseInterpolatedStringToken ( )
84+ private InterpolatedStringExpressionSyntax ParseInterpolatedStringToken ( )
8885 {
8986 // We don't want to make the scanner stateful (between tokens) if we can possibly avoid it.
9087 // The approach implemented here is
@@ -109,19 +106,24 @@ private ExpressionSyntax ParseInterpolatedStringToken()
109106 Debug . Assert ( this . CurrentToken . Kind == SyntaxKind . InterpolatedStringToken ) ;
110107 var originalToken = this . EatToken ( ) ;
111108
112- var originalText = originalToken . ValueText ; // this is actually the source text
113- Debug . Assert ( originalText [ 0 ] == '$' || originalText [ 0 ] == '@' ) ;
109+ Debug . Assert ( originalToken . Text [ 0 ] is '$' or '@' ) ;
114110
115- return ParseInterpolatedOrRawStringToken (
116- originalToken , originalText , originalText . AsSpan ( ) , isInterpolatedString : true ) ;
111+ return ParseInterpolatedOrRawStringToken ( originalToken , isInterpolatedString : true ) ;
117112 }
118113
114+ /// <summary>
115+ /// Takes the token produced by the lexer for an (raw or regular) interpolated string or non-interpolated raw
116+ /// string literal and creates an actual parsed <see cref="InterpolatedStringExpressionSyntax"/> for the syntax
117+ /// tree. For an interpolated string, this will now contain all the holes parsed out as well. For a raw string
118+ /// this will contain a single <see cref="InterpolatedStringTextSyntax"/> for the contents of the raw string.
119+ /// </summary>
119120 private InterpolatedStringExpressionSyntax ParseInterpolatedOrRawStringToken (
120121 SyntaxToken originalToken ,
121- string originalText ,
122- ReadOnlySpan < char > originalTextSpan ,
123122 bool isInterpolatedString )
124123 {
124+ var originalText = originalToken . Text ;
125+ var originalTextSpan = originalText . AsSpan ( ) ;
126+
125127 // compute the positions of the interpolations in the original string literal, if there was an error or not,
126128 // and where the open and close quotes can be found.
127129 var interpolations = ArrayBuilder < Lexer . Interpolation > . GetInstance ( ) ;
@@ -133,12 +135,22 @@ private InterpolatedStringExpressionSyntax ParseInterpolatedOrRawStringToken(
133135 var needsDedentation = kind == Lexer . InterpolatedStringKind . MultiLineRaw && error == null ;
134136
135137 var result = SyntaxFactory . InterpolatedStringExpression ( getOpenQuote ( ) , getContent ( originalTextSpan ) , getCloseQuote ( ) ) ;
138+ Debug . Assert ( originalToken . ToFullString ( ) == result . ToFullString ( ) ) ; // yield from text equals yield from node
139+
140+ #if DEBUG
141+ // None of the added text tokens should have diagnostics. Any diagnostics should be on their containing
142+ // InterpolatedStringTextSyntax node instead.
143+ foreach ( var content in result . Contents )
144+ {
145+ if ( content is InterpolatedStringTextSyntax interpolatedText )
146+ Debug . Assert ( ! interpolatedText . TextToken . ContainsDiagnostics ) ;
147+ }
148+ #endif
136149
137- interpolations . Free ( ) ;
138150 if ( error != null )
139151 result = result . WithDiagnosticsGreen ( [ error ] ) ;
140152
141- Debug . Assert ( originalToken . ToFullString ( ) == result . ToFullString ( ) ) ; // yield from text equals yield from node
153+ interpolations . Free ( ) ;
142154 return result ;
143155
144156 void rescanInterpolation ( out Lexer . InterpolatedStringKind kind , out SyntaxDiagnosticInfo ? error , out Range openQuoteRange , ArrayBuilder < Lexer . Interpolation > interpolations , out Range closeQuoteRange )
@@ -190,7 +202,7 @@ CodeAnalysis.Syntax.InternalSyntax.SyntaxList<InterpolatedStringContentSyntax> g
190202 // Make sure the interpolation starts at the right location.
191203 var indentationError = getInterpolationIndentationError ( indentationWhitespace , interpolation ) ;
192204 if ( indentationError != null )
193- interpolationNode = interpolationNode . WithDiagnosticsGreen ( new [ ] { indentationError } ) ;
205+ interpolationNode = interpolationNode . WithDiagnosticsGreen ( [ indentationError ] ) ;
194206
195207 builder . Add ( interpolationNode ) ;
196208 currentContentStart = interpolation . CloseBraceRange . End ;
@@ -312,11 +324,13 @@ ReadOnlySpan<char> getIndentationWhitespace(ReadOnlySpan<char> originalTextSpan)
312324 var textString = text . ToString ( ) ;
313325 var valueString = indentationError != null ? textString : content . ToString ( ) ;
314326
327+ // Note: we place errors on the InterpolatedStringText node itself, not on the token. This is an
328+ // invariant that higher up callers can depend on.
315329 var node = SyntaxFactory . InterpolatedStringText (
316330 SyntaxFactory . Literal ( leading : null , textString , SyntaxKind . InterpolatedStringTextToken , valueString , trailing : null ) ) ;
317331
318332 return indentationError != null
319- ? node . WithDiagnosticsGreen ( new [ ] { indentationError } )
333+ ? node . WithDiagnosticsGreen ( [ indentationError ] )
320334 : node ;
321335 }
322336
0 commit comments