/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/SyntaxHelpers.cs
326 строк
13 KB
AlekseyTs
Support `field` keyword in EE. (#80515)
04 окт 2025, 19:23
Не верифицирован
04 окт 2025, 19:23
b02727d
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.ObjectModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using InternalSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax; namespace Microsoft.CodeAnalysis.CSharp.ExpressionEvaluator { internal static class SyntaxHelpers { internal static readonly CSharpParseOptions PreviewParseOptions = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview); // Used to be LanguageVersionFacts.CurrentVersion /// <summary> /// Parse expression. Returns null if there are any errors. /// </summary> internal static ExpressionSyntax? ParseExpression( this string expr, bool isInFieldKeywordContext, DiagnosticBag diagnostics, bool allowFormatSpecifiers, out ReadOnlyCollection<string>? formatSpecifiers) { // Remove trailing semi-colon if any. This is to support copy/paste // of (simple cases of) RHS of assignment in Watch window, not to // allow arbitrary syntax after the semi-colon, not even comments. if (RemoveSemicolonIfAny(ref expr)) { // Format specifiers are not expected before a semi-colon. allowFormatSpecifiers = false; } var syntax = ParseDebuggerExpression(expr, consumeFullText: !allowFormatSpecifiers, isInFieldKeywordContext); diagnostics.AddRange(syntax.GetDiagnostics()); formatSpecifiers = null; if (allowFormatSpecifiers) { var builder = ArrayBuilder<string>.GetInstance(); if (ParseFormatSpecifiers(builder, expr, syntax.FullWidth, diagnostics) && builder.Count > 0) { formatSpecifiers = new ReadOnlyCollection<string>(builder.ToArray()); } builder.Free(); } return diagnostics.HasAnyErrors() ? null : syntax; } internal static ExpressionSyntax? ParseAssignment( this string target, string expr, bool isInFieldKeywordContext, DiagnosticBag diagnostics) { var text = SourceText.From(expr, encoding: null, SourceHashAlgorithms.Default); var expression = ParseDebuggerExpressionInternal(text, consumeFullText: true, isInFieldKeywordContext); // We're creating a SyntaxTree for just the RHS so that the Diagnostic spans for parse errors // will be correct (with respect to the original input text). If we ever expose a SemanticModel // for debugger expressions, we should use this SyntaxTree. var syntaxTree = expression.CreateSyntaxTree(text); diagnostics.AddRange(syntaxTree.GetDiagnostics()); if (diagnostics.HasAnyErrors()) { return null; } // Any Diagnostic spans produced in binding will be offset by the length of the "target" expression text. // If we want to support live squiggles in debugger windows, SemanticModel, etc, we'll want to address this. var targetSyntax = ParseDebuggerExpressionInternal(SourceText.From(target, encoding: null, SourceHashAlgorithms.Default), consumeFullText: true, isInFieldKeywordContext); Debug.Assert(!targetSyntax.GetDiagnostics().Any(), "The target of an assignment should never contain Diagnostics if we're being allowed to assign to it in the debugger."); var assignment = InternalSyntax.SyntaxFactory.AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, targetSyntax, InternalSyntax.SyntaxFactory.Token(SyntaxKind.EqualsToken), expression); return assignment.MakeDebuggerExpression(SourceText.From(assignment.ToString(), encoding: null, SourceHashAlgorithms.Default)); } /// <summary> /// Parse statement. Returns null if there are any errors. /// </summary> internal static StatementSyntax? ParseStatement( this string expr, bool isInFieldKeywordContext, DiagnosticBag diagnostics) { var syntax = ParseDebuggerStatement(expr, isInFieldKeywordContext); diagnostics.AddRange(syntax.GetDiagnostics()); return diagnostics.HasAnyErrors() ? null : syntax; } /// <summary> /// Return set of identifier tokens, with leading and /// trailing spaces and comma separators removed. /// </summary> private static bool ParseFormatSpecifiers( ArrayBuilder<string> builder, string expr, int offset, DiagnosticBag diagnostics) { bool expectingComma = true; int start = -1; int n = expr.Length; for (; offset < n; offset++) { var c = expr[offset]; if (SyntaxFacts.IsWhitespace(c) || (c == ',')) { if (start >= 0) { var token = expr.Substring(start, offset - start); if (expectingComma) { ReportInvalidFormatSpecifier(token, diagnostics); return false; } builder.Add(token); start = -1; expectingComma = (c != ','); } else if (c == ',') { if (!expectingComma) { ReportInvalidFormatSpecifier(",", diagnostics); return false; } expectingComma = false; } } else if (start < 0) { start = offset; } } if (start >= 0) { var token = expr.Substring(start); if (expectingComma) { ReportInvalidFormatSpecifier(token, diagnostics); return false; } builder.Add(token); } else if (!expectingComma) { ReportInvalidFormatSpecifier(",", diagnostics); return false; } // Verify format specifiers are valid identifiers. foreach (var token in builder) { if (!token.All(SyntaxFacts.IsIdentifierPartCharacter)) { ReportInvalidFormatSpecifier(token, diagnostics); return false; } } return true; } private static void ReportInvalidFormatSpecifier(string token, DiagnosticBag diagnostics) { diagnostics.Add(ErrorCode.ERR_InvalidSpecifier, Location.None, token); } private static bool RemoveSemicolonIfAny(ref string str) { for (int i = str.Length - 1; i >= 0; i--) { var c = str[i]; if (c == ';') { str = str.Substring(0, i); return true; } if (!SyntaxFacts.IsWhitespace(c)) { break; } } return false; } private static ExpressionSyntax ParseDebuggerExpression(string text, bool consumeFullText, bool isInFieldKeywordContext) { var source = SourceText.From(text, encoding: null, SourceHashAlgorithms.Default); var expression = ParseDebuggerExpressionInternal(source, consumeFullText, isInFieldKeywordContext); return expression.MakeDebuggerExpression(source); } private static InternalSyntax.ExpressionSyntax ParseDebuggerExpressionInternal(SourceText source, bool consumeFullText, bool isInFieldKeywordContext) { using var lexer = new InternalSyntax.Lexer(source, PreviewParseOptions, allowPreprocessorDirectives: false); using var parser = new InternalSyntax.LanguageParser(lexer, oldTree: null, changes: null, lexerMode: InternalSyntax.LexerMode.DebuggerSyntax); parser.IsInFieldKeywordContext = isInFieldKeywordContext; var node = parser.ParseExpression(); if (consumeFullText) node = parser.ConsumeUnexpectedTokens(node); return node; } private static StatementSyntax ParseDebuggerStatement(string text, bool isInFieldKeywordContext) { var source = SourceText.From(text, encoding: null, SourceHashAlgorithms.Default); using var lexer = new InternalSyntax.Lexer(source, PreviewParseOptions); using var parser = new InternalSyntax.LanguageParser(lexer, oldTree: null, changes: null, lexerMode: InternalSyntax.LexerMode.DebuggerSyntax); parser.IsInFieldKeywordContext = isInFieldKeywordContext; var statement = parser.ParseStatement(); var syntaxTree = statement.CreateSyntaxTree(source); return (StatementSyntax)syntaxTree.GetRoot(); } private static SyntaxTree CreateSyntaxTree(this InternalSyntax.CSharpSyntaxNode root, SourceText text) { return CSharpSyntaxTree.CreateForDebugger((CSharpSyntaxNode)root.CreateRed(), text, PreviewParseOptions); } private static ExpressionSyntax MakeDebuggerExpression(this InternalSyntax.ExpressionSyntax expression, SourceText text) { var syntaxTree = InternalSyntax.SyntaxFactory.ExpressionStatement(attributeLists: default, expression, InternalSyntax.SyntaxFactory.Token(SyntaxKind.SemicolonToken)).CreateSyntaxTree(text); return ((ExpressionStatementSyntax)syntaxTree.GetRoot()).Expression; } internal static string EscapeKeywordIdentifiers(string identifier) { return SyntaxFacts.IsKeywordKind(SyntaxFacts.GetKeywordKind(identifier)) ? "@" + identifier : identifier; } /// <remarks> /// We don't want to use the real lexer because we want to treat keywords as identifiers. /// Since the inputs are so simple, we'll just do the lexing ourselves. /// </remarks> internal static bool TryParseDottedName(string input, [NotNullWhen(true)] out NameSyntax? output) { var pooled = PooledStringBuilder.GetInstance(); try { var builder = pooled.Builder; output = null; foreach (var ch in input) { if (builder.Length == 0) { if (!SyntaxFacts.IsIdentifierStartCharacter(ch)) { output = null; return false; } builder.Append(ch); } else if (ch == '.') { var identifierName = SyntaxFactory.IdentifierName(builder.ToString()); builder.Clear(); output = output == null ? (NameSyntax)identifierName : SyntaxFactory.QualifiedName(output, identifierName); } else if (SyntaxFacts.IsIdentifierPartCharacter(ch)) { builder.Append(ch); } else { output = null; return false; } } // There must be at least one character in the last identifier. if (builder.Length == 0) { output = null; return false; } var finalIdentifierName = SyntaxFactory.IdentifierName(builder.ToString()); output = output == null ? (NameSyntax)finalIdentifierName : SyntaxFactory.QualifiedName(output, finalIdentifierName); return true; } finally { pooled.Free(); } } internal static NameSyntax PrependExternAlias(IdentifierNameSyntax externAliasSyntax, NameSyntax nameSyntax) { if (nameSyntax is QualifiedNameSyntax qualifiedNameSyntax) { return SyntaxFactory.QualifiedName( PrependExternAlias(externAliasSyntax, qualifiedNameSyntax.Left), qualifiedNameSyntax.Right); } else { return SyntaxFactory.AliasQualifiedName(externAliasSyntax, (SimpleNameSyntax)nameSyntax); } } } }