Skip to content

Commit e112a7e

Browse files
Clarify and add asserts
1 parent d801109 commit e112a7e

File tree

1 file changed

+35
-21
lines changed

1 file changed

+35
-21
lines changed

src/Compilers/CSharp/Portable/Parser/LanguageParser_InterpolatedString.cs

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)