/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Binder/Binder_TupleOperators.cs
430 строк
22 KB
Miha Zupan
Replace string.Compare == 0 patterns with Equals/StartsWith calls (#84036)
16 июн 2026, 19:27
Не верифицирован
16 июн 2026, 19:27
6128161
Код
Авторство
О чём код?
// 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. #nullable disable using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { internal partial class Binder { /// <summary> /// If the left and right are tuples of matching cardinality, we'll try to bind the operator element-wise. /// When that succeeds, the element-wise conversions are collected. We keep them for semantic model. /// The element-wise binary operators are collected and stored as a tree for lowering. /// </summary> private BoundTupleBinaryOperator BindTupleBinaryOperator(BinaryExpressionSyntax node, BinaryOperatorKind kind, BoundExpression left, BoundExpression right, BindingDiagnosticBag diagnostics) { TupleBinaryOperatorInfo.Multiple operators = BindTupleBinaryOperatorNestedInfo(node, kind, left, right, diagnostics); BoundExpression convertedLeft = ApplyConvertedTypes(left, operators, isRight: false, diagnostics); BoundExpression convertedRight = ApplyConvertedTypes(right, operators, isRight: true, diagnostics); TypeSymbol resultType = GetSpecialType(SpecialType.System_Boolean, diagnostics, node); return new BoundTupleBinaryOperator(node, convertedLeft, convertedRight, kind, operators, resultType); } private BoundExpression ApplyConvertedTypes(BoundExpression expr, TupleBinaryOperatorInfo @operator, bool isRight, BindingDiagnosticBag diagnostics) { TypeSymbol convertedType = isRight ? @operator.RightConvertedTypeOpt : @operator.LeftConvertedTypeOpt; if (convertedType is null) { // Note: issues with default will already have been reported by BindSimpleBinaryOperator (ie. we couldn't find a suitable element-wise operator) if (@operator.InfoKind == TupleBinaryOperatorInfoKind.Multiple && expr is BoundTupleLiteral tuple) { // Although the tuple will remain typeless, we'll give elements converted types as possible var multiple = (TupleBinaryOperatorInfo.Multiple)@operator; if (multiple.Operators.Length == 0) { return BindToNaturalType(expr, diagnostics, reportNoTargetType: false); } ImmutableArray<BoundExpression> arguments = tuple.Arguments; int length = arguments.Length; Debug.Assert(length == multiple.Operators.Length); var builder = ArrayBuilder<BoundExpression>.GetInstance(length); for (int i = 0; i < length; i++) { builder.Add(ApplyConvertedTypes(arguments[i], multiple.Operators[i], isRight, diagnostics)); } return new BoundConvertedTupleLiteral( tuple.Syntax, tuple, wasTargetTyped: false, builder.ToImmutableAndFree(), tuple.ArgumentNamesOpt, tuple.InferredNamesOpt, tuple.Type, tuple.HasErrors); } // This element isn't getting a converted type return BindToNaturalType(expr, diagnostics, reportNoTargetType: false); } // We were able to determine a converted type (for this tuple literal or element), we can just convert to it return GenerateConversionForAssignment(convertedType, expr, diagnostics); } /// <summary> /// Binds: /// 1. dynamically, if either side is dynamic /// 2. as tuple binary operator, if both sides are tuples of matching cardinalities /// 3. as regular binary operator otherwise /// </summary> private TupleBinaryOperatorInfo BindTupleBinaryOperatorInfo(BinaryExpressionSyntax node, BinaryOperatorKind kind, BoundExpression left, BoundExpression right, BindingDiagnosticBag diagnostics) { TypeSymbol leftType = left.Type; TypeSymbol rightType = right.Type; if ((object)leftType != null && leftType.IsDynamic() || (object)rightType != null && rightType.IsDynamic()) { return BindTupleDynamicBinaryOperatorSingleInfo(node, kind, left, right, diagnostics); } if (IsTupleBinaryOperation(left, right)) { return BindTupleBinaryOperatorNestedInfo(node, kind, left, right, diagnostics); } BoundExpression comparison = BindSimpleBinaryOperator(node, diagnostics, left, right, leaveUnconvertedIfInterpolatedString: false); switch (comparison) { case BoundLiteral _: // this case handles `null == null` and the like return new TupleBinaryOperatorInfo.NullNull(kind); case BoundBinaryOperator binary: PrepareBoolConversionAndTruthOperator(binary.Type, node, kind, diagnostics, out BoundExpression conversionIntoBoolOperator, out BoundValuePlaceholder conversionIntoBoolOperatorPlaceholder, out UnaryOperatorSignature boolOperator); CheckConstraintLanguageVersionAndRuntimeSupportForOperator(node, boolOperator.Method, isUnsignedRightShift: false, boolOperator.ConstrainedToTypeOpt, diagnostics); Debug.Assert(!binary.OperatorKind.IsDynamic()); return new TupleBinaryOperatorInfo.Single(binary.Left.Type, binary.Right.Type, binary.OperatorKind, binary.BinaryOperatorMethod, binary.ConstrainedToType, conversionIntoBoolOperatorPlaceholder, conversionIntoBoolOperator, boolOperator); default: throw ExceptionUtilities.UnexpectedValue(comparison); } } /// <summary> /// If an element-wise binary operator returns a non-bool type, we will either: /// - prepare a conversion to bool if one exists /// - prepare a truth operator: op_false in the case of an equality (<c>a == b</c> will be lowered to <c>!((a == b).op_false)</c>) or op_true in the case of inequality, /// with the conversion being used for its input. /// </summary> private void PrepareBoolConversionAndTruthOperator(TypeSymbol type, BinaryExpressionSyntax node, BinaryOperatorKind binaryOperator, BindingDiagnosticBag diagnostics, out BoundExpression conversionForBool, out BoundValuePlaceholder conversionForBoolPlaceholder, out UnaryOperatorSignature boolOperator) { // Is the operand implicitly convertible to bool? CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); TypeSymbol boolean = GetSpecialType(SpecialType.System_Boolean, diagnostics, node); Conversion conversion = this.Conversions.ClassifyImplicitConversionFromType(type, boolean, ref useSiteInfo); diagnostics.Add(node, useSiteInfo); if (conversion.IsImplicit) { conversionForBoolPlaceholder = new BoundValuePlaceholder(node, type).MakeCompilerGenerated(); conversionForBool = CreateConversion(node, conversionForBoolPlaceholder, conversion, isCast: false, conversionGroupOpt: null, InConversionGroupFlags.Unspecified, boolean, diagnostics); boolOperator = default; return; } // It was not. Does it implement operator true (or false)? UnaryOperatorKind boolOpKind; switch (binaryOperator) { case BinaryOperatorKind.Equal: boolOpKind = UnaryOperatorKind.False; break; case BinaryOperatorKind.NotEqual: boolOpKind = UnaryOperatorKind.True; break; default: throw ExceptionUtilities.UnexpectedValue(binaryOperator); } LookupResultKind resultKind; ImmutableArray<MethodSymbol> originalUserDefinedOperators; BoundExpression comparisonResult = new BoundTupleOperandPlaceholder(node, type); OperatorResolutionForReporting discardedOperatorResolutionForReporting = default; UnaryOperatorAnalysisResult best = this.UnaryOperatorOverloadResolution(boolOpKind, comparisonResult, node, diagnostics, ref discardedOperatorResolutionForReporting, out resultKind, out originalUserDefinedOperators); discardedOperatorResolutionForReporting.Free(); if (best.HasValue) { conversionForBoolPlaceholder = new BoundValuePlaceholder(node, type).MakeCompilerGenerated(); conversionForBool = CreateConversion(node, conversionForBoolPlaceholder, best.Conversion, isCast: false, conversionGroupOpt: null, InConversionGroupFlags.Unspecified, best.Signature.OperandType, diagnostics); boolOperator = best.Signature; return; } // It did not. Give a "not convertible to bool" error. GenerateImplicitConversionError(diagnostics, node, conversion, comparisonResult, boolean); conversionForBoolPlaceholder = null; conversionForBool = null; boolOperator = default; return; } private TupleBinaryOperatorInfo BindTupleDynamicBinaryOperatorSingleInfo(BinaryExpressionSyntax node, BinaryOperatorKind kind, BoundExpression left, BoundExpression right, BindingDiagnosticBag diagnostics) { // This method binds binary == and != operators where one or both of the operands are dynamic. Debug.Assert((object)left.Type != null && left.Type.IsDynamic() || (object)right.Type != null && right.Type.IsDynamic()); bool hasError = false; if (!IsLegalDynamicOperand(left) || !IsLegalDynamicOperand(right)) { // Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' Error(diagnostics, ErrorCode.ERR_BadBinaryOps, node, node.OperatorToken.Text, left.Display, right.Display); hasError = true; } BinaryOperatorKind elementOperatorKind = hasError ? kind : kind.WithType(BinaryOperatorKind.Dynamic); TypeSymbol dynamicType = hasError ? CreateErrorType() : Compilation.DynamicType; // We'll want to dynamically invoke operators op_true (/op_false) for equality (/inequality) comparison, but we don't need // to prepare either a conversion or a truth operator. Those can just be synthesized during lowering. return new TupleBinaryOperatorInfo.Single(dynamicType, dynamicType, elementOperatorKind, methodSymbolOpt: null, constrainedToTypeOpt: null, conversionForBoolPlaceholder: null, conversionForBool: null, boolOperator: default); } private TupleBinaryOperatorInfo.Multiple BindTupleBinaryOperatorNestedInfo(BinaryExpressionSyntax node, BinaryOperatorKind kind, BoundExpression left, BoundExpression right, BindingDiagnosticBag diagnostics) { left = GiveTupleTypeToDefaultLiteralIfNeeded(left, right.Type); right = GiveTupleTypeToDefaultLiteralIfNeeded(right, left.Type); if (left.IsLiteralDefaultOrImplicitObjectCreation() || right.IsLiteralDefaultOrImplicitObjectCreation()) { OperatorResolutionForReporting discardedOperatorResolutionForReporting = default; ReportBinaryOperatorError(node, diagnostics, node.OperatorToken, left, right, LookupResultKind.Ambiguous, ref discardedOperatorResolutionForReporting); discardedOperatorResolutionForReporting.Free(); return TupleBinaryOperatorInfo.Multiple.ErrorInstance; } // Aside from default (which we fixed or ruled out above) and tuple literals, // we must have typed expressions at this point Debug.Assert((object)left.Type != null || left.Kind == BoundKind.TupleLiteral); Debug.Assert((object)right.Type != null || right.Kind == BoundKind.TupleLiteral); int leftCardinality = GetTupleCardinality(left); int rightCardinality = GetTupleCardinality(right); if (leftCardinality != rightCardinality) { Error(diagnostics, ErrorCode.ERR_TupleSizesMismatchForBinOps, node, leftCardinality, rightCardinality); return TupleBinaryOperatorInfo.Multiple.ErrorInstance; } (ImmutableArray<BoundExpression> leftParts, ImmutableArray<string> leftNames) = GetTupleArgumentsOrPlaceholders(left); (ImmutableArray<BoundExpression> rightParts, ImmutableArray<string> rightNames) = GetTupleArgumentsOrPlaceholders(right); ReportNamesMismatchesIfAny(left, right, leftNames, rightNames, diagnostics); int length = leftParts.Length; Debug.Assert(length == rightParts.Length); var operatorsBuilder = ArrayBuilder<TupleBinaryOperatorInfo>.GetInstance(length); for (int i = 0; i < length; i++) { operatorsBuilder.Add(BindTupleBinaryOperatorInfo(node, kind, leftParts[i], rightParts[i], diagnostics)); } var compilation = this.Compilation; var operators = operatorsBuilder.ToImmutableAndFree(); // typeless tuple literals are not nullable bool leftNullable = left.Type?.IsNullableType() == true; bool rightNullable = right.Type?.IsNullableType() == true; bool isNullable = leftNullable || rightNullable; TypeSymbol leftTupleType = MakeConvertedType(operators.SelectAsArray(o => o.LeftConvertedTypeOpt), node.Left, leftParts, leftNames, isNullable, compilation, diagnostics); TypeSymbol rightTupleType = MakeConvertedType(operators.SelectAsArray(o => o.RightConvertedTypeOpt), node.Right, rightParts, rightNames, isNullable, compilation, diagnostics); return new TupleBinaryOperatorInfo.Multiple(operators, leftTupleType, rightTupleType); } /// <summary> /// If an element in a tuple literal has an explicit name which doesn't match the name on the other side, we'll warn. /// The user can either remove the name, or fix it. /// /// This method handles two expressions, each of which is either a tuple literal or an expression with tuple type. /// In a tuple literal, each element can have an explicit name, an inferred name or no name. /// In an expression of tuple type, each element can have a name or not. /// </summary> private static void ReportNamesMismatchesIfAny(BoundExpression left, BoundExpression right, ImmutableArray<string> leftNames, ImmutableArray<string> rightNames, BindingDiagnosticBag diagnostics) { bool leftIsTupleLiteral = left is BoundTupleExpression; bool rightIsTupleLiteral = right is BoundTupleExpression; if (!leftIsTupleLiteral && !rightIsTupleLiteral) { return; } bool leftNoNames = leftNames.IsDefault; bool rightNoNames = rightNames.IsDefault; if (leftNoNames && rightNoNames) { return; } Debug.Assert(leftNoNames || rightNoNames || leftNames.Length == rightNames.Length); ImmutableArray<bool> leftInferred = leftIsTupleLiteral ? ((BoundTupleExpression)left).InferredNamesOpt : default; bool leftNoInferredNames = leftInferred.IsDefault; ImmutableArray<bool> rightInferred = rightIsTupleLiteral ? ((BoundTupleExpression)right).InferredNamesOpt : default; bool rightNoInferredNames = rightInferred.IsDefault; int length = leftNoNames ? rightNames.Length : leftNames.Length; for (int i = 0; i < length; i++) { string leftName = leftNoNames ? null : leftNames[i]; string rightName = rightNoNames ? null : rightNames[i]; if (string.Equals(leftName, rightName, StringComparison.Ordinal)) { continue; } bool leftWasInferred = leftNoInferredNames ? false : leftInferred[i]; bool rightWasInferred = rightNoInferredNames ? false : rightInferred[i]; bool leftComplaint = leftIsTupleLiteral && leftName != null && !leftWasInferred; bool rightComplaint = rightIsTupleLiteral && rightName != null && !rightWasInferred; if (!leftComplaint && !rightComplaint) { // No complaints, let's move on continue; } // When in doubt, we'll complain on the right side if it's a literal bool useRight = (leftComplaint && rightComplaint) ? rightIsTupleLiteral : rightComplaint; Location location = ((BoundTupleExpression)(useRight ? right : left)).Arguments[i].Syntax.Parent.Location; string complaintName = useRight ? rightName : leftName; diagnostics.Add(ErrorCode.WRN_TupleBinopLiteralNameMismatch, location, complaintName); } } internal static BoundExpression GiveTupleTypeToDefaultLiteralIfNeeded(BoundExpression expr, TypeSymbol targetType) { if (!expr.IsLiteralDefault() || targetType is null) { return expr; } Debug.Assert(targetType.StrippedType().IsTupleType); return new BoundDefaultExpression(expr.Syntax, targetType); } private static bool IsTupleBinaryOperation(BoundExpression left, BoundExpression right) { bool leftDefaultOrNew = left.IsLiteralDefaultOrImplicitObjectCreation(); bool rightDefaultOrNew = right.IsLiteralDefaultOrImplicitObjectCreation(); if (leftDefaultOrNew && rightDefaultOrNew) { return false; } return (GetTupleCardinality(left) > 1 || leftDefaultOrNew) && (GetTupleCardinality(right) > 1 || rightDefaultOrNew); } private static int GetTupleCardinality(BoundExpression expr) { if (expr is BoundTupleExpression tuple) { return tuple.Arguments.Length; } TypeSymbol type = expr.Type; if (type is null) { return -1; } if (type.StrippedType() is { IsTupleType: true } tupleType) { return tupleType.TupleElementTypesWithAnnotations.Length; } return -1; } /// <summary> /// Given a tuple literal or expression, we'll get two arrays: /// - the elements from the literal, or some placeholder with proper type (for tuple expressions) /// - the elements' names /// </summary> private static (ImmutableArray<BoundExpression> Elements, ImmutableArray<string> Names) GetTupleArgumentsOrPlaceholders(BoundExpression expr) { if (expr is BoundTupleExpression tuple) { return (tuple.Arguments, tuple.ArgumentNamesOpt); } // placeholder bound nodes with the proper types are sufficient to bind the element-wise binary operators TypeSymbol tupleType = expr.Type.StrippedType(); ImmutableArray<BoundExpression> placeholders = tupleType.TupleElementTypesWithAnnotations .SelectAsArray((t, s) => (BoundExpression)new BoundTupleOperandPlaceholder(s, t.Type), expr.Syntax); return (placeholders, tupleType.TupleElementNames); } /// <summary> /// Make a tuple type (with appropriate nesting) from the types (on the left or on the right) collected /// from binding element-wise binary operators. /// If any of the elements is typeless, then the tuple is typeless too. /// </summary> private TypeSymbol MakeConvertedType(ImmutableArray<TypeSymbol> convertedTypes, CSharpSyntaxNode syntax, ImmutableArray<BoundExpression> elements, ImmutableArray<string> names, bool isNullable, CSharpCompilation compilation, BindingDiagnosticBag diagnostics) { foreach (var convertedType in convertedTypes) { if (convertedType is null) { return null; } } ImmutableArray<Location> elementLocations = elements.SelectAsArray(e => e.Syntax.Location); var tuple = NamedTypeSymbol.CreateTuple(locationOpt: null, elementTypesWithAnnotations: convertedTypes.SelectAsArray(t => TypeWithAnnotations.Create(t)), elementLocations, elementNames: names, compilation, shouldCheckConstraints: true, includeNullability: false, errorPositions: default, syntax, diagnostics); if (!isNullable) { return tuple; } // Any violated constraints on nullable tuples would have been reported already NamedTypeSymbol nullableT = GetSpecialType(SpecialType.System_Nullable_T, diagnostics, syntax); return nullableT.Construct(tuple); } } }