/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_StringInterpolation.cs
301 строка
15 KB
AlekseyTs
Do not use overload resolution while lowering interpolated strings (#75341)
08 окт 2024, 00:43
Не верифицирован
08 окт 2024, 00:43
b7b39ca
Код
Авторство
О чём код?
// 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.Immutable; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; using System.Diagnostics; using System.Linq; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { internal sealed partial class LocalRewriter { private BoundExpression RewriteInterpolatedStringConversion(BoundConversion conversion) { Debug.Assert(conversion.ConversionKind == ConversionKind.InterpolatedString); var interpolatedString = (BoundInterpolatedString)conversion.Operand; Debug.Assert(interpolatedString.InterpolationData is { Construction: not null }); return VisitExpression(interpolatedString.InterpolationData.GetValueOrDefault().Construction); } /// <summary> /// Rewrites the given interpolated string to the set of handler creation and Append calls, returning an array builder of the append calls and the result /// local temp. /// </summary> /// <remarks>Caller is responsible for freeing the ArrayBuilder</remarks> private InterpolationHandlerResult RewriteToInterpolatedStringHandlerPattern(InterpolatedStringHandlerData data, ImmutableArray<BoundExpression> parts, SyntaxNode syntax) { Debug.Assert(data.BuilderType is not null); Debug.Assert(data.ReceiverPlaceholder is not null); Debug.Assert(parts.All(static p => p is BoundCall or BoundDynamicInvocation)); var builderTempSymbol = _factory.InterpolatedStringHandlerLocal(data.BuilderType, syntax); BoundLocal builderTemp = _factory.Local(builderTempSymbol); // var handler = new HandlerType(baseStringLength, numFormatHoles, ...InterpolatedStringHandlerArgumentAttribute parameters, <optional> out bool appendShouldProceed); var construction = (BoundObjectCreationExpression)data.Construction; BoundLocal? appendShouldProceedLocal = null; if (data.HasTrailingHandlerValidityParameter) { #if DEBUG for (int i = construction.ArgumentRefKindsOpt.Length - 1; i >= 0; i--) { if (construction.ArgumentRefKindsOpt[i] == RefKind.Out) { break; } Debug.Assert(construction.ArgumentRefKindsOpt[i] == RefKind.None); Debug.Assert(construction.DefaultArguments[i]); } #endif BoundInterpolatedStringArgumentPlaceholder trailingParameter = data.ArgumentPlaceholders[^1]; TypeSymbol localType = trailingParameter.Type; Debug.Assert(localType.SpecialType == SpecialType.System_Boolean); var outLocal = _factory.SynthesizedLocal(localType); appendShouldProceedLocal = _factory.Local(outLocal); AddPlaceholderReplacement(trailingParameter, appendShouldProceedLocal); } var handlerConstructionAssignment = _factory.AssignmentExpression(builderTemp, (BoundExpression)VisitObjectCreationExpression(construction)); AddPlaceholderReplacement(data.ReceiverPlaceholder, builderTemp); bool usesBoolReturns = data.UsesBoolReturns; var resultExpressions = ArrayBuilder<BoundExpression>.GetInstance(parts.Length + 1); foreach (var part in parts) { if (part is BoundCall call) { Debug.Assert(call.Type.SpecialType == SpecialType.System_Boolean == usesBoolReturns); resultExpressions.Add((BoundExpression)VisitCall(call)); } else if (part is BoundDynamicInvocation dynamicInvocation) { resultExpressions.Add(VisitDynamicInvocation(dynamicInvocation, resultDiscarded: !usesBoolReturns)); } else { throw ExceptionUtilities.UnexpectedValue(part.Kind); } } RemovePlaceholderReplacement(data.ReceiverPlaceholder); if (appendShouldProceedLocal is not null) { RemovePlaceholderReplacement(data.ArgumentPlaceholders[^1]); } if (usesBoolReturns) { // We assume non-bool returns if there was no parts to the string, and code below is predicated on that. Debug.Assert(!parts.IsEmpty); // Start the sequence with appendProceedLocal, if appropriate BoundExpression? currentExpression = appendShouldProceedLocal; var boolType = _compilation.GetSpecialType(SpecialType.System_Boolean); foreach (var appendCall in resultExpressions) { var actualCall = appendCall; if (actualCall.Type!.IsDynamic()) { actualCall = _dynamicFactory.MakeDynamicConversion(actualCall, isExplicit: false, isArrayIndex: false, isChecked: false, boolType).ToExpression(); } // previousAppendCalls && appendCall currentExpression = currentExpression is null ? actualCall : _factory.LogicalAnd(currentExpression, actualCall); } Debug.Assert(currentExpression != null); resultExpressions.Clear(); resultExpressions.Add(handlerConstructionAssignment); resultExpressions.Add(currentExpression); } else if (appendShouldProceedLocal is not null && resultExpressions.Count > 0) { // appendCalls as expressionStatements var appendCallsStatements = resultExpressions.SelectAsArray(static (appendCall, @this) => (BoundStatement)@this._factory.ExpressionStatement(appendCall), this); resultExpressions.Free(); // if (appendShouldProceedLocal) { appendCallsStatements } var resultIf = _factory.If(appendShouldProceedLocal, _factory.StatementList(appendCallsStatements)); return new InterpolationHandlerResult(ImmutableArray.Create(_factory.ExpressionStatement(handlerConstructionAssignment), resultIf), builderTemp, appendShouldProceedLocal.LocalSymbol, this); } else { resultExpressions.Insert(0, handlerConstructionAssignment); } return new InterpolationHandlerResult(resultExpressions.ToImmutableAndFree(), builderTemp, appendShouldProceedLocal?.LocalSymbol, this); } public override BoundNode VisitInterpolatedString(BoundInterpolatedString node) { Debug.Assert(node.Type is { SpecialType: SpecialType.System_String }); // if target-converted, we should not get here. if (node.InterpolationData is InterpolatedStringHandlerData { BuilderType: not null } data) { return LowerPartsToString(data, node.Parts, node.Syntax, node.Type); } else if (node.InterpolationData is null) { // All fill-ins, if any, are strings, and none of them have alignment or format specifiers. // We can lower to a more efficient string concatenation // The normal pattern for lowering is to lower subtrees before the enclosing tree. However in this case // we want to lower the entire concatenation so we get the optimizations done by that lowering (e.g. constant folding). int length = node.Parts.Length; if (length == 0) { // $"" -> "" return _factory.StringLiteral(""); } BoundExpression? result = null; for (int i = 0; i < length; i++) { var part = node.Parts[i]; if (part is BoundStringInsert fillin) { // this is one of the filled-in expressions part = fillin.Value; } else { // this is one of the literal parts Debug.Assert(part is BoundLiteral && part.ConstantValueOpt?.StringValue is not null); part = _factory.StringLiteral(part.ConstantValueOpt.StringValue); } result = result == null ? part : _factory.Binary(BinaryOperatorKind.StringConcatenation, node.Type, result, part); } Debug.Assert(result is not null); // We need to ensure that the result of the interpolated string is not null. If the single part has a non-null constant value // or is itself an interpolated string (which by proxy cannot be null), then there's nothing else that needs to be done. Otherwise, // we need to test for null and ensure "" if it is. if (length == 1 && result is not ({ Kind: BoundKind.InterpolatedString } or { ConstantValueOpt.IsString: true })) { Debug.Assert(result is not null); Debug.Assert(result.Type is not null); Debug.Assert(result.Type.SpecialType == SpecialType.System_String || result.Type.IsErrorType()); var placeholder = new BoundValuePlaceholder(result.Syntax, result.Type); result = new BoundNullCoalescingOperator(result.Syntax, result, _factory.StringLiteral(""), leftPlaceholder: placeholder, leftConversion: placeholder, BoundNullCoalescingOperatorResultKind.LeftType, @checked: false, result.Type) { WasCompilerGenerated = true }; } return VisitExpression(result); } else { // // We lower an interpolated string into an invocation of String.Format. For example, we translate the expression // // $"Jenny don\'t change your number { 8675309 }" // // into // // String.Format("Jenny don\'t change your number {0}", new object[] { 8675309 }) // Debug.Assert(node.InterpolationData is { Construction: not null }); return VisitExpression(node.InterpolationData.GetValueOrDefault().Construction); } } private BoundExpression LowerPartsToString(InterpolatedStringHandlerData data, ImmutableArray<BoundExpression> parts, SyntaxNode syntax, TypeSymbol type) { // If we can lower to the builder pattern, do so. InterpolationHandlerResult result = RewriteToInterpolatedStringHandlerPattern(data, parts, syntax); // resultTemp = builderTemp.ToStringAndClear(); var toStringAndClear = (MethodSymbol)Binder.GetWellKnownTypeMember(_compilation, WellKnownMember.System_Runtime_CompilerServices_DefaultInterpolatedStringHandler__ToStringAndClear, _diagnostics, syntax: syntax); BoundExpression toStringAndClearCall = toStringAndClear is not null ? BoundCall.Synthesized(syntax, result.HandlerTemp, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, toStringAndClear) : new BoundBadExpression(syntax, LookupResultKind.Empty, symbols: ImmutableArray<Symbol?>.Empty, childBoundNodes: ImmutableArray<BoundExpression>.Empty, type); return result.WithFinalResult(toStringAndClearCall); } [Conditional("DEBUG")] private static void AssertNoImplicitInterpolatedStringHandlerConversions(ImmutableArray<BoundExpression> arguments, bool allowConversionsWithNoContext = false) { if (allowConversionsWithNoContext) { foreach (var arg in arguments) { if (arg is BoundConversion { Conversion: { Kind: ConversionKind.InterpolatedStringHandler }, ExplicitCastInCode: false, Operand: var operand }) { var data = operand.GetInterpolatedStringHandlerData(); Debug.Assert(((BoundObjectCreationExpression)data.Construction).Arguments.All( a => a is BoundInterpolatedStringArgumentPlaceholder { ArgumentIndex: BoundInterpolatedStringArgumentPlaceholder.TrailingConstructorValidityParameter } or not BoundInterpolatedStringArgumentPlaceholder)); } } } else { Debug.Assert(arguments.All(arg => arg is not BoundConversion { Conversion: { IsInterpolatedStringHandler: true }, ExplicitCastInCode: false })); } } private readonly struct InterpolationHandlerResult { private readonly ImmutableArray<BoundStatement> _statements; private readonly ImmutableArray<BoundExpression> _expressions; private readonly LocalRewriter _rewriter; private readonly LocalSymbol? _outTemp; public readonly BoundLocal HandlerTemp; public InterpolationHandlerResult(ImmutableArray<BoundStatement> statements, BoundLocal handlerTemp, LocalSymbol outTemp, LocalRewriter rewriter) { _statements = statements; _expressions = default; _outTemp = outTemp; HandlerTemp = handlerTemp; _rewriter = rewriter; } public InterpolationHandlerResult(ImmutableArray<BoundExpression> expressions, BoundLocal handlerTemp, LocalSymbol? outTemp, LocalRewriter rewriter) { _statements = default; _expressions = expressions; _outTemp = outTemp; HandlerTemp = handlerTemp; _rewriter = rewriter; } public BoundExpression WithFinalResult(BoundExpression result) { Debug.Assert(_statements.IsDefault ^ _expressions.IsDefault); var locals = _outTemp != null ? ImmutableArray.Create(HandlerTemp.LocalSymbol, _outTemp) : ImmutableArray.Create(HandlerTemp.LocalSymbol); if (_statements.IsDefault) { return _rewriter._factory.Sequence(locals, _expressions, result); } else { _rewriter._needsSpilling = true; return _rewriter._factory.SpillSequence(locals, _statements, result); } } } } }