From ca99490714d8198903f760dc7ba54115f62084f0 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 12 Mar 2018 16:28:08 -0700 Subject: [PATCH] Track and check names in tuple equality (#25396) --- .../Portable/Binder/Binder_TupleOperators.cs | 101 ++++++++++++++++-- .../BoundTree/TupleBinaryOperatorInfo.cs | 3 + .../Portable/CSharpResources.Designer.cs | 18 ++++ .../CSharp/Portable/CSharpResources.resx | 6 ++ .../CSharp/Portable/Errors/ErrorCode.cs | 1 + .../CSharp/Portable/Errors/ErrorFacts.cs | 1 + .../Generated/ErrorFacts.Generated.cs | 1 + .../Portable/xlf/CSharpResources.cs.xlf | 10 ++ .../Portable/xlf/CSharpResources.de.xlf | 10 ++ .../Portable/xlf/CSharpResources.es.xlf | 10 ++ .../Portable/xlf/CSharpResources.fr.xlf | 10 ++ .../Portable/xlf/CSharpResources.it.xlf | 10 ++ .../Portable/xlf/CSharpResources.ja.xlf | 10 ++ .../Portable/xlf/CSharpResources.ko.xlf | 10 ++ .../Portable/xlf/CSharpResources.pl.xlf | 10 ++ .../Portable/xlf/CSharpResources.pt-BR.xlf | 10 ++ .../Portable/xlf/CSharpResources.ru.xlf | 10 ++ .../Portable/xlf/CSharpResources.tr.xlf | 10 ++ .../Portable/xlf/CSharpResources.zh-Hans.xlf | 10 ++ .../Portable/xlf/CSharpResources.zh-Hant.xlf | 10 ++ .../Emit/CodeGen/CodeGenTupleEqualityTests.cs | 98 +++++++++++++++-- .../Test/Syntax/Diagnostics/DiagnosticTest.cs | 1 + 22 files changed, 343 insertions(+), 17 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_TupleOperators.cs b/src/Compilers/CSharp/Portable/Binder/Binder_TupleOperators.cs index bf82f9c7c36..e5416e11ad0 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_TupleOperators.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_TupleOperators.cs @@ -207,7 +207,7 @@ private BoundExpression ApplyConvertedTypes(BoundExpression expr, TupleBinaryOpe (right.Type is null && right.IsLiteralDefault())) { Error(diagnostics, ErrorCode.ERR_AmbigBinaryOps, node, node.OperatorToken.Text, left.Display, right.Display); - return new TupleBinaryOperatorInfo.Multiple(ImmutableArray.Empty, left.Type, right.Type); + return TupleBinaryOperatorInfo.Multiple.ErrorInstance; } // Aside from default (which we fixed or ruled out above) and tuple literals, @@ -221,11 +221,12 @@ private BoundExpression ApplyConvertedTypes(BoundExpression expr, TupleBinaryOpe if (leftCardinality != rightCardinality) { Error(diagnostics, ErrorCode.ERR_TupleSizesMismatchForBinOps, node, leftCardinality, rightCardinality); - return new TupleBinaryOperatorInfo.Multiple(ImmutableArray.Empty, leftConvertedTypeOpt: null, rightConvertedTypeOpt: null); + return TupleBinaryOperatorInfo.Multiple.ErrorInstance; } - ImmutableArray leftParts = GetTupleArgumentsOrPlaceholders(left); - ImmutableArray rightParts = GetTupleArgumentsOrPlaceholders(right); + (ImmutableArray leftParts, ImmutableArray leftNames) = GetTupleArgumentsOrPlaceholders(left); + (ImmutableArray rightParts, ImmutableArray rightNames) = GetTupleArgumentsOrPlaceholders(right); + ReportNamesMismatchesIfAny(left, right, leftNames, rightNames, diagnostics); int length = leftParts.Length; Debug.Assert(length == rightParts.Length); @@ -245,12 +246,80 @@ private BoundExpression ApplyConvertedTypes(BoundExpression expr, TupleBinaryOpe bool rightNullable = right.Type?.IsNullableType() == true; bool isNullable = leftNullable || rightNullable; - TypeSymbol leftTupleType = MakeConvertedType(operators.SelectAsArray(o => o.LeftConvertedTypeOpt), node.Left, leftParts, isNullable, compilation, diagnostics); - TypeSymbol rightTupleType = MakeConvertedType(operators.SelectAsArray(o => o.RightConvertedTypeOpt), node.Right, rightParts, isNullable, compilation, diagnostics); + 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); } + /// + /// 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. + /// + private static void ReportNamesMismatchesIfAny(BoundExpression left, BoundExpression right, + ImmutableArray leftNames, ImmutableArray rightNames, DiagnosticBag diagnostics) + { + bool leftIsTupleLiteral = left.Kind == BoundKind.TupleLiteral; + bool rightIsTupleLiteral = right.Kind == BoundKind.TupleLiteral; + + 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 leftInferred = leftIsTupleLiteral ? ((BoundTupleLiteral)left).InferredNamesOpt : default; + bool leftNoInferredNames = leftInferred.IsDefault; + + ImmutableArray rightInferred = rightIsTupleLiteral ? ((BoundTupleLiteral)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]; + + bool different = string.CompareOrdinal(rightName, leftName) != 0; + if (!different) + { + 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 = ((BoundTupleLiteral)(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) @@ -298,16 +367,25 @@ private static int GetTupleCardinality(BoundExpression expr) return -1; } - private static ImmutableArray GetTupleArgumentsOrPlaceholders(BoundExpression expr) + /// + /// 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 + /// + private static (ImmutableArray Elements, ImmutableArray Names) GetTupleArgumentsOrPlaceholders(BoundExpression expr) { if (expr.Kind == BoundKind.TupleLiteral) { - return ((BoundTupleLiteral)expr).Arguments; + var tuple = (BoundTupleLiteral)expr; + return (tuple.Arguments, tuple.ArgumentNamesOpt); } // placeholder bound nodes with the proper types are sufficient to bind the element-wise binary operators - return expr.Type.StrippedType().TupleElementTypes + TypeSymbol tupleType = expr.Type.StrippedType(); + ImmutableArray placeholders = tupleType.TupleElementTypes .SelectAsArray((t, s) => (BoundExpression)new BoundTupleOperandPlaceholder(s, t), expr.Syntax); + + return (placeholders, tupleType.TupleElementNames); } /// @@ -316,7 +394,8 @@ private static ImmutableArray GetTupleArgumentsOrPlaceholders(B /// If any of the elements is typeless, then the tuple is typeless too. /// private TypeSymbol MakeConvertedType(ImmutableArray convertedTypes, CSharpSyntaxNode syntax, - ImmutableArray elements, bool isNullable, CSharpCompilation compilation, DiagnosticBag diagnostics) + ImmutableArray elements, ImmutableArray names, + bool isNullable, CSharpCompilation compilation, DiagnosticBag diagnostics) { foreach (var convertedType in convertedTypes) { @@ -330,7 +409,7 @@ private static ImmutableArray GetTupleArgumentsOrPlaceholders(B // PROTOTYPE(tuple-equality) Add test for violated tuple constraint var tuple = TupleTypeSymbol.Create(locationOpt: null, elementTypes: convertedTypes, - elementLocations, elementNames: default, compilation, + elementLocations, elementNames: names, compilation, shouldCheckConstraints: true, errorPositions: default, syntax, diagnostics); if (!isNullable) diff --git a/src/Compilers/CSharp/Portable/BoundTree/TupleBinaryOperatorInfo.cs b/src/Compilers/CSharp/Portable/BoundTree/TupleBinaryOperatorInfo.cs index 49c9b0e8713..26883c40a13 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/TupleBinaryOperatorInfo.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/TupleBinaryOperatorInfo.cs @@ -94,6 +94,9 @@ internal class Multiple : TupleBinaryOperatorInfo { internal readonly ImmutableArray Operators; + static internal readonly Multiple ErrorInstance = + new Multiple(operators: ImmutableArray.Empty, leftConvertedTypeOpt: null, rightConvertedTypeOpt: null); + internal Multiple(ImmutableArray operators, TypeSymbol leftConvertedTypeOpt, TypeSymbol rightConvertedTypeOpt) : base(leftConvertedTypeOpt, rightConvertedTypeOpt) { diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index b1ed801fb80..08b1da14e73 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -14299,6 +14299,24 @@ internal class CSharpResources { } } + /// + /// Looks up a localized string similar to The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator.. + /// + internal static string WRN_TupleBinopLiteralNameMismatch { + get { + return ResourceManager.GetString("WRN_TupleBinopLiteralNameMismatch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator.. + /// + internal static string WRN_TupleBinopLiteralNameMismatch_Title { + get { + return ResourceManager.GetString("WRN_TupleBinopLiteralNameMismatch_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to The tuple element name '{0}' is ignored because a different name or no name is specified by the target type '{1}'.. /// diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 2c99b8ecc72..a650880fe3b 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5034,6 +5034,12 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ The tuple element name is ignored because a different name or no name is specified by the assignment target. + + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + Predefined type '{0}' must be a struct. diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 6a9bcddd698..9ef9bbea8d9 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1562,6 +1562,7 @@ internal enum ErrorCode ERR_TupleSizesMismatchForBinOps = 8373, ERR_ExpressionTreeContainsTupleBinOp = 8374, + WRN_TupleBinopLiteralNameMismatch = 8375, #endregion diagnostics introduced for C# 7.3 // Note: you will need to re-generate compiler code after adding warnings (build\scripts\generate-compiler-code.cmd) diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index 38ffa98fe91..82fc3f81884 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -322,6 +322,7 @@ internal static int GetWarningLevel(ErrorCode code) case ErrorCode.WRN_TupleLiteralNameMismatch: case ErrorCode.WRN_Experimental: case ErrorCode.WRN_AttributesOnBackingFieldsNotAvailable: + case ErrorCode.WRN_TupleBinopLiteralNameMismatch: return 1; default: return 0; diff --git a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs index 0425bc4f2ac..31d1fdf774e 100644 --- a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs @@ -178,6 +178,7 @@ public static bool IsWarning(ErrorCode code) case ErrorCode.WRN_FilterIsConstantFalse: case ErrorCode.WRN_FilterIsConstantFalseRedundantTryCatch: case ErrorCode.WRN_AttributesOnBackingFieldsNotAvailable: + case ErrorCode.WRN_TupleBinopLiteralNameMismatch: return true; default: return false; diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 1e4103875ac..8ea84f4295f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -8635,6 +8635,16 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference An expression tree may not contain a tuple == or != operator + + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + + + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 287a79a77f8..2220ed28b16 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -8635,6 +8635,16 @@ Um die Warnung zu beheben, können Sie stattdessen /reference verwenden (Einbett An expression tree may not contain a tuple == or != operator + + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + + + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 7e9f070c727..7c858066b6a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -8635,6 +8635,16 @@ Para eliminar la advertencia puede usar /reference (establezca la propiedad Embe An expression tree may not contain a tuple == or != operator + + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + + + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 23cc955ea61..45e042c6f64 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -8635,6 +8635,16 @@ Pour supprimer l'avertissement, vous pouvez utiliser la commande /reference (dé An expression tree may not contain a tuple == or != operator + + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + + + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 9a8c8f362d8..e6232b733dd 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -8635,6 +8635,16 @@ Per rimuovere l'avviso, è invece possibile usare /reference (impostare la propr An expression tree may not contain a tuple == or != operator + + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + + + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index f3db2838376..e3eb4b7eae5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -8635,6 +8635,16 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ An expression tree may not contain a tuple == or != operator + + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + + + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 7d5565b4441..59d7cb91af7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -8635,6 +8635,16 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ An expression tree may not contain a tuple == or != operator + + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + + + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 4d936d5faae..33e82a2edf7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -8635,6 +8635,16 @@ Aby usunąć ostrzeżenie, możesz zamiast tego użyć opcji /reference (ustaw w An expression tree may not contain a tuple == or != operator + + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + + + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index f0271942076..2b5819c4b1c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -8635,6 +8635,16 @@ Para incorporar informações de tipo de interoperabilidade para os dois assembl An expression tree may not contain a tuple == or != operator + + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + + + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 7f5cb9a1c5e..cccc47bb852 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -8635,6 +8635,16 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ An expression tree may not contain a tuple == or != operator + + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + + + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 6c72aa5bc45..1931abc1575 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -8635,6 +8635,16 @@ Uyarıyı kaldırmak için, /reference kullanabilirsiniz (Birlikte Çalışma T An expression tree may not contain a tuple == or != operator + + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + + + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index eb3d3562da8..a16d21aabe6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -8635,6 +8635,16 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ An expression tree may not contain a tuple == or != operator + + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + + + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 56742fa5a53..89e434766b4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -8635,6 +8635,16 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ An expression tree may not contain a tuple == or != operator + + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name '{0}' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + + + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + The tuple element name is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + + \ No newline at end of file diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs index a1bc5da9ab7..e76037acf94 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenTupleEqualityTests.cs @@ -749,7 +749,7 @@ static void Main() Assert.Equal("(s, null)", tuple1.ToString()); var tupleType1 = model.GetTypeInfo(tuple1); Assert.Null(tupleType1.Type); - Assert.Equal("(System.String, System.String)", tupleType1.ConvertedType.ToTestDisplayString()); + Assert.Equal("(System.String s, System.String)", tupleType1.ConvertedType.ToTestDisplayString()); var tuple1Null = tuple1.Arguments[1].Expression; var tuple1NullTypeInfo = model.GetTypeInfo(tuple1Null); @@ -762,7 +762,7 @@ static void Main() Assert.Equal("(null, s)", tuple2.ToString()); var tupleType2 = model.GetTypeInfo(tuple2); Assert.Null(tupleType2.Type); - Assert.Equal("(System.String, System.String)", tupleType2.ConvertedType.ToTestDisplayString()); + Assert.Equal("(System.String, System.String s)", tupleType2.ConvertedType.ToTestDisplayString()); var tuple2Null = tuple2.Arguments[0].Expression; var tuple2NullTypeInfo = model.GetTypeInfo(tuple2Null); @@ -866,7 +866,7 @@ static void Main() Assert.Equal("(1L, t2)", tuple.ToString()); var tupleType = model.GetTypeInfo(tuple); Assert.Equal("(System.Int64, (System.Int32, System.String) t2)", tupleType.Type.ToTestDisplayString()); - Assert.Equal("(System.Int64, (System.Int64, System.String))", tupleType.ConvertedType.ToTestDisplayString()); + Assert.Equal("(System.Int64, (System.Int64, System.String) t2)", tupleType.ConvertedType.ToTestDisplayString()); var t2 = tuple.Arguments[1].Expression; Assert.Equal("t2", t2.ToString()); @@ -1368,7 +1368,7 @@ static void Main() var tupleType = model.GetTypeInfo(tuple); Assert.Null(tupleType.Type); - Assert.Equal("((System.String, System.String), (System.String, System.String))", + Assert.Equal("((System.String, System.String), (System.String, System.String) t)", tupleType.ConvertedType.ToTestDisplayString()); // ... its t ... @@ -1736,13 +1736,13 @@ public static void Main() Assert.Equal("(d1, null)", tuple1.ToString()); var tupleType1 = model.GetTypeInfo(tuple1); Assert.Null(tupleType1.Type); - Assert.Equal("(dynamic, dynamic)", tupleType1.ConvertedType.ToTestDisplayString()); + Assert.Equal("(dynamic d1, dynamic)", tupleType1.ConvertedType.ToTestDisplayString()); var tuple2 = tree.GetCompilationUnitRoot().DescendantNodes().OfType().ElementAt(1); Assert.Equal("(null, d2)", tuple2.ToString()); var tupleType2 = model.GetTypeInfo(tuple2); Assert.Null(tupleType2.Type); - Assert.Equal("(dynamic, dynamic)", tupleType2.ConvertedType.ToTestDisplayString()); + Assert.Equal("(dynamic, dynamic d2)", tupleType2.ConvertedType.ToTestDisplayString()); } [Fact] @@ -3973,6 +3973,92 @@ public override int GetHashCode() ); } + [Fact] + public void TestElementNames() + { + var source = @" +#pragma warning disable CS0219 +using static System.Console; +public class C +{ + public static void Main() + { + int a = 1; + int b = 2; + int c = 3; + int d = 4; + int x = 5; + int y = 6; + (int x, int y) t1 = (1, 2); + (int, int) t2 = (1, 2); + Write($""{REPLACE}""); + } +} +"; + + // tuple expression vs tuple expression + validate("t1 == t2"); + + // tuple expression vs tuple literal + validate("t1 == (x: 1, y: 2)"); + validate("t1 == (1, 2)"); + validate("(1, 2) == t1"); + validate("(x: 1, d) == t1"); + + validate("t2 == (x: 1, y: 2)", + // (16,25): warning CS8375: The tuple element name 'x' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + // Write($"{t2 == (x: 1, y: 2)}"); + Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "x: 1").WithArguments("x").WithLocation(16, 25), + // (16,31): warning CS8375: The tuple element name 'y' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + // Write($"{t2 == (x: 1, y: 2)}"); + Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "y: 2").WithArguments("y").WithLocation(16, 31) + ); + + // tuple literal vs tuple literal + // - warnings reported on the right when both sides could complain + // - no warnings on inferred names + + validate("((a, b), c: 3) == ((1, x: 2), 3)", + // (16,27): warning CS8375: The tuple element name 'c' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + // Write($"{((a, b), c: 3) == ((1, x: 2), 3)}"); + Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "c: 3").WithArguments("c").WithLocation(16, 27), + // (16,41): warning CS8375: The tuple element name 'x' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + // Write($"{((a, b), c: 3) == ((1, x: 2), 3)}"); + Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "x: 2").WithArguments("x").WithLocation(16, 41) + ); + + validate("(a, b) == (a: 1, b: 2)"); + validate("(a, b) == (c: 1, d)", + // (16,29): warning CS8375: The tuple element name 'c' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + // Write($"{(a, b) == (c: 1, d)}"); + Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "c: 1").WithArguments("c").WithLocation(16, 29) + ); + + validate("(a: 1, b: 2) == (c: 1, d)", + // (16,35): warning CS8375: The tuple element name 'c' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + // Write($"{(a: 1, b: 2) == (c: 1, d)}"); + Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "c: 1").WithArguments("c").WithLocation(16, 35), + // (16,25): warning CS8375: The tuple element name 'b' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + // Write($"{(a: 1, b: 2) == (c: 1, d)}"); + Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "b: 2").WithArguments("b").WithLocation(16, 25) + ); + + validate("(null, b) == (c: null, d: 2)", + // (16,32): warning CS8375: The tuple element name 'c' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + // Write($"{(null, b) == (c: null, d: 2)}"); + Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "c: null").WithArguments("c").WithLocation(16, 32), + // (16,41): warning CS8375: The tuple element name 'd' is ignored because a different name or no name is specified on the other side of the tuple == or != operator. + // Write($"{(null, b) == (c: null, d: 2)}"); + Diagnostic(ErrorCode.WRN_TupleBinopLiteralNameMismatch, "d: 2").WithArguments("d").WithLocation(16, 41) + ); + + void validate(string expression, params DiagnosticDescription[] diagnostics) + { + var comp = CreateCompilation(source.Replace("REPLACE", expression)); + comp.VerifyDiagnostics(diagnostics); + } + } + [Fact] void TestValueTupleWithObsoleteEqualityOperator() { diff --git a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs index cec5d1fe5f0..89e111375d1 100644 --- a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs +++ b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs @@ -243,6 +243,7 @@ public void WarningLevel_2() case ErrorCode.WRN_TupleLiteralNameMismatch: case ErrorCode.WRN_Experimental: case ErrorCode.WRN_AttributesOnBackingFieldsNotAvailable: + case ErrorCode.WRN_TupleBinopLiteralNameMismatch: Assert.Equal(1, ErrorFacts.GetWarningLevel(errorCode)); break; case ErrorCode.WRN_MainIgnored: -- GitLab