提交 3d3b5240 编写于 作者: J Julien Couvreur 提交者: GitHub

Escape rules for deconstruction and foreach variables (#22354)

上级 1fd9a04c
......@@ -1954,6 +1954,27 @@ internal static bool CheckRefEscape(SyntaxNode node, BoundExpression expr, uint
return false;
}
internal static uint GetBroadestValEscape(BoundTupleExpression expr, uint scopeOfTheContainingExpression)
{
uint broadest = scopeOfTheContainingExpression;
foreach (var element in expr.Arguments)
{
uint valEscape;
if (element.Kind == BoundKind.TupleLiteral)
{
valEscape = GetBroadestValEscape((BoundTupleExpression)element, scopeOfTheContainingExpression);
}
else
{
valEscape = GetValEscape(element, scopeOfTheContainingExpression);
}
broadest = Math.Min(broadest, valEscape);
}
return broadest;
}
/// <summary>
/// Computes the widest scope depth to which the given expression can escape by value.
///
......@@ -1989,6 +2010,14 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin
// always returnable
return Binder.ExternalScope;
case BoundKind.TupleLiteral:
var tupleLiteral = (BoundTupleLiteral)expr;
return GetTupleValEscape(tupleLiteral.Arguments, scopeOfTheContainingExpression);
case BoundKind.ConvertedTupleLiteral:
var convertedTupleLiteral = (BoundConvertedTupleLiteral)expr;
return GetTupleValEscape(convertedTupleLiteral.Arguments, scopeOfTheContainingExpression);
case BoundKind.MakeRefOperator:
case BoundKind.RefValueOperator:
// for compat reasons
......@@ -2000,6 +2029,9 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin
// same as uninitialized local
return Binder.ExternalScope;
case BoundKind.DeconstructValuePlaceholder:
return ((BoundDeconstructValuePlaceholder)expr).ValEscape;
case BoundKind.Local:
return ((BoundLocal)expr).LocalSymbol.ValEscapeScope;
......@@ -2162,6 +2194,17 @@ internal static uint GetValEscape(BoundExpression expr, uint scopeOfTheContainin
}
}
private static uint GetTupleValEscape(ImmutableArray<BoundExpression> elements, uint scopeOfTheContainingExpression)
{
uint narrowestScope = scopeOfTheContainingExpression;
foreach (var element in elements)
{
narrowestScope = Math.Max(narrowestScope, GetValEscape(element, scopeOfTheContainingExpression));
}
return narrowestScope;
}
private static uint GetValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, uint scopeOfTheContainingExpression)
{
var result = Binder.ExternalScope;
......@@ -2236,6 +2279,14 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint
// always returnable
return true;
case BoundKind.TupleLiteral:
var tupleLiteral = (BoundTupleLiteral)expr;
return CheckTupleValEscape(tupleLiteral.Arguments, escapeFrom, escapeTo, diagnostics);
case BoundKind.ConvertedTupleLiteral:
var convertedTupleLiteral = (BoundConvertedTupleLiteral)expr;
return CheckTupleValEscape(convertedTupleLiteral.Arguments, escapeFrom, escapeTo, diagnostics);
case BoundKind.MakeRefOperator:
case BoundKind.RefValueOperator:
// for compat reasons
......@@ -2245,6 +2296,15 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint
// same as uninitialized local
return true;
case BoundKind.DeconstructValuePlaceholder:
var placeholder = (BoundDeconstructValuePlaceholder)expr;
if (placeholder.ValEscape > escapeTo)
{
Error(diagnostics, ErrorCode.ERR_EscapeLocal, node, placeholder.Syntax);
return false;
}
return true;
case BoundKind.Local:
var localSymbol = ((BoundLocal)expr).LocalSymbol;
if (localSymbol.ValEscapeScope > escapeTo)
......@@ -2459,8 +2519,6 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint
// case BoundKind.NameOfOperator:
// case BoundKind.InterpolatedString:
// case BoundKind.StringInsert:
// case BoundKind.TupleLiteral:
// case BoundKind.ConvertedTupleLiteral:
// case BoundKind.DynamicIndexerAccess:
// case BoundKind.Lambda:
// case BoundKind.DynamicObjectCreationExpression:
......@@ -2537,7 +2595,6 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint
// case BoundKind.DeclarationPattern:
// case BoundKind.ConstantPattern:
// case BoundKind.WildcardPattern:
// case BoundKind.DeconstructValuePlaceholder:
#endregion
......@@ -2573,6 +2630,19 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint
}
}
private static bool CheckTupleValEscape(ImmutableArray<BoundExpression> elements, uint escapeFrom, uint escapeTo, DiagnosticBag diagnostics)
{
foreach (var element in elements)
{
if (!CheckValEscape(element.Syntax, element, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics))
{
return false;
}
}
return true;
}
private static bool CheckValEscapeOfObjectInitializer(BoundObjectInitializerExpression initExpr, uint escapeFrom, uint escapeTo, DiagnosticBag diagnostics)
{
foreach (var expression in initExpr.Initializers)
......
......@@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
......@@ -95,13 +94,15 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia
DeconstructionVariable locals = BindDeconstructionVariables(left, diagnostics, ref declaration, ref expression);
Debug.Assert(locals.HasNestedVariables);
BoundExpression boundRight = rightPlaceholder ?? BindValue(right, diagnostics, BindValueKind.RValue);
boundRight = FixTupleLiteral(locals.NestedVariables, boundRight, deconstruction, diagnostics);
var deconstructionDiagnostics = new DiagnosticBag();
BoundExpression boundRight = rightPlaceholder ?? BindValue(right, deconstructionDiagnostics, BindValueKind.RValue);
boundRight = FixTupleLiteral(locals.NestedVariables, boundRight, deconstruction, deconstructionDiagnostics);
bool resultIsUsed = resultIsUsedOverride || IsDeconstructionResultUsed(left);
var assignment = BindDeconstructionAssignment(deconstruction, left, boundRight, locals.NestedVariables, resultIsUsed, diagnostics);
var assignment = BindDeconstructionAssignment(deconstruction, left, boundRight, locals.NestedVariables, resultIsUsed, deconstructionDiagnostics);
DeconstructionVariable.FreeDeconstructionVariables(locals.NestedVariables);
diagnostics.AddRange(deconstructionDiagnostics);
return assignment;
}
......@@ -113,10 +114,12 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia
bool resultIsUsed,
DiagnosticBag diagnostics)
{
uint rightEscape = GetValEscape(boundRHS, this.LocalScopeDepth);
if ((object)boundRHS.Type == null || boundRHS.Type.IsErrorType())
{
// we could still not infer a type for the RHS
FailRemainingInferences(checkedVariables, diagnostics);
FailRemainingInferencesAndSetValEscape(checkedVariables, diagnostics, rightEscape);
var voidType = GetSpecialType(SpecialType.System_Void, diagnostics, node);
var type = boundRHS.Type ?? voidType;
......@@ -139,11 +142,14 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia
checkedVariables,
out conversion);
FailRemainingInferences(checkedVariables, diagnostics);
FailRemainingInferencesAndSetValEscape(checkedVariables, diagnostics, rightEscape);
var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: hasErrors || !resultIsUsed);
var lhsTuple = DeconstructionVariablesAsTuple(left, checkedVariables, diagnostics, ignoreDiagnosticsFromTuple: diagnostics.HasAnyErrors() || !resultIsUsed);
TypeSymbol returnType = hasErrors ? CreateErrorType() : lhsTuple.Type;
uint leftEscape = GetBroadestValEscape(lhsTuple, this.LocalScopeDepth);
boundRHS = ValidateEscape(boundRHS, leftEscape, isByRef: false, diagnostics: diagnostics);
var boundConversion = new BoundConversion(
boundRHS.Syntax,
boundRHS,
......@@ -197,7 +203,10 @@ private BoundExpression FixTupleLiteral(ArrayBuilder<DeconstructionVariable> che
// Let's fix the literal up by figuring out its type
// For declarations, that means merging type information from the LHS and RHS
// For assignments, only the LHS side matters since it is necessarily typed
TypeSymbol mergedTupleType = MakeMergedTupleType(checkedVariables, (BoundTupleLiteral)boundRHS, syntax, Compilation, diagnostics);
// If we already have diagnostics at this point, it is not worth collecting likely duplicate diagnostics from making the merged type
bool hadErrors = diagnostics.HasAnyErrors();
TypeSymbol mergedTupleType = MakeMergedTupleType(checkedVariables, (BoundTupleLiteral)boundRHS, syntax, Compilation, hadErrors ? null : diagnostics);
if ((object)mergedTupleType != null)
{
boundRHS = GenerateConversionForAssignment(mergedTupleType, boundRHS, diagnostics);
......@@ -248,7 +257,7 @@ private BoundExpression FixTupleLiteral(ArrayBuilder<DeconstructionVariable> che
else
{
ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders;
var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, type);
var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, this.LocalScopeDepth, type);
var deconstructInvocation = MakeDeconstructInvocationExpression(variables.Count,
inputPlaceholder, rightSyntax, diagnostics, out outPlaceholders);
......@@ -345,8 +354,10 @@ private BoundExpression SetInferredType(BoundExpression expression, TypeSymbol t
/// <summary>
/// Find any deconstruction locals that are still pending inference and fail their inference.
/// Set the safe-to-escape scope for all deconstruction locals.
/// </summary>
private void FailRemainingInferences(ArrayBuilder<DeconstructionVariable> variables, DiagnosticBag diagnostics)
private void FailRemainingInferencesAndSetValEscape(ArrayBuilder<DeconstructionVariable> variables, DiagnosticBag diagnostics,
uint rhsValEscape)
{
int count = variables.Count;
for (int i = 0; i < count; i++)
......@@ -354,15 +365,22 @@ private void FailRemainingInferences(ArrayBuilder<DeconstructionVariable> variab
var variable = variables[i];
if (variable.HasNestedVariables)
{
FailRemainingInferences(variable.NestedVariables, diagnostics);
FailRemainingInferencesAndSetValEscape(variable.NestedVariables, diagnostics, rhsValEscape);
}
else
{
switch (variable.Single.Kind)
{
case BoundKind.Local:
var local = (BoundLocal)variable.Single;
if (local.IsDeclaration)
{
((SourceLocalSymbol)local.LocalSymbol).SetValEscape(rhsValEscape);
}
break;
case BoundKind.DeconstructionVariablePendingInference:
BoundExpression local = ((DeconstructionVariablePendingInference)variable.Single).FailInference(this, diagnostics);
variables[i] = new DeconstructionVariable(local, local.Syntax);
BoundExpression errorLocal = ((DeconstructionVariablePendingInference)variable.Single).FailInference(this, diagnostics);
variables[i] = new DeconstructionVariable(errorLocal, errorLocal.Syntax);
break;
case BoundKind.DiscardExpression:
var pending = (BoundDiscardExpression)variable.Single;
......@@ -439,6 +457,7 @@ private static TypeSymbol MakeMergedTupleType(ArrayBuilder<DeconstructionVariabl
int rightLength = rhsLiteral.Arguments.Length;
var typesBuilder = ArrayBuilder<TypeSymbol>.GetInstance(leftLength);
var locationsBuilder = ArrayBuilder<Location>.GetInstance(leftLength);
for (int i = 0; i < rightLength; i++)
{
BoundExpression element = rhsLiteral.Arguments[i];
......@@ -479,29 +498,29 @@ private static TypeSymbol MakeMergedTupleType(ArrayBuilder<DeconstructionVariabl
}
typesBuilder.Add(mergedType);
locationsBuilder.Add(element.Syntax.Location);
}
if (typesBuilder.Any(t => t == null))
{
typesBuilder.Free();
locationsBuilder.Free();
return null;
}
// The tuple created here is not identical to the one created by
// DeconstructionVariablesAsTuple. It represents a smaller
// tree of types used for figuring out natural types in tuple literal.
// Therefore, we do not check constraints here as it would report errors
// that are already reported later. DeconstructionVariablesAsTuple
// constructs the final tuple type and checks constraints.
return TupleTypeSymbol.Create(
locationOpt: null,
elementTypes: typesBuilder.ToImmutableAndFree(),
elementLocations: default(ImmutableArray<Location>),
elementLocations: locationsBuilder.ToImmutableAndFree(),
elementNames: default(ImmutableArray<string>),
compilation: compilation,
diagnostics: diagnostics,
shouldCheckConstraints: false,
errorPositions: default(ImmutableArray<bool>));
shouldCheckConstraints: true,
errorPositions: default(ImmutableArray<bool>),
syntax: syntax);
}
private BoundTupleLiteral DeconstructionVariablesAsTuple(CSharpSyntaxNode syntax, ArrayBuilder<DeconstructionVariable> variables,
......@@ -672,7 +691,7 @@ private static string ExtractDeconstructResultElementName(BoundExpression expres
out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders, BoundExpression childNode)
{
Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, rightSyntax, receiver.Type, numParameters);
outPlaceholders = default(ImmutableArray<BoundDeconstructValuePlaceholder>);
outPlaceholders = default;
return BadExpression(rightSyntax, childNode);
}
......
......@@ -2536,7 +2536,7 @@ private BoundExpression BindArgumentExpression(DiagnosticBag diagnostics, Expres
else if (argument.Kind == BoundKind.OutDeconstructVarPendingInference)
{
TypeSymbol parameterType = GetCorrespondingParameterType(ref result, parameters, arg);
arguments[arg] = ((OutDeconstructVarPendingInference)argument).SetInferredType(parameterType, success: true);
arguments[arg] = ((OutDeconstructVarPendingInference)argument).SetInferredType(parameterType, this, success: true);
}
else if (argument.Kind == BoundKind.DiscardExpression && !argument.HasExpressionType())
{
......
......@@ -168,7 +168,10 @@ internal override BoundStatement BindForEachDeconstruction(DiagnosticBag diagnos
bool hasErrors = !GetEnumeratorInfoAndInferCollectionElementType(ref builder, ref collectionExpr, diagnostics, out inferredType);
ExpressionSyntax variables = ((ForEachVariableStatementSyntax)_syntax).Variable;
var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, inferredType ?? CreateErrorType("var"));
// Tracking narrowest safe-to-escape scope by default, the proper val escape will be set when doing full binding of the foreach statement
var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, this.LocalScopeDepth, inferredType ?? CreateErrorType("var"));
DeclarationExpressionSyntax declaration = null;
ExpressionSyntax expression = null;
BoundDeconstructionAssignmentOperator deconstruction = BindDeconstruction(
......@@ -202,6 +205,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics,
BoundTypeExpression boundIterationVariableType;
bool hasNameConflicts = false;
BoundForEachDeconstructStep deconstructStep = null;
uint collectionEscape = GetValEscape(collectionExpr, this.LocalScopeDepth);
switch (_syntax.Kind())
{
case SyntaxKind.ForEachStatement:
......@@ -230,7 +234,11 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics,
}
boundIterationVariableType = new BoundTypeExpression(typeSyntax, alias, iterationVariableType);
this.IterationVariable.SetType(iterationVariableType);
SourceLocalSymbol local = this.IterationVariable;
local.SetType(iterationVariableType);
local.SetValEscape(collectionEscape);
break;
}
case SyntaxKind.ForEachVariableStatement:
......@@ -241,7 +249,7 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics,
var variables = node.Variable;
if (variables.IsDeconstructionLeft())
{
var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, iterationVariableType);
var valuePlaceholder = new BoundDeconstructValuePlaceholder(_syntax.Expression, collectionEscape, iterationVariableType);
DeclarationExpressionSyntax declaration = null;
ExpressionSyntax expression = null;
BoundDeconstructionAssignmentOperator deconstruction = BindDeconstruction(
......@@ -283,8 +291,8 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics,
// I.E. - they will be considered declared and assigned in each iteration step.
ImmutableArray<LocalSymbol> iterationVariables = this.Locals;
Debug.Assert(hasErrors ||
_syntax.HasErrors ||
Debug.Assert(hasErrors ||
_syntax.HasErrors ||
iterationVariables.All(local => local.DeclarationKind == LocalDeclarationKind.ForEachIterationVariable),
"Should not have iteration variables that are not ForEachIterationVariable in valid code");
......@@ -349,8 +357,8 @@ private BoundForEachStatement BindForEachPartsWorker(DiagnosticBag diagnostics,
var getEnumeratorType = builder.GetEnumeratorMethod.ReturnType;
// we never convert struct enumerators to object - it is done only for null-checks.
builder.EnumeratorConversion = getEnumeratorType.IsValueType?
Conversion.Identity:
builder.EnumeratorConversion = getEnumeratorType.IsValueType ?
Conversion.Identity :
this.Conversions.ClassifyConversionFromType(getEnumeratorType, GetSpecialType(SpecialType.System_Object, diagnostics, _syntax), ref useSiteDiagnostics);
if (getEnumeratorType.IsRestrictedType() && (IsDirectlyInIterator || IsInAsyncMethod()))
......
......@@ -87,6 +87,7 @@
It is used to perform intermediate binding, and will not survive the local rewriting.
-->
<Node Name="BoundDeconstructValuePlaceholder" Base="BoundValuePlaceholderBase">
<Field Name="ValEscape" Type="uint" Null="NotApplicable"/>
</Node>
<!-- only used by codegen -->
......
......@@ -9,17 +9,18 @@ internal partial class OutDeconstructVarPendingInference
{
public BoundDeconstructValuePlaceholder Placeholder;
public BoundDeconstructValuePlaceholder SetInferredType(TypeSymbol type, bool success)
public BoundDeconstructValuePlaceholder SetInferredType(TypeSymbol type, Binder binder, bool success)
{
Debug.Assert((object)Placeholder == null);
Debug.Assert(Placeholder is null);
Placeholder = new BoundDeconstructValuePlaceholder(this.Syntax, type, hasErrors: this.HasErrors || !success);
// The val escape scope for this placeholder won't be used, so defaulting to narrowest scope
Placeholder = new BoundDeconstructValuePlaceholder(this.Syntax, binder.LocalScopeDepth, type, hasErrors: this.HasErrors || !success);
return Placeholder;
}
public BoundDeconstructValuePlaceholder FailInference(Binder binder)
{
return SetInferredType(binder.CreateErrorType(), success: false);
return SetInferredType(binder.CreateErrorType(), binder, success: false);
}
}
}
......@@ -412,33 +412,37 @@ protected BoundValuePlaceholderBase(BoundKind kind, SyntaxNode syntax, TypeSymbo
internal sealed partial class BoundDeconstructValuePlaceholder : BoundValuePlaceholderBase
{
public BoundDeconstructValuePlaceholder(SyntaxNode syntax, TypeSymbol type, bool hasErrors)
public BoundDeconstructValuePlaceholder(SyntaxNode syntax, uint valEscape, TypeSymbol type, bool hasErrors)
: base(BoundKind.DeconstructValuePlaceholder, syntax, type, hasErrors)
{
Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)");
this.ValEscape = valEscape;
}
public BoundDeconstructValuePlaceholder(SyntaxNode syntax, TypeSymbol type)
public BoundDeconstructValuePlaceholder(SyntaxNode syntax, uint valEscape, TypeSymbol type)
: base(BoundKind.DeconstructValuePlaceholder, syntax, type)
{
Debug.Assert(type != null, "Field 'type' cannot be null (use Null=\"allow\" in BoundNodes.xml to remove this check)");
this.ValEscape = valEscape;
}
public uint ValEscape { get; }
public override BoundNode Accept(BoundTreeVisitor visitor)
{
return visitor.VisitDeconstructValuePlaceholder(this);
}
public BoundDeconstructValuePlaceholder Update(TypeSymbol type)
public BoundDeconstructValuePlaceholder Update(uint valEscape, TypeSymbol type)
{
if (type != this.Type)
if (valEscape != this.ValEscape || type != this.Type)
{
var result = new BoundDeconstructValuePlaceholder(this.Syntax, type, this.HasErrors);
var result = new BoundDeconstructValuePlaceholder(this.Syntax, valEscape, type, this.HasErrors);
result.WasCompilerGenerated = this.WasCompilerGenerated;
return result;
}
......@@ -8473,7 +8477,7 @@ public override BoundNode VisitGlobalStatementInitializer(BoundGlobalStatementIn
public override BoundNode VisitDeconstructValuePlaceholder(BoundDeconstructValuePlaceholder node)
{
TypeSymbol type = this.VisitType(node.Type);
return node.Update(type);
return node.Update(node.ValEscape, type);
}
public override BoundNode VisitDup(BoundDup node)
{
......@@ -9385,6 +9389,7 @@ public override TreeDumperNode VisitDeconstructValuePlaceholder(BoundDeconstruct
{
return new TreeDumperNode("deconstructValuePlaceholder", null, new TreeDumperNode[]
{
new TreeDumperNode("valEscape", node.ValEscape, null),
new TreeDumperNode("type", node.Type, null)
}
);
......
......@@ -244,7 +244,7 @@ internal virtual void SetRefEscape(uint value)
internal virtual void SetValEscape(uint value)
{
throw ExceptionUtilities.Unreachable;
_valEscapeScope = value;
}
public override Symbol ContainingSymbol
......
......@@ -4953,24 +4953,77 @@ static void Main()
}
[Fact]
public void TupleLiteralElement004()
public void TupleLiteralElement004_WithoutValueTuple()
{
var source =
@"
class C
{
static void Main()
{
(short X, string Y) = (Alice: 1, Bob: null);
}
}
";
using System;
var compilation = CreateStandardCompilation(source);
compilation.VerifyDiagnostics(
// (6,31): error CS8179: Predefined type 'System.ValueTuple`2' is not defined or imported, or is declared in multiple referenced assemblies
// (short X, string Y) = (Alice: 1, Bob: null);
Diagnostic(ErrorCode.ERR_PredefinedValueTupleTypeNotFound, "(Alice: 1, Bob: null)").WithArguments("System.ValueTuple`2").WithLocation(6, 31),
// (6,32): warning CS8123: The tuple element name 'Alice' is ignored because a different name or no name is specified by the target type '(short, string)'.
// (short X, string Y) = (Alice: 1, Bob: null);
Diagnostic(ErrorCode.WRN_TupleLiteralNameMismatch, "Alice: 1").WithArguments("Alice", "(short, string)").WithLocation(6, 32),
// (6,42): warning CS8123: The tuple element name 'Bob' is ignored because a different name or no name is specified by the target type '(short, string)'.
// (short X, string Y) = (Alice: 1, Bob: null);
Diagnostic(ErrorCode.WRN_TupleLiteralNameMismatch, "Bob: null").WithArguments("Bob", "(short, string)").WithLocation(6, 42)
);
var tree = compilation.SyntaxTrees[0];
var decl = (ArgumentSyntax)tree.GetCompilationUnitRoot().DescendantNodes().Last(n => n.IsKind(SyntaxKind.Argument));
var model = compilation.GetSemanticModel(tree);
var element = (FieldSymbol)model.GetDeclaredSymbol(decl);
Assert.Equal(element.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), "(short Alice, string Bob).Bob");
Assert.Equal(element.DeclaringSyntaxReferences.Single().GetSyntax().ToString(), "Bob");
Assert.Equal(element.Locations.Single().IsInSource, true);
}
[Fact]
public void TupleLiteralElement004()
{
var source =
@"
class C
{
static void Main()
{
static void Main()
{
(short X, string Y) = (Alice: 1, Bob: null);
}
}
namespace System
{
public struct ValueTuple<T1, T2>
{
public T1 Item1;
public T2 Item2;
public ValueTuple(T1 item1, T2 item2)
{
this.Item1 = item1;
this.Item2 = item2;
}
}
}
";
var compilation = CreateStandardCompilation(source);
compilation.VerifyDiagnostics(
// (6,32): warning CS8123: The tuple element name 'Alice' is ignored because a different name or no name is specified by the target type '(short, string)'.
// (short X, string Y) = (Alice: 1, Bob: null);
Diagnostic(ErrorCode.WRN_TupleLiteralNameMismatch, "Alice: 1").WithArguments("Alice", "(short, string)").WithLocation(6, 32),
// (6,42): warning CS8123: The tuple element name 'Bob' is ignored because a different name or no name is specified by the target type '(short, string)'.
// (short X, string Y) = (Alice: 1, Bob: null);
Diagnostic(ErrorCode.WRN_TupleLiteralNameMismatch, "Bob: null").WithArguments("Bob", "(short, string)").WithLocation(6, 42)
);
var tree = compilation.SyntaxTrees[0];
var decl = (ArgumentSyntax)tree.GetCompilationUnitRoot().DescendantNodes().Last(n => n.IsKind(SyntaxKind.Argument));
var model = compilation.GetSemanticModel(tree);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册