diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index b6509ead28a68..705159daada2c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -265,7 +265,7 @@ private bool MakeDeconstructionConversion( var inputPlaceholder = new BoundDeconstructValuePlaceholder(syntax, this.LocalScopeDepth, type); BoundExpression deconstructInvocation = MakeDeconstructInvocationExpression(variables.Count, - inputPlaceholder, rightSyntax, diagnostics, outPlaceholders: out ImmutableArray outPlaceholders); + inputPlaceholder, rightSyntax, diagnostics, outPlaceholders: out ImmutableArray outPlaceholders, out _); if (deconstructInvocation.HasAnyErrors) { @@ -600,8 +600,10 @@ private BoundExpression MakeDeconstructInvocationExpression( BoundExpression receiver, SyntaxNode rightSyntax, DiagnosticBag diagnostics, - out ImmutableArray outPlaceholders) + out ImmutableArray outPlaceholders, + out bool anyApplicableCandidates) { + anyApplicableCandidates = false; var receiverSyntax = (CSharpSyntaxNode)receiver.Syntax; if (receiver.Type.IsDynamic()) { @@ -643,22 +645,18 @@ private BoundExpression MakeDeconstructInvocationExpression( // So the generated invocation expression will contain placeholders instead of those outVar nodes. // Those placeholders are also recorded in the outVar for easy access below, by the `SetInferredType` call on the outVar nodes. BoundExpression result = BindMethodGroupInvocation( - rightSyntax, rightSyntax, methodName, (BoundMethodGroup)memberAccess, analyzedArguments, diagnostics, queryClause: null, - allowUnexpandedForm: true); + rightSyntax, rightSyntax, methodName, (BoundMethodGroup)memberAccess, analyzedArguments, diagnostics, queryClause: null, + allowUnexpandedForm: true, anyApplicableCandidates: out anyApplicableCandidates); result.WasCompilerGenerated = true; - if (result.HasErrors && !receiver.HasAnyErrors) - { - return MissingDeconstruct(receiver, rightSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result); - } - - // Verify all the parameters (except "this" for extension methods) are out parameters - if (result.Kind != BoundKind.Call) + if (!anyApplicableCandidates) { return MissingDeconstruct(receiver, rightSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result); } + // Verify all the parameters (except "this" for extension methods) are out parameters. + // This prevents, for example, an unused params parameter after the out parameters. var deconstructMethod = ((BoundCall)result).Method; var parameters = deconstructMethod.Parameters; for (int i = (deconstructMethod.IsExtensionMethod ? 1 : 0); i < parameters.Length; i++) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 67119823f8bb2..09b983018e556 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -1640,7 +1640,7 @@ protected virtual BoundExpression BindRangeVariable(SimpleNameSyntax node, Range return Next.BindRangeVariable(node, qv, diagnostics); } - private BoundExpression SynthesizeReceiver(CSharpSyntaxNode node, Symbol member, DiagnosticBag diagnostics) + private BoundExpression SynthesizeReceiver(SyntaxNode node, Symbol member, DiagnosticBag diagnostics) { // SPEC: Otherwise, if T is the instance type of the immediately enclosing class or // struct type, if the lookup identifies an instance member, and if the reference occurs @@ -1710,7 +1710,7 @@ internal Symbol ContainingMember() return containingMember; } - private BoundExpression TryBindInteractiveReceiver(CSharpSyntaxNode syntax, Symbol currentMember, NamedTypeSymbol currentType, NamedTypeSymbol memberDeclaringType) + private BoundExpression TryBindInteractiveReceiver(SyntaxNode syntax, Symbol currentMember, NamedTypeSymbol currentType, NamedTypeSymbol memberDeclaringType) { if (currentType.TypeKind == TypeKind.Submission && !currentMember.IsStatic) { @@ -1841,7 +1841,7 @@ private BoundThisReference BindThis(ThisExpressionSyntax node, DiagnosticBag dia return ThisReference(node, this.ContainingType, hasErrors); } - private BoundThisReference ThisReference(CSharpSyntaxNode node, NamedTypeSymbol thisTypeOpt, bool hasErrors = false, bool wasCompilerGenerated = false) + private BoundThisReference ThisReference(SyntaxNode node, NamedTypeSymbol thisTypeOpt, bool hasErrors = false, bool wasCompilerGenerated = false) { return new BoundThisReference(node, thisTypeOpt ?? CreateErrorType(), hasErrors) { WasCompilerGenerated = wasCompilerGenerated }; } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 8d4f33f35803f..07042130a1c6e 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -242,7 +242,9 @@ private BoundExpression BindInvocationExpression( } else if (boundExpression.Kind == BoundKind.MethodGroup) { - result = BindMethodGroupInvocation(node, expression, methodName, (BoundMethodGroup)boundExpression, analyzedArguments, diagnostics, queryClause, allowUnexpandedForm: allowUnexpandedForm); + result = BindMethodGroupInvocation( + node, expression, methodName, (BoundMethodGroup)boundExpression, analyzedArguments, + diagnostics, queryClause, allowUnexpandedForm: allowUnexpandedForm, anyApplicableCandidates: out _); } else if ((object)(delegateType = GetDelegateType(boundExpression)) != null) { @@ -555,7 +557,8 @@ private BoundExpression BindMethodGroupInvocation( AnalyzedArguments analyzedArguments, DiagnosticBag diagnostics, CSharpSyntaxNode queryClause, - bool allowUnexpandedForm = true) + bool allowUnexpandedForm, + out bool anyApplicableCandidates) { BoundExpression result; HashSet useSiteDiagnostics = null; @@ -563,6 +566,7 @@ private BoundExpression BindMethodGroupInvocation( methodGroup, expression, methodName, analyzedArguments, isMethodGroupConversion: false, useSiteDiagnostics: ref useSiteDiagnostics, allowUnexpandedForm: allowUnexpandedForm); diagnostics.Add(expression, useSiteDiagnostics); + anyApplicableCandidates = resolution.ResultKind == LookupResultKind.Viable && resolution.OverloadResolutionResult.HasAnyApplicableMember; if (!methodGroup.HasAnyErrors) diagnostics.AddRange(resolution.Diagnostics); // Suppress cascading. @@ -1105,13 +1109,8 @@ private BoundCall BindInvocationExpressionContinued( diagnostics); } - if ((object)delegateTypeOpt != null) - { - return new BoundCall(node, receiver, method, args, argNames, argRefKinds, isDelegateCall: true, - expanded: expanded, invokedAsExtensionMethod: invokedAsExtensionMethod, - argsToParamsOpt: argsToParams, resultKind: LookupResultKind.Viable, binderOpt: this, type: returnType, hasErrors: gotError); - } - else + bool isDelegateCall = (object)delegateTypeOpt != null; + if (!isDelegateCall) { if ((object)receiver != null && receiver.Kind == BoundKind.BaseReference && method.IsAbstract) { @@ -1127,11 +1126,11 @@ private BoundCall BindInvocationExpressionContinued( receiver, diagnostics); } - - return new BoundCall(node, receiver, method, args, argNames, argRefKinds, isDelegateCall: false, - expanded: expanded, invokedAsExtensionMethod: invokedAsExtensionMethod, - argsToParamsOpt: argsToParams, resultKind: LookupResultKind.Viable, binderOpt: this, type: returnType, hasErrors: gotError); } + + return new BoundCall(node, receiver, method, args, argNames, argRefKinds, isDelegateCall: isDelegateCall, + expanded: expanded, invokedAsExtensionMethod: invokedAsExtensionMethod, + argsToParamsOpt: argsToParams, resultKind: LookupResultKind.Viable, binderOpt: this, type: returnType, hasErrors: gotError); } private bool IsBindingModuleLevelAttribute() diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs index 7ba3eaaeeb9cf..95c06d26b704c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs @@ -38,7 +38,13 @@ private BoundExpression BindIsPatternExpression(IsPatternExpressionSyntax node, hasErrors, diagnostics); } - private BoundExpression MakeIsPatternExpression(SyntaxNode node, BoundExpression expression, BoundPattern pattern, TypeSymbol boolType, bool hasErrors, DiagnosticBag diagnostics) + private BoundExpression MakeIsPatternExpression( + SyntaxNode node, + BoundExpression expression, + BoundPattern pattern, + TypeSymbol boolType, + bool hasErrors, + DiagnosticBag diagnostics) { // Note that these labels are for the convenience of the compilation of patterns, and are not actually emitted into the lowered code. LabelSymbol whenTrueLabel = new GeneratedLabelSymbol("isPatternSuccess"); @@ -67,7 +73,8 @@ private BoundExpression MakeIsPatternExpression(SyntaxNode node, BoundExpression } } - return new BoundIsPatternExpression(node, expression, pattern, decisionDag, whenTrueLabel: whenTrueLabel, whenFalseLabel: whenFalseLabel, boolType, hasErrors); + return new BoundIsPatternExpression( + node, expression, pattern, decisionDag, whenTrueLabel: whenTrueLabel, whenFalseLabel: whenFalseLabel, boolType, hasErrors); } private BoundExpression BindSwitchExpression(SwitchExpressionSyntax node, DiagnosticBag diagnostics) @@ -77,7 +84,10 @@ private BoundExpression BindSwitchExpression(SwitchExpressionSyntax node, Diagno return switchBinder.BindSwitchExpressionCore(node, switchBinder, diagnostics); } - internal virtual BoundExpression BindSwitchExpressionCore(SwitchExpressionSyntax node, Binder originalBinder, DiagnosticBag diagnostics) + internal virtual BoundExpression BindSwitchExpressionCore( + SwitchExpressionSyntax node, + Binder originalBinder, + DiagnosticBag diagnostics) { return this.Next.BindSwitchExpressionCore(node, originalBinder, diagnostics); } @@ -133,7 +143,7 @@ private BoundConstantPattern BindConstantPattern( } internal BoundConstantPattern BindConstantPattern( - CSharpSyntaxNode node, + SyntaxNode node, TypeSymbol inputType, ExpressionSyntax patternExpression, bool hasErrors, @@ -142,7 +152,8 @@ internal BoundConstantPattern BindConstantPattern( { BoundExpression expression = BindValue(patternExpression, diagnostics, BindValueKind.RValue); ConstantValue constantValueOpt = null; - BoundExpression convertedExpression = ConvertPatternExpression(inputType, patternExpression, expression, out constantValueOpt, hasErrors, diagnostics); + BoundExpression convertedExpression = ConvertPatternExpression( + inputType, patternExpression, expression, out constantValueOpt, hasErrors, diagnostics); wasExpression = expression.Type?.IsErrorType() != true; if (!convertedExpression.HasErrors && constantValueOpt == null) { @@ -156,7 +167,7 @@ internal BoundConstantPattern BindConstantPattern( convertedExpression = new BoundConversion( convertedExpression.Syntax, convertedExpression, Conversion.NoConversion, isBaseConversion: false, @checked: false, explicitCastInCode: false, constantValueOpt: constantValueOpt, conversionGroupOpt: default, type: CreateErrorType(), hasErrors: true) - { WasCompilerGenerated = true }; + { WasCompilerGenerated = true }; } return new BoundConstantPattern(node, convertedExpression, constantValueOpt ?? ConstantValue.Bad, inputType, hasErrors); @@ -178,9 +189,9 @@ internal BoundExpression ConvertPatternExpression( if (inputContainsTypeParameter) { convertedExpression = expression; - HashSet useSiteDiagnostics = null; if (!hasErrors) { + HashSet useSiteDiagnostics = null; if (expression.ConstantValue == ConstantValue.Null) { if (inputType.IsNonNullableValueType()) @@ -193,9 +204,9 @@ internal BoundExpression ConvertPatternExpression( { diagnostics.Add(ErrorCode.ERR_PatternWrongType, expression.Syntax.Location, inputType, expression.Display); } - } - diagnostics.Add(node, useSiteDiagnostics); + diagnostics.Add(node, useSiteDiagnostics); + } } else { @@ -241,7 +252,7 @@ internal BoundExpression ConvertPatternExpression( /// Check that the pattern type is valid for the operand. Return true if an error was reported. /// private bool CheckValidPatternType( - CSharpSyntaxNode typeSyntax, + SyntaxNode typeSyntax, TypeSymbol inputType, TypeSymbol patternType, bool patternTypeWasInSource, @@ -281,7 +292,8 @@ private bool CheckValidPatternType( } HashSet useSiteDiagnostics = null; - bool? matchPossible = ExpressionOfTypeMatchesPatternType(Conversions, inputType, patternType, ref useSiteDiagnostics, out Conversion conversion, operandConstantValue: null, operandCouldBeNull: true); + bool? matchPossible = ExpressionOfTypeMatchesPatternType( + Conversions, inputType, patternType, ref useSiteDiagnostics, out Conversion conversion, operandConstantValue: null, operandCouldBeNull: true); diagnostics.Add(typeSyntax, useSiteDiagnostics); if (matchPossible != false) { @@ -353,7 +365,9 @@ private BoundPattern BindDeclarationPattern( Debug.Assert(!isVar); TypeSymbol declType = boundDeclType.Type; inputValEscape = GetValEscape(declType, inputValEscape); - BindPatternDesignation(node, node.Designation, boundDeclType.Type, inputValEscape, typeSyntax, diagnostics, ref hasErrors, out Symbol variableSymbol, out BoundExpression variableAccess); + BindPatternDesignation( + node.Designation, boundDeclType.Type, inputValEscape, typeSyntax, diagnostics, + ref hasErrors, out Symbol variableSymbol, out BoundExpression variableAccess); return new BoundDeclarationPattern(node, variableSymbol, variableAccess, boundDeclType, isVar: false, inputType, hasErrors); } @@ -369,9 +383,7 @@ private BoundTypeExpression BindPatternType( AliasSymbol aliasOpt; TypeSymbolWithAnnotations declType = BindTypeOrVarKeyword(typeSyntax, diagnostics, out isVar, out aliasOpt); if (isVar) - { declType = TypeSymbolWithAnnotations.Create(NonNullTypesContext, inputType); - } if (declType.IsNull) { @@ -386,7 +398,6 @@ private BoundTypeExpression BindPatternType( } private void BindPatternDesignation( - PatternSyntax node, VariableDesignationSyntax designation, TypeSymbol declType, uint inputValEscape, @@ -405,9 +416,7 @@ private void BindPatternDesignation( if (localSymbol != (object)null) { if ((InConstructorInitializer || InFieldInitializer) && ContainingMemberOrLambda.ContainingSymbol.Kind == SymbolKind.NamedType) - { - CheckFeatureAvailability(node, MessageID.IDS_FeatureExpressionVariablesInQueriesAndInitializers, diagnostics); - } + CheckFeatureAvailability(designation, MessageID.IDS_FeatureExpressionVariablesInQueriesAndInitializers, diagnostics); localSymbol.SetType(TypeSymbolWithAnnotations.Create(declType)); localSymbol.SetValEscape(GetValEscape(declType, inputValEscape)); @@ -416,28 +425,26 @@ private void BindPatternDesignation( hasErrors |= localSymbol.ScopeBinder.ValidateDeclarationNameConflictsInScope(localSymbol, diagnostics); if (!hasErrors) - { hasErrors = CheckRestrictedTypeInAsync(this.ContainingMemberOrLambda, declType, diagnostics, typeSyntax ?? (SyntaxNode)designation); - } variableSymbol = localSymbol; variableAccess = new BoundLocal( - syntax: node, localSymbol: localSymbol, constantValueOpt: null, type: declType); + syntax: designation, localSymbol: localSymbol, constantValueOpt: null, type: declType); return; } else { // We should have the right binder in the chain for a script or interactive, so we use the field for the pattern. - Debug.Assert(node.SyntaxTree.Options.Kind != SourceCodeKind.Regular); + Debug.Assert(designation.SyntaxTree.Options.Kind != SourceCodeKind.Regular); GlobalExpressionVariable expressionVariableField = LookupDeclaredField(singleVariableDesignation); DiagnosticBag tempDiagnostics = DiagnosticBag.GetInstance(); expressionVariableField.SetType(TypeSymbolWithAnnotations.Create(declType), tempDiagnostics); tempDiagnostics.Free(); - BoundExpression receiver = SynthesizeReceiver(node, expressionVariableField, diagnostics); + BoundExpression receiver = SynthesizeReceiver(designation, expressionVariableField, diagnostics); variableSymbol = expressionVariableField; variableAccess = new BoundFieldAccess( - syntax: node, receiver: receiver, fieldSymbol: expressionVariableField, constantValueOpt: null, hasErrors: hasErrors); + syntax: designation, receiver: receiver, fieldSymbol: expressionVariableField, constantValueOpt: null, hasErrors: hasErrors); return; } case DiscardDesignationSyntax _: @@ -460,7 +467,12 @@ private static uint GetValEscape(TypeSymbol type, uint possibleValEscape) return type.IsByRefLikeType ? possibleValEscape : Binder.ExternalScope; } - TypeSymbol BindRecursivePatternType(TypeSyntax typeSyntax, TypeSymbol inputType, DiagnosticBag diagnostics, ref bool hasErrors, out BoundTypeExpression boundDeclType) + TypeSymbol BindRecursivePatternType( + TypeSyntax typeSyntax, + TypeSymbol inputType, + DiagnosticBag diagnostics, + ref bool hasErrors, + out BoundTypeExpression boundDeclType) { if (typeSyntax != null) { @@ -486,6 +498,16 @@ TypeSymbol BindRecursivePatternType(TypeSyntax typeSyntax, TypeSymbol inputType, } } + // Work around https://github.com/dotnet/roslyn/issues/20648: The compiler's internal APIs such as `declType.IsTupleType` + // do not correctly treat the non-generic struct `System.ValueTuple` as a tuple type. We explicitly perform the tests + // required to identify it. When that bug is fixed we should be able to remove this code and its callers. + internal static bool IsZeroElementTupleType(TypeSymbol type) + { + return type.IsStructType() && type.Name == "ValueTuple" && type.GetArity() == 0 && + type.ContainingSymbol is var declContainer && declContainer.Kind == SymbolKind.Namespace && declContainer.Name == "System" && + (declContainer.ContainingSymbol as NamespaceSymbol)?.IsGlobalNamespace == true; + } + private BoundPattern BindRecursivePattern(RecursivePatternSyntax node, TypeSymbol inputType, uint inputValEscape, bool hasErrors, DiagnosticBag diagnostics) { if (inputType.IsPointerType()) @@ -499,16 +521,54 @@ private BoundPattern BindRecursivePattern(RecursivePatternSyntax node, TypeSymbo TypeSymbol declType = BindRecursivePatternType(typeSyntax, inputType, diagnostics, ref hasErrors, out BoundTypeExpression boundDeclType); inputValEscape = GetValEscape(declType, inputValEscape); - if (ShouldUseITuple(node, declType, diagnostics, out NamedTypeSymbol iTupleType, out MethodSymbol iTupleGetLength, out MethodSymbol iTupleGetItem)) - { - return BindITuplePattern(node, inputType, iTupleType, iTupleGetLength, iTupleGetItem, hasErrors, diagnostics); - } - MethodSymbol deconstructMethod = null; ImmutableArray deconstructionSubpatterns = default; if (node.DeconstructionPatternClause != null) { - deconstructionSubpatterns = BindDeconstructionPatternClause(node.DeconstructionPatternClause, declType, inputValEscape, diagnostics, out deconstructMethod, ref hasErrors); + DeconstructionPatternClauseSyntax deconstructClause = node.DeconstructionPatternClause; + var patternsBuilder = ArrayBuilder.GetInstance(deconstructClause.Subpatterns.Count); + if (IsZeroElementTupleType(declType)) + { + // Work around https://github.com/dotnet/roslyn/issues/20648: The compiler's internal APIs such as `declType.IsTupleType` + // do not correctly treat the non-generic struct `System.ValueTuple` as a tuple type. We explicitly perform the tests + // required to identify it. When that bug is fixed we should be able to remove this if statement. + BindValueTupleSubpatterns( + deconstructClause, declType, ImmutableArray.Empty, inputValEscape, ref hasErrors, patternsBuilder, diagnostics); + } + else if (declType.IsTupleType) + { + // It is a tuple type. Work according to its elements + BindValueTupleSubpatterns(deconstructClause, declType, declType.TupleElementTypes, inputValEscape, ref hasErrors, patternsBuilder, diagnostics); + } + else + { + // It is not a tuple type. Seek an appropriate Deconstruct method. + var inputPlaceholder = new BoundImplicitReceiver(deconstructClause, declType); // A fake receiver expression to permit us to reuse binding logic + var deconstructDiagnostics = DiagnosticBag.GetInstance(); + BoundExpression deconstruct = MakeDeconstructInvocationExpression( + deconstructClause.Subpatterns.Count, inputPlaceholder, deconstructClause, + deconstructDiagnostics, outPlaceholders: out ImmutableArray outPlaceholders, + out bool anyDeconstructCandidates); + if (!anyDeconstructCandidates && + ShouldUseITupleForRecursivePattern(node, declType, diagnostics, out var iTupleType, out var iTupleGetLength, out var iTupleGetItem)) + { + // There was no Deconstruct, but the constraints for the use of ITuple are satisfied. + // Use that and forget any errors from trying to bind Deconstruct. + deconstructDiagnostics.Free(); + BindITupleSubpatterns(deconstructClause, patternsBuilder, diagnostics); + deconstructionSubpatterns = patternsBuilder.ToImmutableAndFree(); + return new BoundITuplePattern(node, iTupleGetLength, iTupleGetItem, deconstructionSubpatterns, inputType, hasErrors); + } + else + { + diagnostics.AddRangeAndFree(deconstructDiagnostics); + } + + deconstructMethod = BindDeconstructSubpatterns( + deconstructClause, inputValEscape, deconstruct, outPlaceholders, patternsBuilder, ref hasErrors, diagnostics); + } + + deconstructionSubpatterns = patternsBuilder.ToImmutableAndFree(); } ImmutableArray properties = default; @@ -517,137 +577,145 @@ private BoundPattern BindRecursivePattern(RecursivePatternSyntax node, TypeSymbo properties = BindPropertyPatternClause(node.PropertyPatternClause, declType, inputValEscape, diagnostics, ref hasErrors); } - BindPatternDesignation(node, node.Designation, declType, inputValEscape, typeSyntax, diagnostics, ref hasErrors, out Symbol variableSymbol, out BoundExpression variableAccess); + BindPatternDesignation( + node.Designation, declType, inputValEscape, typeSyntax, diagnostics, + ref hasErrors, out Symbol variableSymbol, out BoundExpression variableAccess); return new BoundRecursivePattern( syntax: node, declaredType: boundDeclType, inputType: inputType, deconstructMethod: deconstructMethod, - deconstruction: deconstructionSubpatterns, properties: properties, variable: variableSymbol, variableAccess: variableAccess, hasErrors: hasErrors); + deconstruction: deconstructionSubpatterns, properties: properties, variable: variableSymbol, + variableAccess: variableAccess, hasErrors: hasErrors); } - private BoundPattern BindITuplePattern( - RecursivePatternSyntax node, - TypeSymbol inputType, - NamedTypeSymbol iTupleType, - MethodSymbol iTupleGetLength, - MethodSymbol iTupleGetItem, - bool hasErrors, + private MethodSymbol BindDeconstructSubpatterns( + DeconstructionPatternClauseSyntax node, + uint inputValEscape, + BoundExpression deconstruct, + ImmutableArray outPlaceholders, + ArrayBuilder patterns, + ref bool hasErrors, DiagnosticBag diagnostics) { + var deconstructMethod = deconstruct.ExpressionSymbol as MethodSymbol; + if (deconstructMethod is null) + hasErrors = true; + + int skippedExtensionParameters = deconstructMethod?.IsExtensionMethod == true ? 1 : 0; + for (int i = 0; i < node.Subpatterns.Count; i++) + { + var subPattern = node.Subpatterns[i]; + bool isError = outPlaceholders.IsDefaultOrEmpty || i >= outPlaceholders.Length; + TypeSymbol elementType = isError ? CreateErrorType() : outPlaceholders[i].Type; + ParameterSymbol parameter = null; + if (subPattern.NameColon != null && !isError) + { + // Check that the given name is the same as the corresponding parameter of the method. + string name = subPattern.NameColon.Name.Identifier.ValueText; + int parameterIndex = i + skippedExtensionParameters; + if (parameterIndex < deconstructMethod.ParameterCount) + { + parameter = deconstructMethod.Parameters[parameterIndex]; + string parameterName = parameter.Name; + if (name != parameterName) + { + diagnostics.Add(ErrorCode.ERR_DeconstructParameterNameMismatch, subPattern.NameColon.Name.Location, name, parameterName); + } + } + else + { + Debug.Assert(deconstructMethod is ErrorMethodSymbol); + } + } + + var boundSubpattern = new BoundSubpattern( + subPattern, + parameter, + BindPattern(subPattern.Pattern, elementType, GetValEscape(elementType, inputValEscape), isError, diagnostics) + ); + patterns.Add(boundSubpattern); + } + + return deconstructMethod; + } + + private void BindITupleSubpatterns( + DeconstructionPatternClauseSyntax node, + ArrayBuilder patterns, + DiagnosticBag diagnostics) + { + // Since the input has been cast to ITuple, it must be escapable. + const uint valEscape = Binder.ExternalScope; var objectType = Compilation.GetSpecialType(SpecialType.System_Object); - var deconstruction = node.DeconstructionPatternClause; - var patterns = ArrayBuilder.GetInstance(deconstruction.Subpatterns.Count); - foreach (var subpatternSyntax in deconstruction.Subpatterns) + foreach (var subpatternSyntax in node.Subpatterns) { - TypeSymbol elementType = objectType; if (subpatternSyntax.NameColon != null) { // error: name not permitted in ITuple deconstruction diagnostics.Add(ErrorCode.ERR_ArgumentNameInITuplePattern, subpatternSyntax.NameColon.Location); } - // Since the input has been cast to ITuple, it must be escapable. - const uint valEscape = Binder.ExternalScope; var boundSubpattern = new BoundSubpattern( subpatternSyntax, null, - BindPattern(subpatternSyntax.Pattern, elementType, valEscape, false, diagnostics)); + BindPattern(subpatternSyntax.Pattern, objectType, valEscape, hasErrors: false, diagnostics)); patterns.Add(boundSubpattern); } + } - return new BoundITuplePattern(node, iTupleGetLength, iTupleGetItem, patterns.ToImmutableAndFree(), inputType, hasErrors); + private void BindITupleSubpatterns( + ParenthesizedVariableDesignationSyntax node, + ArrayBuilder patterns, + DiagnosticBag diagnostics) + { + // Since the input has been cast to ITuple, it must be escapable. + const uint valEscape = Binder.ExternalScope; + var objectType = Compilation.GetSpecialType(SpecialType.System_Object); + foreach (var variable in node.Variables) + { + BoundPattern pattern = BindVarDesignation(variable, objectType, valEscape, hasErrors: false, diagnostics); + var boundSubpattern = new BoundSubpattern( + variable, + null, + pattern); + patterns.Add(boundSubpattern); + } } - private ImmutableArray BindDeconstructionPatternClause( + private void BindValueTupleSubpatterns( DeconstructionPatternClauseSyntax node, TypeSymbol declType, + ImmutableArray elementTypes, uint inputValEscape, - DiagnosticBag diagnostics, - out MethodSymbol deconstructMethod, - ref bool hasErrors) + ref bool hasErrors, + ArrayBuilder patterns, + DiagnosticBag diagnostics) { - deconstructMethod = null; - var patterns = ArrayBuilder.GetInstance(node.Subpatterns.Count); - if (declType.IsTupleType) + if (elementTypes.Length != node.Subpatterns.Count && !hasErrors) { - // It is a tuple type. Work according to its elements - ImmutableArray elementTypes = declType.TupleElementTypes; - if (elementTypes.Length != node.Subpatterns.Count && !hasErrors) - { - var location = new SourceLocation(node.SyntaxTree, new Text.TextSpan(node.OpenParenToken.SpanStart, node.CloseParenToken.Span.End - node.OpenParenToken.SpanStart)); - diagnostics.Add(ErrorCode.ERR_WrongNumberOfSubpatterns, location, declType, elementTypes.Length, node.Subpatterns.Count); - hasErrors = true; - } - - for (int i = 0; i < node.Subpatterns.Count; i++) - { - var subpatternSyntax = node.Subpatterns[i]; - bool isError = i >= elementTypes.Length; - TypeSymbol elementType = isError ? CreateErrorType() : elementTypes[i].TypeSymbol; - FieldSymbol foundField = null; - if (subpatternSyntax.NameColon != null) - { - string name = subpatternSyntax.NameColon.Name.Identifier.ValueText; - foundField = CheckIsTupleElement(subpatternSyntax.NameColon.Name, (NamedTypeSymbol)declType, name, i, diagnostics); - } - - BoundSubpattern boundSubpattern = new BoundSubpattern( - subpatternSyntax, - foundField, - BindPattern(subpatternSyntax.Pattern, elementType, GetValEscape(elementType, inputValEscape), isError, diagnostics)); - patterns.Add(boundSubpattern); - } + diagnostics.Add(ErrorCode.ERR_WrongNumberOfSubpatterns, node.Location, declType, elementTypes.Length, node.Subpatterns.Count); + hasErrors = true; } - else + + for (int i = 0; i < node.Subpatterns.Count; i++) { - // It is not a tuple type or ITuple. Seek an appropriate Deconstruct method. - var inputPlaceholder = new BoundImplicitReceiver(node, declType); // A fake receiver expression to permit us to reuse binding logic - BoundExpression deconstruct = MakeDeconstructInvocationExpression( - node.Subpatterns.Count, inputPlaceholder, node, diagnostics, outPlaceholders: out ImmutableArray outPlaceholders); - deconstructMethod = deconstruct.ExpressionSymbol as MethodSymbol; - if (deconstructMethod is null) + var subpatternSyntax = node.Subpatterns[i]; + bool isError = i >= elementTypes.Length; + TypeSymbol elementType = isError ? CreateErrorType() : elementTypes[i].TypeSymbol; + FieldSymbol foundField = null; + if (subpatternSyntax.NameColon != null && !isError) { - hasErrors = true; + string name = subpatternSyntax.NameColon.Name.Identifier.ValueText; + foundField = CheckIsTupleElement(subpatternSyntax.NameColon.Name, (NamedTypeSymbol)declType, name, i, diagnostics); } - int skippedExtensionParameters = deconstructMethod?.IsExtensionMethod == true ? 1 : 0; - for (int i = 0; i < node.Subpatterns.Count; i++) - { - var subPattern = node.Subpatterns[i]; - bool isError = outPlaceholders.IsDefaultOrEmpty || i >= outPlaceholders.Length; - TypeSymbol elementType = isError ? CreateErrorType() : outPlaceholders[i].Type; - ParameterSymbol parameter = null; - if (subPattern.NameColon != null && !isError) - { - // Check that the given name is the same as the corresponding parameter of the method. - string name = subPattern.NameColon.Name.Identifier.ValueText; - int parameterIndex = i + skippedExtensionParameters; - if (parameterIndex < deconstructMethod.ParameterCount) - { - parameter = deconstructMethod.Parameters[parameterIndex]; - string parameterName = parameter.Name; - if (name != parameterName) - { - diagnostics.Add(ErrorCode.ERR_DeconstructParameterNameMismatch, subPattern.NameColon.Name.Location, name, parameterName); - } - } - else - { - Debug.Assert(deconstructMethod is ErrorMethodSymbol); - } - } - - var boundSubpattern = new BoundSubpattern( - subPattern, - parameter, - BindPattern(subPattern.Pattern, elementType, GetValEscape(elementType, inputValEscape), isError, diagnostics) - ); - patterns.Add(boundSubpattern); - } + BoundSubpattern boundSubpattern = new BoundSubpattern( + subpatternSyntax, + foundField, + BindPattern(subpatternSyntax.Pattern, elementType, GetValEscape(elementType, inputValEscape), isError, diagnostics)); + patterns.Add(boundSubpattern); } - - return patterns.ToImmutableAndFree(); } - private bool ShouldUseITuple( + private bool ShouldUseITupleForRecursivePattern( RecursivePatternSyntax node, TypeSymbol declType, DiagnosticBag diagnostics, @@ -657,11 +725,6 @@ private bool ShouldUseITuple( { iTupleType = null; iTupleGetLength = iTupleGetItem = null; - if (declType.IsTupleType) - { - return false; - } - if (node.Type != null) { // ITuple matching only applies if no type is given explicitly. @@ -680,12 +743,28 @@ private bool ShouldUseITuple( return false; } - if (node.Designation != null) + if (node.Designation?.Kind() == SyntaxKind.SingleVariableDesignation) { - // ITuple matching only applies if there is no designation (what type would the designation be?) + // ITuple matching only applies if there is no variable declared (what type would the variable be?) return false; } + return ShouldUseITuple(node, declType, diagnostics, out iTupleType, out iTupleGetLength, out iTupleGetItem); + } + + private bool ShouldUseITuple( + SyntaxNode node, + TypeSymbol declType, + DiagnosticBag diagnostics, + out NamedTypeSymbol iTupleType, + out MethodSymbol iTupleGetLength, + out MethodSymbol iTupleGetItem) + { + iTupleType = null; + iTupleGetLength = iTupleGetItem = null; + Debug.Assert(!declType.IsTupleType); + Debug.Assert(!IsZeroElementTupleType(declType)); + if (Compilation.LanguageVersion < MessageID.IDS_FeatureRecursivePatterns.RequiredVersion()) { return false; @@ -699,7 +778,7 @@ private bool ShouldUseITuple( } // Resolution 2017-11-20 LDM: permit matching via ITuple only for `object`, `ITuple`, and types that are - // declared to implement `ITuple` but contain no `Deconstruct` methods. + // declared to implement `ITuple`. if (declType != (object)Compilation.GetSpecialType(SpecialType.System_Object) && declType != (object)Compilation.DynamicType && declType != (object)iTupleType && @@ -708,30 +787,6 @@ private bool ShouldUseITuple( return false; } - // If there are any accessible Deconstruct method members, then we do not permit ITuple - var lookupResult = LookupResult.GetInstance(); - try - { - const LookupOptions options = LookupOptions.MustBeInvocableIfMember | LookupOptions.AllMethodsOnArityZero; - HashSet useSiteDiagnostics = null; - this.LookupMembersWithFallback(lookupResult, declType, WellKnownMemberNames.DeconstructMethodName, arity: 0, ref useSiteDiagnostics, basesBeingResolved: null, options: options); - diagnostics.Add(node, useSiteDiagnostics); - if (lookupResult.IsMultiViable) - { - foreach (var symbol in lookupResult.Symbols) - { - if (symbol.Kind == SymbolKind.Method) - { - return false; - } - } - } - } - finally - { - lookupResult.Free(); - } - // Ensure ITuple has a Length and indexer iTupleGetLength = (MethodSymbol)Compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_ITuple__get_Length); iTupleGetItem = (MethodSymbol)Compilation.GetWellKnownTypeMember(WellKnownMember.System_Runtime_CompilerServices_ITuple__get_Item); @@ -794,66 +849,85 @@ private BoundPattern BindVarPattern(VarPatternSyntax node, TypeSymbol inputType, hasErrors = true; } - return BindVarDesignation(node, node.Designation, inputType, inputValEscape, hasErrors, diagnostics); + return BindVarDesignation(node.Designation, inputType, inputValEscape, hasErrors, diagnostics); } - private BoundPattern BindVarDesignation(VarPatternSyntax node, VariableDesignationSyntax designation, TypeSymbol inputType, uint inputValEscape, bool hasErrors, DiagnosticBag diagnostics) + private BoundPattern BindVarDesignation( + VariableDesignationSyntax node, + TypeSymbol inputType, + uint inputValEscape, + bool hasErrors, + DiagnosticBag diagnostics) { - switch (designation.Kind()) + switch (node.Kind()) { case SyntaxKind.DiscardDesignation: { - return new BoundDiscardPattern(designation, inputType); + return new BoundDiscardPattern(node, inputType); } case SyntaxKind.SingleVariableDesignation: { BindPatternDesignation( - node: node, designation: designation, declType: inputType, inputValEscape: inputValEscape, typeSyntax: null, diagnostics: diagnostics, - hasErrors: ref hasErrors, variableSymbol: out Symbol variableSymbol, variableAccess: out BoundExpression variableAccess); + designation: node, declType: inputType, inputValEscape: inputValEscape, typeSyntax: null, diagnostics: diagnostics, hasErrors: ref hasErrors, + variableSymbol: out Symbol variableSymbol, variableAccess: out BoundExpression variableAccess); var boundOperandType = new BoundTypeExpression(syntax: node, aliasOpt: null, type: inputType); // fake a type expression for the variable's type // We continue to use a BoundDeclarationPattern for the var pattern, as they have more in common. return new BoundDeclarationPattern( - designation.Parent == node ? node : (SyntaxNode)designation, // for `var x` use whole pattern, otherwise use designation for the syntax + node.Parent.Kind() == SyntaxKind.VarPattern ? node.Parent : node, // for `var x` use whole pattern, otherwise use designation for the syntax variableSymbol, variableAccess, boundOperandType, isVar: true, inputType: inputType, hasErrors: hasErrors); } case SyntaxKind.ParenthesizedVariableDesignation: { - var tupleDesignation = (ParenthesizedVariableDesignationSyntax)designation; + var tupleDesignation = (ParenthesizedVariableDesignationSyntax)node; var subPatterns = ArrayBuilder.GetInstance(tupleDesignation.Variables.Count); MethodSymbol deconstructMethod = null; - if (inputType.IsTupleType) + var strippedInputType = inputType.StrippedType(); + + if (IsZeroElementTupleType(strippedInputType)) + { + // Work around https://github.com/dotnet/roslyn/issues/20648: The compiler's internal APIs such as `declType.IsTupleType` + // do not correctly treat the non-generic struct `System.ValueTuple` as a tuple type. We explicitly perform the tests + // required to identify it. When that bug is fixed we should be able to remove this if statement. + addSubpatternsForTuple(ImmutableArray.Empty); + } + else if (strippedInputType.IsTupleType) { // It is a tuple type. Work according to its elements - ImmutableArray elementTypes = inputType.TupleElementTypes; - if (elementTypes.Length != tupleDesignation.Variables.Count && !hasErrors) - { - var location = new SourceLocation(node.SyntaxTree, - new Text.TextSpan(tupleDesignation.OpenParenToken.SpanStart, tupleDesignation.CloseParenToken.Span.End - tupleDesignation.OpenParenToken.SpanStart)); - diagnostics.Add(ErrorCode.ERR_WrongNumberOfSubpatterns, location, inputType.TupleElementTypes, elementTypes.Length, tupleDesignation.Variables.Count); - hasErrors = true; - } - for (int i = 0; i < tupleDesignation.Variables.Count; i++) - { - var variable = tupleDesignation.Variables[i]; - bool isError = i >= elementTypes.Length; - TypeSymbol elementType = isError ? CreateErrorType() : elementTypes[i].TypeSymbol; - BoundPattern pattern = BindVarDesignation(node, variable, elementType, GetValEscape(elementType, inputValEscape), isError, diagnostics); - subPatterns.Add(new BoundSubpattern(variable, symbol: null, pattern)); - } + addSubpatternsForTuple(strippedInputType.TupleElementTypes); } else { // It is not a tuple type. Seek an appropriate Deconstruct method. - var inputPlaceholder = new BoundImplicitReceiver(node, inputType); // A fake receiver expression to permit us to reuse binding logic + var inputPlaceholder = new BoundImplicitReceiver(node, strippedInputType); // A fake receiver expression to permit us to reuse binding logic + var deconstructDiagnostics = DiagnosticBag.GetInstance(); BoundExpression deconstruct = MakeDeconstructInvocationExpression( - tupleDesignation.Variables.Count, inputPlaceholder, node, diagnostics, outPlaceholders: out ImmutableArray outPlaceholders); + tupleDesignation.Variables.Count, inputPlaceholder, node, deconstructDiagnostics, + outPlaceholders: out ImmutableArray outPlaceholders, + out bool anyDeconstructCandidates); + if (!anyDeconstructCandidates && + ShouldUseITuple(node, strippedInputType, diagnostics, out var iTupleType, out var iTupleGetLength, out var iTupleGetItem)) + { + // There was no applicable candidate Deconstruct, and the constraints for the use of ITuple are satisfied. + // Use that and forget any errors from trying to bind Deconstruct. + deconstructDiagnostics.Free(); + BindITupleSubpatterns(tupleDesignation, subPatterns, diagnostics); + return new BoundITuplePattern(node, iTupleGetLength, iTupleGetItem, subPatterns.ToImmutableAndFree(), strippedInputType, hasErrors); + } + else + { + diagnostics.AddRangeAndFree(deconstructDiagnostics); + } + deconstructMethod = deconstruct.ExpressionSymbol as MethodSymbol; + if (!hasErrors) + hasErrors = outPlaceholders.IsDefaultOrEmpty || tupleDesignation.Variables.Count != outPlaceholders.Length; + for (int i = 0; i < tupleDesignation.Variables.Count; i++) { var variable = tupleDesignation.Variables[i]; bool isError = outPlaceholders.IsDefaultOrEmpty || i >= outPlaceholders.Length; TypeSymbol elementType = isError ? CreateErrorType() : outPlaceholders[i].Type; - BoundPattern pattern = BindVarDesignation(node, variable, elementType, GetValEscape(elementType, inputValEscape), isError, diagnostics); + BoundPattern pattern = BindVarDesignation(variable, elementType, GetValEscape(elementType, inputValEscape), isError, diagnostics); subPatterns.Add(new BoundSubpattern(variable, symbol: null, pattern)); } } @@ -861,10 +935,28 @@ private BoundPattern BindVarDesignation(VarPatternSyntax node, VariableDesignati return new BoundRecursivePattern( syntax: node, declaredType: null, inputType: inputType, deconstructMethod: deconstructMethod, deconstruction: subPatterns.ToImmutableAndFree(), properties: default, variable: null, variableAccess: null, hasErrors: hasErrors); + + void addSubpatternsForTuple(ImmutableArray elementTypes) + { + if (elementTypes.Length != tupleDesignation.Variables.Count && !hasErrors) + { + diagnostics.Add(ErrorCode.ERR_WrongNumberOfSubpatterns, tupleDesignation.Location, + strippedInputType.TupleElementTypes, elementTypes.Length, tupleDesignation.Variables.Count); + hasErrors = true; + } + for (int i = 0; i < tupleDesignation.Variables.Count; i++) + { + var variable = tupleDesignation.Variables[i]; + bool isError = i >= elementTypes.Length; + TypeSymbol elementType = isError ? CreateErrorType() : elementTypes[i].TypeSymbol; + BoundPattern pattern = BindVarDesignation(variable, elementType, GetValEscape(elementType, inputValEscape), isError, diagnostics); + subPatterns.Add(new BoundSubpattern(variable, symbol: null, pattern)); + } + } } default: { - throw ExceptionUtilities.UnexpectedValue(designation.Kind()); + throw ExceptionUtilities.UnexpectedValue(node.Kind()); } } } @@ -886,9 +978,7 @@ ImmutableArray BindPropertyPatternClause( if (name == null) { if (!hasErrors) - { diagnostics.Add(ErrorCode.ERR_PropertyPatternNameMissing, pattern.Location, pattern); - } memberType = CreateErrorType(); hasErrors = true; @@ -911,13 +1001,9 @@ private Symbol LookupMemberForPropertyPattern( Symbol symbol = BindPropertyPatternMember(inputType, name, ref hasErrors, diagnostics); if (inputType.IsErrorType() || hasErrors || symbol == (object)null) - { memberType = CreateErrorType(); - } else - { memberType = symbol.GetTypeOrReturnType().TypeSymbol; - } return symbol; } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index eee5013a6d0bb..f71fb106ad895 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1204,14 +1204,15 @@ private MethodSymbol GetFixedPatternMethodOpt(BoundExpression initializer, Diagn var analyzedArguments = AnalyzedArguments.GetInstance(); BoundExpression patternMethodCall = BindMethodGroupInvocation( - initializer.Syntax, - initializer.Syntax, - methodName, - (BoundMethodGroup)boundAccess, - analyzedArguments, - bindingDiagnostics, - queryClause: null, - allowUnexpandedForm: false); + initializer.Syntax, + initializer.Syntax, + methodName, + (BoundMethodGroup)boundAccess, + analyzedArguments, + bindingDiagnostics, + queryClause: null, + allowUnexpandedForm: false, + anyApplicableCandidates: out _); analyzedArguments.Free(); diff --git a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs index b90cd15343a05..f1152764f171b 100644 --- a/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs +++ b/src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs @@ -459,6 +459,14 @@ private void MakeTestsAndBindings( MakeTestsAndBindings(output, pattern, tests, bindings); } } + else if (Binder.IsZeroElementTupleType(inputType)) + { + // Work around https://github.com/dotnet/roslyn/issues/20648: The compiler's internal APIs such as `declType.IsTupleType` + // do not correctly treat the non-generic struct `System.ValueTuple` as a tuple type. We explicitly perform the tests + // required to identify it. When that bug is fixed we should be able to remove this if statement. + + // nothing to do, as there are no tests for the zero elements of this tuple + } else if (inputType.IsTupleType) { ImmutableArray elements = inputType.TupleElements; diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index dde24127dd0ad..aaee73bbaed71 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -9304,11 +9304,11 @@ internal static string ERR_SignButNoPrivateKey { } /// - /// Looks up a localized string similar to A single-element deconstruct pattern requires a type before the open parenthesis.. + /// Looks up a localized string similar to A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'.. /// - internal static string ERR_SingleElementPositionalPatternRequiresType { + internal static string ERR_SingleElementPositionalPatternRequiresDisambiguation { get { - return ResourceManager.GetString("ERR_SingleElementPositionalPatternRequiresType", resourceCulture); + return ResourceManager.GetString("ERR_SingleElementPositionalPatternRequiresDisambiguation", resourceCulture); } } diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index fcde0a9c8f87b..f5a1dd8ac1f8b 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5586,8 +5586,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ No best type was found for the switch expression. - - A single-element deconstruct pattern requires a type before the open parenthesis. + + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. The syntax 'var' for a pattern is not permitted to refer to a type, but '{0}' is in scope here. diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 8728dfaa2c92e..d2859337eddca 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1601,7 +1601,7 @@ internal enum ErrorCode ERR_MissingPattern = 8504, ERR_DefaultPattern = 8505, ERR_SwitchExpressionNoBestType = 8506, - ERR_SingleElementPositionalPatternRequiresType = 8507, + ERR_SingleElementPositionalPatternRequiresDisambiguation = 8507, ERR_VarMayNotBindToType = 8508, WRN_SwitchExpressionNotExhaustive = 8509, ERR_SwitchArmSubsumed = 8510, diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 3489030c674fd..b5e989fce9651 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -8385,30 +8385,42 @@ private StatementSyntax ParseLocalDeclarationStatement() } } - private VariableDesignationSyntax ParseDesignation() + private VariableDesignationSyntax ParseDesignation(bool forPattern) { // the two forms of designation are // (1) identifier // (2) ( designation ... ) + // for pattern-matching, we permit the designation list to be empty VariableDesignationSyntax result; if (this.CurrentToken.Kind == SyntaxKind.OpenParenToken) { var openParen = this.EatToken(SyntaxKind.OpenParenToken); var listOfDesignations = _pool.AllocateSeparated(); - listOfDesignations.Add(ParseDesignation()); - listOfDesignations.AddSeparator(this.EatToken(SyntaxKind.CommaToken)); + bool done = false; + if (forPattern) + { + done = (this.CurrentToken.Kind == SyntaxKind.CloseParenToken); + } + else + { + listOfDesignations.Add(ParseDesignation(forPattern)); + listOfDesignations.AddSeparator(EatToken(SyntaxKind.CommaToken)); + } - while (true) + if (!done) { - listOfDesignations.Add(ParseDesignation()); - if (this.CurrentToken.Kind == SyntaxKind.CommaToken) - { - listOfDesignations.AddSeparator(this.EatToken(SyntaxKind.CommaToken)); - } - else + while (true) { - break; + listOfDesignations.Add(ParseDesignation(forPattern)); + if (this.CurrentToken.Kind == SyntaxKind.CommaToken) + { + listOfDesignations.AddSeparator(this.EatToken(SyntaxKind.CommaToken)); + } + else + { + break; + } } } @@ -9304,7 +9316,7 @@ private ExpressionSyntax ParseSubExpressionCore(Precedence precedence) private ExpressionSyntax ParseDeclarationExpression(ParseTypeMode mode, MessageID feature) { TypeSyntax type = this.ParseType(mode); - var designation = ParseDesignation(); + var designation = ParseDesignation(forPattern: false); if (feature != MessageID.None) { designation = CheckFeatureAvailability(designation, feature); diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser_Patterns.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser_Patterns.cs index df240a4195d2a..a5b72e500aacf 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser_Patterns.cs @@ -419,7 +419,7 @@ private PatternSyntax ParsePatternContinued(TypeSyntax type, bool whenIsKeyword) { // we have a "var" pattern; "var" is not permitted to be a stand-in for a type (or a constant) in a pattern. var varToken = ConvertToKeyword(typeIdentifierToken); - var varDesignation = ParseDesignation(); + var varDesignation = ParseDesignation(forPattern: true); return _syntaxFactory.VarPattern(varToken, varDesignation); } } @@ -456,11 +456,16 @@ private PatternSyntax ParsePatternContinued(TypeSyntax type, bool whenIsKeyword) var deconstructionPatternClause = _syntaxFactory.DeconstructionPatternClause(openParenToken, subPatterns, closeParenToken); var result = _syntaxFactory.RecursivePattern(type, deconstructionPatternClause, propertyPatternClause0, designation0); - // 2017-11-20 LDM decision is to disallow a deconstruction pattern that contains just a - // single subpattern but for which the type is omitted. - // This keeps the design space open for using parentheses for grouping patterns in the future, e.g. if we introduce `or` and - // `and` patterns. We may add other ways to disambiguate later (e.g. a property subpattern or a trailing comma inside the parens). - return (type == null && subPatterns.Count == 1) ? this.AddError(result, ErrorCode.ERR_SingleElementPositionalPatternRequiresType) : result; + bool singleElementPattern = + type == null && + subPatterns.Count == 1 && + propertyPatternClause0 == null && + designation0 == null && + subPatterns[0].NameColon == null; + // A single-element parenthesized pattern requires some other syntax to disambiguate it from a merely parenthesized pattern, + // thus leaving open the possibility that we can use parentheses for grouping patterns in the future, e.g. if we introduce `or` and + // `and` patterns. + return singleElementPattern ? this.AddError(result, ErrorCode.ERR_SingleElementPositionalPatternRequiresDisambiguation) : result; } if (parsePropertyPatternClause(out PropertyPatternClauseSyntax propertyPatternClause)) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index c75d9e20b50f8..ba0c183062026 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -212,9 +212,9 @@ The left-hand side of a ref assignment must be a ref local or parameter. - - A single-element deconstruct pattern requires a type before the open parenthesis. - A single-element deconstruct pattern requires a type before the open parenthesis. + + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index a60369fc97581..5c7c01ddc8c31 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -212,9 +212,9 @@ The left-hand side of a ref assignment must be a ref local or parameter. - - A single-element deconstruct pattern requires a type before the open parenthesis. - A single-element deconstruct pattern requires a type before the open parenthesis. + + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index f46fb27e48228..7ee4b1f634d8b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -212,9 +212,9 @@ The left-hand side of a ref assignment must be a ref local or parameter. - - A single-element deconstruct pattern requires a type before the open parenthesis. - A single-element deconstruct pattern requires a type before the open parenthesis. + + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 228bbd933f734..1c74c89bdf188 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -212,9 +212,9 @@ The left-hand side of a ref assignment must be a ref local or parameter. - - A single-element deconstruct pattern requires a type before the open parenthesis. - A single-element deconstruct pattern requires a type before the open parenthesis. + + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 900067115ff83..7bd363cc16e70 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -212,9 +212,9 @@ The left-hand side of a ref assignment must be a ref local or parameter. - - A single-element deconstruct pattern requires a type before the open parenthesis. - A single-element deconstruct pattern requires a type before the open parenthesis. + + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index f90ba8fbb6819..1d37d311d4899 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -212,9 +212,9 @@ The left-hand side of a ref assignment must be a ref local or parameter. - - A single-element deconstruct pattern requires a type before the open parenthesis. - A single-element deconstruct pattern requires a type before the open parenthesis. + + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index d715c124b9ab5..b730a129e76ab 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -212,9 +212,9 @@ The left-hand side of a ref assignment must be a ref local or parameter. - - A single-element deconstruct pattern requires a type before the open parenthesis. - A single-element deconstruct pattern requires a type before the open parenthesis. + + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index e72b17dfdea1f..18b67c8e567ff 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -212,9 +212,9 @@ The left-hand side of a ref assignment must be a ref local or parameter. - - A single-element deconstruct pattern requires a type before the open parenthesis. - A single-element deconstruct pattern requires a type before the open parenthesis. + + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 2e777ea67a775..9055aeccdc240 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -212,9 +212,9 @@ The left-hand side of a ref assignment must be a ref local or parameter. - - A single-element deconstruct pattern requires a type before the open parenthesis. - A single-element deconstruct pattern requires a type before the open parenthesis. + + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index e955fc0e1ed49..5a24297706077 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -212,9 +212,9 @@ The left-hand side of a ref assignment must be a ref local or parameter. - - A single-element deconstruct pattern requires a type before the open parenthesis. - A single-element deconstruct pattern requires a type before the open parenthesis. + + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 9c0b103450425..4e057aa520bd9 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -212,9 +212,9 @@ The left-hand side of a ref assignment must be a ref local or parameter. - - A single-element deconstruct pattern requires a type before the open parenthesis. - A single-element deconstruct pattern requires a type before the open parenthesis. + + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 3d4e6d106eac2..4c013a70f694a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -212,9 +212,9 @@ The left-hand side of a ref assignment must be a ref local or parameter. - - A single-element deconstruct pattern requires a type before the open parenthesis. - A single-element deconstruct pattern requires a type before the open parenthesis. + + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index bdd38eeae4ec6..4b45ae3943e46 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -212,9 +212,9 @@ The left-hand side of a ref assignment must be a ref local or parameter. - - A single-element deconstruct pattern requires a type before the open parenthesis. - A single-element deconstruct pattern requires a type before the open parenthesis. + + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. + A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/BetterCandidates.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/BetterCandidates.cs index 542fd78adcb6f..53f36e91d64dc 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/BetterCandidates.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/BetterCandidates.cs @@ -299,9 +299,6 @@ class MyDeconstructable // (6,26): error CS0121: The call is ambiguous between the following methods or properties: 'MyDeconstructable.Deconstruct(out int, out int)' and 'MyDeconstructable.Deconstruct(out long, out long)' // (var a, var b) = o; Diagnostic(ErrorCode.ERR_AmbigCall, "o").WithArguments("MyDeconstructable.Deconstruct(out int, out int)", "MyDeconstructable.Deconstruct(out long, out long)").WithLocation(6, 26), - // (6,26): error CS8129: No suitable Deconstruct instance or extension method was found for type 'MyDeconstructable', with 2 out parameters and a void return type. - // (var a, var b) = o; - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "o").WithArguments("MyDeconstructable", "2").WithLocation(6, 26), // (6,14): error CS8130: Cannot infer the type of implicitly-typed deconstruction variable 'a'. // (var a, var b) = o; Diagnostic(ErrorCode.ERR_TypeInferenceFailedForImplicitlyTypedDeconstructionVariable, "a").WithArguments("a").WithLocation(6, 14), diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs index 3e334f898aac5..3b6683729de91 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs @@ -1368,12 +1368,9 @@ static void Main() null "; var expectedDiagnostics = new DiagnosticDescription[] { - // CS0121: The call is ambiguous between the following methods or properties: 'Base.Deconstruct(out int, out int)' and 'Base.Deconstruct(out long, out long)' + // file.cs(12,28): error CS0121: The call is ambiguous between the following methods or properties: 'Base.Deconstruct(out int, out int)' and 'Base.Deconstruct(out long, out long)' // /**/(x, y) = new C()/**/; - Diagnostic(ErrorCode.ERR_AmbigCall, "new C()").WithArguments("Base.Deconstruct(out int, out int)", "Base.Deconstruct(out long, out long)").WithLocation(12, 28), - // CS8129: No suitable Deconstruct instance or extension method was found for type 'C', with 2 out parameters and a void return type. - // /**/(x, y) = new C()/**/; - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(12, 28) + Diagnostic(ErrorCode.ERR_AmbigCall, "new C()").WithArguments("Base.Deconstruct(out int, out int)", "Base.Deconstruct(out long, out long)").WithLocation(12, 28) }; VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs index 744ccd3f028dd..1b612fa567e2d 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs @@ -975,9 +975,9 @@ static bool Dummy(int x) True"); CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular7_2).VerifyDiagnostics( - // (9,30): error CS8320: Feature 'declaration of expression variables in member initializers and queries' is not available in C# 7.2. Please use language version 7.3 or greater. + // (9,34): error CS8320: Feature 'declaration of expression variables in member initializers and queries' is not available in C# 7.2. Please use language version 7.3 or greater. // static bool Test1 = 1 is int x1 && Dummy(x1); - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_2, "int x1").WithArguments("declaration of expression variables in member initializers and queries", "7.3").WithLocation(9, 30) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_2, "x1").WithArguments("declaration of expression variables in member initializers and queries", "7.3").WithLocation(9, 34) ); } @@ -1068,9 +1068,9 @@ static bool Dummy(int x) True"); CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular7_2).VerifyDiagnostics( - // (9,37): error CS8320: Feature 'declaration of expression variables in member initializers and queries' is not available in C# 7.2. Please use language version 7.3 or greater. + // (9,41): error CS8320: Feature 'declaration of expression variables in member initializers and queries' is not available in C# 7.2. Please use language version 7.3 or greater. // static bool Test1 {get;} = 1 is int x1 && Dummy(x1); - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_2, "int x1").WithArguments("declaration of expression variables in member initializers and queries", "7.3").WithLocation(9, 37) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_2, "x1").WithArguments("declaration of expression variables in member initializers and queries", "7.3").WithLocation(9, 41) ); } @@ -1159,12 +1159,12 @@ public C(object b) Assert.Equal("System.Int32", ((LocalSymbol)compilation.GetSemanticModel(tree).GetDeclaredSymbol(x1Decl[0])).Type.ToTestDisplayString()); CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular7_2).VerifyDiagnostics( - // (12,36): error CS8320: Feature 'declaration of expression variables in member initializers and queries' is not available in C# 7.2. Please use language version 7.3 or greater. + // (12,40): error CS8320: Feature 'declaration of expression variables in member initializers and queries' is not available in C# 7.2. Please use language version 7.3 or greater. // public D(object o) : base(2 is var x1 && Dummy(x1)) - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_2, "var x1").WithArguments("declaration of expression variables in member initializers and queries", "7.3").WithLocation(12, 36), - // (17,28): error CS8320: Feature 'declaration of expression variables in member initializers and queries' is not available in C# 7.2. Please use language version 7.3 or greater. + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_2, "x1").WithArguments("declaration of expression variables in member initializers and queries", "7.3").WithLocation(12, 40), + // (17,32): error CS8320: Feature 'declaration of expression variables in member initializers and queries' is not available in C# 7.2. Please use language version 7.3 or greater. // public D() : this(1 is int x1 && Dummy(x1)) - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_2, "int x1").WithArguments("declaration of expression variables in member initializers and queries", "7.3").WithLocation(17, 28) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_2, "x1").WithArguments("declaration of expression variables in member initializers and queries", "7.3").WithLocation(17, 32) ); } @@ -6448,9 +6448,9 @@ static bool Dummy(int x) True"); CreateCompilationWithMscorlib45(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular7_2).VerifyDiagnostics( - // (9,61): error CS8320: Feature 'declaration of expression variables in member initializers and queries' is not available in C# 7.2. Please use language version 7.3 or greater. + // (9,65): error CS8320: Feature 'declaration of expression variables in member initializers and queries' is not available in C# 7.2. Please use language version 7.3 or greater. // static event System.Func Test1 = GetDelegate(1 is int x1 && Dummy(x1)); - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_2, "int x1").WithArguments("declaration of expression variables in member initializers and queries", "7.3").WithLocation(9, 61) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_2, "x1").WithArguments("declaration of expression variables in member initializers and queries", "7.3").WithLocation(9, 65) ); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs index af2c544700b2b..af52aee5285a8 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests2.cs @@ -698,9 +698,9 @@ public static void Main() if (t is (int x)) { } // error 1 switch (t) { case (_): break; } // error 2 var u = t switch { (int y) => y, _ => 2 }; // error 3 - if (t is (int z1) _) { } // error 4 - if (t is (Item1: int z2)) { } // error 5 - if (t is (int z3) { }) { } // error 6 + if (t is (int z1) _) { } // ok + if (t is (Item1: int z2)) { } // ok + if (t is (int z3) { }) { } // ok if (t is ValueTuple(int z4)) { } // ok } private static bool Check(T expected, T actual) @@ -711,25 +711,16 @@ private static bool Check(T expected, T actual) }"; var compilation = CreatePatternCompilation(source); compilation.VerifyDiagnostics( - // (8,18): error CS8407: A single-element deconstruct pattern requires a type before the open parenthesis. + // (8,18): error CS8507: A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. // if (t is (int x)) { } // error 1 - Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresType, "(int x)").WithLocation(8, 18), - // (9,27): error CS8407: A single-element deconstruct pattern requires a type before the open parenthesis. + Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresDisambiguation, "(int x)").WithLocation(8, 18), + // (9,27): error CS8507: A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. // switch (t) { case (_): break; } // error 2 - Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresType, "(_)").WithLocation(9, 27), - // (10,28): error CS8407: A single-element deconstruct pattern requires a type before the open parenthesis. + Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresDisambiguation, "(_)").WithLocation(9, 27), + // (10,28): error CS8507: A single-element deconstruct pattern requires some other syntax for disambiguation. It is recommended to add a discard designator '_' after the close paren ')'. // var u = t switch { (int y) => y, _ => 2 }; // error 3 - Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresType, "(int y)").WithLocation(10, 28), - // (11,18): error CS8407: A single-element deconstruct pattern requires a type before the open parenthesis. - // if (t is (int z1) _) { } // error 4 - Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresType, "(int z1) _").WithLocation(11, 18), - // (12,18): error CS8407: A single-element deconstruct pattern requires a type before the open parenthesis. - // if (t is (Item1: int z2)) { } // error 5 - Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresType, "(Item1: int z2)").WithLocation(12, 18), - // (13,18): error CS8407: A single-element deconstruct pattern requires a type before the open parenthesis. - // if (t is (int z3) { }) { } // error 6 - Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresType, "(int z3) { }").WithLocation(13, 18), - // (10,42): error CS8410: The pattern has already been handled by a previous arm of the switch expression. + Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresDisambiguation, "(int y)").WithLocation(10, 28), + // (10,42): error CS8510: The pattern has already been handled by a previous arm of the switch expression. // var u = t switch { (int y) => y, _ => 2 }; // error 3 Diagnostic(ErrorCode.ERR_SwitchArmSubsumed, "_").WithLocation(10, 42) ); @@ -796,21 +787,21 @@ class var { } // (10,32): error CS8508: The syntax 'var' for a pattern is not permitted to refer to a type, but 'N.var' is in scope here. // { Check(true, t is var (x, y) && x == 1 && y == 2); } // error 1 Diagnostic(ErrorCode.ERR_VarMayNotBindToType, "var").WithArguments("N.var").WithLocation(10, 32), - // (10,32): error CS1061: 'var' does not contain a definition for 'Deconstruct' and no accessible extension method 'Deconstruct' accepting a first argument of type 'var' could be found (are you missing a using directive or an assembly reference?) + // (10,36): error CS1061: 'var' does not contain a definition for 'Deconstruct' and no accessible extension method 'Deconstruct' accepting a first argument of type 'var' could be found (are you missing a using directive or an assembly reference?) // { Check(true, t is var (x, y) && x == 1 && y == 2); } // error 1 - Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "var (x, y)").WithArguments("N.var", "Deconstruct").WithLocation(10, 32), - // (10,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'var', with 2 out parameters and a void return type. + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "(x, y)").WithArguments("N.var", "Deconstruct").WithLocation(10, 36), + // (10,36): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'var', with 2 out parameters and a void return type. // { Check(true, t is var (x, y) && x == 1 && y == 2); } // error 1 - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "var (x, y)").WithArguments("N.var", "2").WithLocation(10, 32), + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(x, y)").WithArguments("N.var", "2").WithLocation(10, 36), // (11,33): error CS8508: The syntax 'var' for a pattern is not permitted to refer to a type, but 'N.var' is in scope here. // { Check(false, t is var (x, y) && x == 1 && y == 3); } // error 2 Diagnostic(ErrorCode.ERR_VarMayNotBindToType, "var").WithArguments("N.var").WithLocation(11, 33), - // (11,33): error CS1061: 'var' does not contain a definition for 'Deconstruct' and no accessible extension method 'Deconstruct' accepting a first argument of type 'var' could be found (are you missing a using directive or an assembly reference?) + // (11,37): error CS1061: 'var' does not contain a definition for 'Deconstruct' and no accessible extension method 'Deconstruct' accepting a first argument of type 'var' could be found (are you missing a using directive or an assembly reference?) // { Check(false, t is var (x, y) && x == 1 && y == 3); } // error 2 - Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "var (x, y)").WithArguments("N.var", "Deconstruct").WithLocation(11, 33), - // (11,33): error CS8129: No suitable Deconstruct instance or extension method was found for type 'var', with 2 out parameters and a void return type. + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "(x, y)").WithArguments("N.var", "Deconstruct").WithLocation(11, 37), + // (11,37): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'var', with 2 out parameters and a void return type. // { Check(false, t is var (x, y) && x == 1 && y == 3); } // error 2 - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "var (x, y)").WithArguments("N.var", "2").WithLocation(11, 33), + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(x, y)").WithArguments("N.var", "2").WithLocation(11, 37), // (12,32): error CS8508: The syntax 'var' for a pattern is not permitted to refer to a type, but 'N.var' is in scope here. // { Check(true, t is var x); } // error 3 Diagnostic(ErrorCode.ERR_VarMayNotBindToType, "var").WithArguments("N.var").WithLocation(12, 32) @@ -2112,13 +2103,10 @@ public void M() { "; var compilation = CreateCompilation(source, options: TestOptions.ReleaseDll); compilation.VerifyDiagnostics( - // (4,21): error CS8407: A single-element deconstruct pattern requires a type before the open parenthesis. - // _ = this is (a: 1); - Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresType, "(a: 1)").WithLocation(4, 21), - // (4,21): error CS1061: 'C' does not contain a definition for 'Deconstruct' and no extension method 'Deconstruct' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?) + // (4,21): error CS1061: 'C' does not contain a definition for 'Deconstruct' and no accessible extension method 'Deconstruct' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?) // _ = this is (a: 1); Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "(a: 1)").WithArguments("C", "Deconstruct").WithLocation(4, 21), - // (4,21): error CS8129: No suitable Deconstruct instance or extension method was found for type 'C', with 1 out parameters and a void return type. + // (4,21): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'C', with 1 out parameters and a void return type. // _ = this is (a: 1); Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(a: 1)").WithArguments("C", "1").WithLocation(4, 21) ); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs index f272cba6c199a..81333ec77a758 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests4.cs @@ -226,7 +226,7 @@ public static void Main() [Fact] public void ITuple_02() { - // - should not match when input type extends ITuple and has Deconstruct (struct) + // - should match when input type extends ITuple and has inapplicable Deconstruct (struct) var source = @"using System; using System.Runtime.CompilerServices; @@ -246,32 +246,13 @@ public void Deconstruct() {} } "; var compilation = CreatePatternCompilation(source); - compilation.VerifyDiagnostics( - // (10,32): error CS1501: No overload for method 'Deconstruct' takes 2 arguments - // Console.WriteLine(t is (3, 4)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4)").WithArguments("Deconstruct", "2").WithLocation(10, 32), - // (10,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'C', with 2 out parameters and a void return type. - // Console.WriteLine(t is (3, 4)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4)").WithArguments("C", "2").WithLocation(10, 32), - // (11,32): error CS1501: No overload for method 'Deconstruct' takes 3 arguments - // Console.WriteLine(t is (3, 4, 5)); // TRUE - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4, 5)").WithArguments("Deconstruct", "3").WithLocation(11, 32), - // (11,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'C', with 3 out parameters and a void return type. - // Console.WriteLine(t is (3, 4, 5)); // TRUE - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4, 5)").WithArguments("C", "3").WithLocation(11, 32), - // (12,32): error CS1501: No overload for method 'Deconstruct' takes 3 arguments - // Console.WriteLine(t is (3, 0, 5)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 0, 5)").WithArguments("Deconstruct", "3").WithLocation(12, 32), - // (12,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'C', with 3 out parameters and a void return type. - // Console.WriteLine(t is (3, 0, 5)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 0, 5)").WithArguments("C", "3").WithLocation(12, 32), - // (13,32): error CS1501: No overload for method 'Deconstruct' takes 4 arguments - // Console.WriteLine(t is (3, 4, 5, 6)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4, 5, 6)").WithArguments("Deconstruct", "4").WithLocation(13, 32), - // (13,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'C', with 4 out parameters and a void return type. - // Console.WriteLine(t is (3, 4, 5, 6)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4, 5, 6)").WithArguments("C", "4").WithLocation(13, 32) - ); + compilation.VerifyDiagnostics(); + var expectedOutput = +@"False +True +False +False"; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); } [Fact] @@ -308,7 +289,7 @@ public static void Main() [Fact] public void ITuple_04() { - // - should not match when input type extends ITuple and has Deconstruct (class) + // - should match when input type extends ITuple and has inapplicable Deconstruct (class) var source = @"using System; using System.Runtime.CompilerServices; @@ -328,32 +309,13 @@ public void Deconstruct() {} } "; var compilation = CreatePatternCompilation(source); - compilation.VerifyDiagnostics( - // (10,32): error CS1501: No overload for method 'Deconstruct' takes 2 arguments - // Console.WriteLine(t is (3, 4)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4)").WithArguments("Deconstruct", "2").WithLocation(10, 32), - // (10,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'C', with 2 out parameters and a void return type. - // Console.WriteLine(t is (3, 4)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4)").WithArguments("C", "2").WithLocation(10, 32), - // (11,32): error CS1501: No overload for method 'Deconstruct' takes 3 arguments - // Console.WriteLine(t is (3, 4, 5)); // TRUE - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4, 5)").WithArguments("Deconstruct", "3").WithLocation(11, 32), - // (11,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'C', with 3 out parameters and a void return type. - // Console.WriteLine(t is (3, 4, 5)); // TRUE - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4, 5)").WithArguments("C", "3").WithLocation(11, 32), - // (12,32): error CS1501: No overload for method 'Deconstruct' takes 3 arguments - // Console.WriteLine(t is (3, 0, 5)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 0, 5)").WithArguments("Deconstruct", "3").WithLocation(12, 32), - // (12,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'C', with 3 out parameters and a void return type. - // Console.WriteLine(t is (3, 0, 5)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 0, 5)").WithArguments("C", "3").WithLocation(12, 32), - // (13,32): error CS1501: No overload for method 'Deconstruct' takes 4 arguments - // Console.WriteLine(t is (3, 4, 5, 6)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4, 5, 6)").WithArguments("Deconstruct", "4").WithLocation(13, 32), - // (13,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'C', with 4 out parameters and a void return type. - // Console.WriteLine(t is (3, 4, 5, 6)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4, 5, 6)").WithArguments("C", "4").WithLocation(13, 32) - ); + compilation.VerifyDiagnostics(); + var expectedOutput = +@"False +True +False +False"; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); } [Fact] @@ -480,7 +442,7 @@ public static void M(T t) [Fact] public void ITuple_06() { - // - should not match when input type extends ITuple and has Deconstruct (type parameter) + // - should match when input type extends ITuple and has inapplicable Deconstruct (type parameter) var source = @"using System; using System.Runtime.CompilerServices; @@ -503,38 +465,18 @@ public void Deconstruct() {} } "; var compilation = CreatePatternCompilation(source); - compilation.VerifyDiagnostics( - // (13,32): error CS1501: No overload for method 'Deconstruct' takes 2 arguments - // Console.WriteLine(t is (3, 4)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4)").WithArguments("Deconstruct", "2").WithLocation(13, 32), - // (13,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 2 out parameters and a void return type. - // Console.WriteLine(t is (3, 4)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4)").WithArguments("T", "2").WithLocation(13, 32), - // (14,32): error CS1501: No overload for method 'Deconstruct' takes 3 arguments - // Console.WriteLine(t is (3, 4, 5)); // TRUE - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4, 5)").WithArguments("Deconstruct", "3").WithLocation(14, 32), - // (14,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 3 out parameters and a void return type. - // Console.WriteLine(t is (3, 4, 5)); // TRUE - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4, 5)").WithArguments("T", "3").WithLocation(14, 32), - // (15,32): error CS1501: No overload for method 'Deconstruct' takes 3 arguments - // Console.WriteLine(t is (3, 0, 5)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 0, 5)").WithArguments("Deconstruct", "3").WithLocation(15, 32), - // (15,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 3 out parameters and a void return type. - // Console.WriteLine(t is (3, 0, 5)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 0, 5)").WithArguments("T", "3").WithLocation(15, 32), - // (16,32): error CS1501: No overload for method 'Deconstruct' takes 4 arguments - // Console.WriteLine(t is (3, 4, 5, 6)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4, 5, 6)").WithArguments("Deconstruct", "4").WithLocation(16, 32), - // (16,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 4 out parameters and a void return type. - // Console.WriteLine(t is (3, 4, 5, 6)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4, 5, 6)").WithArguments("T", "4").WithLocation(16, 32) - ); + compilation.VerifyDiagnostics(); + var expectedOutput = +@"False +True +False +False"; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); } [Fact] public void ITuple_12() { - // - should not match when input type extends ITuple and has Deconstruct (type parameter) var source = @"using System; using System.Runtime.CompilerServices; @@ -548,47 +490,57 @@ public static void Main() } public static void M(T t) where T: C { - Console.WriteLine(t is (3, 4)); // false - Console.WriteLine(t is (3, 4, 5)); // TRUE + Console.WriteLine(t is (3, 4)); // false via ITuple + Console.WriteLine(t is (3, 4, 5)); // true via ITuple Console.WriteLine(t is (3, 0, 5)); // false Console.WriteLine(t is (3, 4, 5, 6)); // false } public int Deconstruct() => 0; } +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics(); + var expectedOutput = +@"False +True +False +False"; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); + } + + [Fact] + public void ITuple_12b() + { + var source = +@"using System; +using System.Runtime.CompilerServices; +public class C : ITuple +{ + int ITuple.Length => 3; + object ITuple.this[int i] => i + 3; + public static void Main() + { + M(new C()); + } + public static void M(T t) where T: C + { + Console.WriteLine(t is ()); + } + public int Deconstruct() => 0; // this is applicable, so prevents ITuple, but it has the wrong return type +} "; var compilation = CreatePatternCompilation(source); compilation.VerifyDiagnostics( - // (13,32): error CS1501: No overload for method 'Deconstruct' takes 2 arguments - // Console.WriteLine(t is (3, 4)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4)").WithArguments("Deconstruct", "2").WithLocation(13, 32), - // (13,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 2 out parameters and a void return type. - // Console.WriteLine(t is (3, 4)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4)").WithArguments("T", "2").WithLocation(13, 32), - // (14,32): error CS1501: No overload for method 'Deconstruct' takes 3 arguments - // Console.WriteLine(t is (3, 4, 5)); // TRUE - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4, 5)").WithArguments("Deconstruct", "3").WithLocation(14, 32), - // (14,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 3 out parameters and a void return type. - // Console.WriteLine(t is (3, 4, 5)); // TRUE - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4, 5)").WithArguments("T", "3").WithLocation(14, 32), - // (15,32): error CS1501: No overload for method 'Deconstruct' takes 3 arguments - // Console.WriteLine(t is (3, 0, 5)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 0, 5)").WithArguments("Deconstruct", "3").WithLocation(15, 32), - // (15,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 3 out parameters and a void return type. - // Console.WriteLine(t is (3, 0, 5)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 0, 5)").WithArguments("T", "3").WithLocation(15, 32), - // (16,32): error CS1501: No overload for method 'Deconstruct' takes 4 arguments - // Console.WriteLine(t is (3, 4, 5, 6)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4, 5, 6)").WithArguments("Deconstruct", "4").WithLocation(16, 32), - // (16,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 4 out parameters and a void return type. - // Console.WriteLine(t is (3, 4, 5, 6)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4, 5, 6)").WithArguments("T", "4").WithLocation(16, 32) + // (13,32): error CS8129: No suitable 'Deconstruct' instance or extension method was found for type 'T', with 0 out parameters and a void return type. + // Console.WriteLine(t is ()); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "()").WithArguments("T", "0").WithLocation(13, 32) ); } [Fact] public void ITuple_07() { - // - should not match when input type extends ITuple and has Deconstruct (inherited) + // - should match when input type extends ITuple and has inapplicable Deconstruct (inherited) var source = @"using System; using System.Runtime.CompilerServices; @@ -614,38 +566,19 @@ public static void M(T t) where T: C } "; var compilation = CreatePatternCompilation(source); - compilation.VerifyDiagnostics( - // (17,32): error CS1501: No overload for method 'Deconstruct' takes 2 arguments - // Console.WriteLine(t is (3, 4)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4)").WithArguments("Deconstruct", "2").WithLocation(17, 32), - // (17,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 2 out parameters and a void return type. - // Console.WriteLine(t is (3, 4)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4)").WithArguments("T", "2").WithLocation(17, 32), - // (18,32): error CS1501: No overload for method 'Deconstruct' takes 3 arguments - // Console.WriteLine(t is (3, 4, 5)); // TRUE - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4, 5)").WithArguments("Deconstruct", "3").WithLocation(18, 32), - // (18,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 3 out parameters and a void return type. - // Console.WriteLine(t is (3, 4, 5)); // TRUE - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4, 5)").WithArguments("T", "3").WithLocation(18, 32), - // (19,32): error CS1501: No overload for method 'Deconstruct' takes 3 arguments - // Console.WriteLine(t is (3, 0, 5)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 0, 5)").WithArguments("Deconstruct", "3").WithLocation(19, 32), - // (19,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 3 out parameters and a void return type. - // Console.WriteLine(t is (3, 0, 5)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 0, 5)").WithArguments("T", "3").WithLocation(19, 32), - // (20,32): error CS1501: No overload for method 'Deconstruct' takes 4 arguments - // Console.WriteLine(t is (3, 4, 5, 6)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4, 5, 6)").WithArguments("Deconstruct", "4").WithLocation(20, 32), - // (20,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 4 out parameters and a void return type. - // Console.WriteLine(t is (3, 4, 5, 6)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4, 5, 6)").WithArguments("T", "4").WithLocation(20, 32) - ); + compilation.VerifyDiagnostics(); + var expectedOutput = +@"False +True +False +False"; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); } [Fact] public void ITuple_08() { - // - should not match when input type extends ITuple and has Deconstruct (static) + // - should match when input type extends ITuple and has an inapplicable Deconstruct (static) var source = @"using System; using System.Runtime.CompilerServices; @@ -671,38 +604,19 @@ public static void M(T t) where T: C } "; var compilation = CreatePatternCompilation(source); - compilation.VerifyDiagnostics( - // (17,32): error CS1501: No overload for method 'Deconstruct' takes 2 arguments - // Console.WriteLine(t is (3, 4)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4)").WithArguments("Deconstruct", "2").WithLocation(17, 32), - // (17,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 2 out parameters and a void return type. - // Console.WriteLine(t is (3, 4)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4)").WithArguments("T", "2").WithLocation(17, 32), - // (18,32): error CS1501: No overload for method 'Deconstruct' takes 3 arguments - // Console.WriteLine(t is (3, 4, 5)); // TRUE - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4, 5)").WithArguments("Deconstruct", "3").WithLocation(18, 32), - // (18,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 3 out parameters and a void return type. - // Console.WriteLine(t is (3, 4, 5)); // TRUE - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4, 5)").WithArguments("T", "3").WithLocation(18, 32), - // (19,32): error CS1501: No overload for method 'Deconstruct' takes 3 arguments - // Console.WriteLine(t is (3, 0, 5)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 0, 5)").WithArguments("Deconstruct", "3").WithLocation(19, 32), - // (19,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 3 out parameters and a void return type. - // Console.WriteLine(t is (3, 0, 5)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 0, 5)").WithArguments("T", "3").WithLocation(19, 32), - // (20,32): error CS1501: No overload for method 'Deconstruct' takes 4 arguments - // Console.WriteLine(t is (3, 4, 5, 6)); // false - Diagnostic(ErrorCode.ERR_BadArgCount, "(3, 4, 5, 6)").WithArguments("Deconstruct", "4").WithLocation(20, 32), - // (20,32): error CS8129: No suitable Deconstruct instance or extension method was found for type 'T', with 4 out parameters and a void return type. - // Console.WriteLine(t is (3, 4, 5, 6)); // false - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "(3, 4, 5, 6)").WithArguments("T", "4").WithLocation(20, 32) - ); + compilation.VerifyDiagnostics(); + var expectedOutput = +@"False +True +False +False"; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); } [Fact] public void ITuple_09() { - // - should match when input type extends ITuple and has only an extension Deconstruct + // - should match when input type extends ITuple and has an extension Deconstruct var source = @"using System; using System.Runtime.CompilerServices; @@ -713,27 +627,63 @@ public class C : ITuple public static void Main() { var t = new C(); - Console.WriteLine(t is (3, 4)); // false - Console.WriteLine(t is (3, 4, 5)); // TRUE + Console.WriteLine(t is (7, 8)); // true (Extensions.Deconstruct) + Console.WriteLine(t is (3, 4, 5)); // true via ITuple Console.WriteLine(t is (3, 0, 5)); // false Console.WriteLine(t is (3, 4, 5, 6)); // false } } static class Extensions { - public static void Deconstruct(this C c, out int X, out int Y) => (X, Y) = (3, 4); + public static void Deconstruct(this C c, out int X, out int Y) => (X, Y) = (7, 8); } "; var compilation = CreatePatternCompilation(source); compilation.VerifyDiagnostics(); var expectedOutput = -@"False +@"True True False False"; var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); } + [Fact] + public void ITuple_09b() + { + // - An extension Deconstruct hides ITuple + var source = +@"using System; +using System.Runtime.CompilerServices; +public class C : ITuple +{ + int ITuple.Length => 4; + object ITuple.this[int i] => i + 3; + public static void Main() + { + var t = new C(); + Console.WriteLine(t is (7, 8)); // true (Extensions.Deconstruct) + Console.WriteLine(t is (3, 4, 5)); // false (ITuple hidden by extension method) + Console.WriteLine(t is (1, 2, 3)); // true via extension Deconstruct + Console.WriteLine(t is (3, 4, 5, 6)); // true (via ITuple) + } +} +static class Extensions +{ + public static void Deconstruct(this C c, out int X, out int Y) => (X, Y) = (7, 8); + public static void Deconstruct(this ITuple c, out int X, out int Y, out int Z) => (X, Y, Z) = (1, 2, 3); +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics(); + var expectedOutput = +@"True +False +True +True"; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); + } + [Fact] public void ITupleLacksLength() { @@ -924,112 +874,404 @@ class C1 } [Fact] - public void DiscardVsConstantInCase_01() + [WorkItem(30906, "https://github.com/dotnet/roslyn/issues/30906")] + public void NullableTupleWithTuplePattern_01() { var source = @"using System; -class Program +class C { + static (int, int)? Get(int i) + { + switch (i) + { + case 1: + return (1, 2); + case 2: + return (3, 4); + default: + return null; + } + } + static void Main() { - const int _ = 3; for (int i = 0; i < 6; i++) { - switch (i) - { - case _: - Console.Write(i); - break; - } + if (Get(i) is var (x, y)) + Console.Write($""{i} {x} {y}; ""); } } }"; var compilation = CreatePatternCompilation(source); - compilation.VerifyDiagnostics( - // (11,22): warning CS8512: The name '_' refers to the constant, not the discard pattern. Use 'var _' to discard the value, or '@_' to refer to a constant by that name. - // case _: - Diagnostic(ErrorCode.WRN_CaseConstantNamedUnderscore, "_").WithLocation(11, 22) - ); - CompileAndVerify(compilation, expectedOutput: "3"); + compilation.VerifyDiagnostics(); + var expectedOutput = @"1 1 2; 2 3 4; "; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); } [Fact] - public void DiscardVsConstantInCase_02() + [WorkItem(30906, "https://github.com/dotnet/roslyn/issues/30906")] + public void NullableTupleWithTuplePattern_01b() { var source = @"using System; -class Program +class C { + static ((int, int)?, int) Get(int i) + { + switch (i) + { + case 1: + return ((1, 2), 1); + case 2: + return ((3, 4), 1); + default: + return (null, 1); + } + } + static void Main() { - const int _ = 3; for (int i = 0; i < 6; i++) { - switch (i) - { - case _ when true: - Console.Write(i); - break; - } + if (Get(i) is var ((x, y), z)) + Console.Write($""{i} {x} {y}; ""); } } }"; var compilation = CreatePatternCompilation(source); - compilation.VerifyDiagnostics( - // (11,22): warning CS8512: The name '_' refers to the constant, not the discard pattern. Use 'var _' to discard the value, or '@_' to refer to a constant by that name. - // case _ when true: - Diagnostic(ErrorCode.WRN_CaseConstantNamedUnderscore, "_").WithLocation(11, 22) - ); - CompileAndVerify(compilation, expectedOutput: "3"); + compilation.VerifyDiagnostics(); + var expectedOutput = @"1 1 2; 2 3 4; "; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); } [Fact] - public void DiscardVsConstantInCase_03() + [WorkItem(30906, "https://github.com/dotnet/roslyn/issues/30906")] + public void NullableTupleWithTuplePattern_02() { var source = @"using System; -class Program +class C { + static object Get(int i) + { + switch (i) + { + case 0: + return ('a', 'b'); + case 1: + return (1, 2); + case 2: + return (3, 4); + case 3: + return new object(); + default: + return null; + } + } + static void Main() { - const int _ = 3; for (int i = 0; i < 6; i++) { - switch (i) + if (Get(i) is var (x, y)) + Console.Write($""{i} {x} {y}; ""); + } + } +} + +// Provide a ValueTuple that implements ITuple +namespace System +{ + using ITuple = System.Runtime.CompilerServices.ITuple; + public struct ValueTuple: ITuple + { + public T1 Item1; + public T2 Item2; + public ValueTuple(T1 item1, T2 item2) => (Item1, Item2) = (item1, item2); + int ITuple.Length => 2; + object ITuple.this[int index] + { + get { - case var _: - Console.Write(i); - break; + switch (index) + { + case 0: return Item1; + case 1: return Item2; + default: throw new System.ArgumentException(""index""); + } } } } -}"; +} +"; var compilation = CreatePatternCompilation(source); - compilation.VerifyDiagnostics( - // (6,19): warning CS0219: The variable '_' is assigned but its value is never used - // const int _ = 3; - Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "_").WithArguments("_").WithLocation(6, 19) - ); - CompileAndVerify(compilation, expectedOutput: "012345"); + compilation.VerifyDiagnostics(); + var expectedOutput = +@"0 a b; 1 1 2; 2 3 4; "; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); } [Fact] - public void DiscardVsConstantInCase_04() + [WorkItem(30906, "https://github.com/dotnet/roslyn/issues/30906")] + public void NullableTupleWithTuplePattern_02b() { var source = @"using System; -class Program +class C { - static void Main() + static object Get(int i) { - const int _ = 3; - for (int i = 0; i < 6; i++) + switch (i) { - switch (i) - { - case var _ when true: - Console.Write(i); - break; - } + case 0: + return (('a', 'b'), 1); + case 1: + return ((1, 2), 1); + case 2: + return ((3, 4), 1); + case 3: + return new object(); + default: + return null; } } -}"; + + static void Main() + { + for (int i = 0; i < 6; i++) + { + if (Get(i) is var ((x, y), z)) + Console.Write($""{i} {x} {y}; ""); + } + } +} + +// Provide a ValueTuple that implements ITuple +namespace System +{ + using ITuple = System.Runtime.CompilerServices.ITuple; + public struct ValueTuple: ITuple + { + public T1 Item1; + public T2 Item2; + public ValueTuple(T1 item1, T2 item2) => (Item1, Item2) = (item1, item2); + int ITuple.Length => 2; + object ITuple.this[int index] + { + get + { + switch (index) + { + case 0: return Item1; + case 1: return Item2; + default: throw new System.ArgumentException(""index""); + } + } + } + } +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics(); + var expectedOutput = @"0 a b; 1 1 2; 2 3 4; "; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); + } + + [Fact] + [WorkItem(30906, "https://github.com/dotnet/roslyn/issues/30906")] + public void NullableTupleWithTuplePattern_03() + { + var source = @"using System; +class C +{ + static object Get(int i) + { + switch (i) + { + case 0: + return ('a', 'b'); + case 1: + return (1, 2); + case 2: + return (3, 4); + case 3: + return new object(); + default: + return null; + } + } + + static void Main() + { + for (int i = 0; i < 6; i++) + { + if (Get(i) is var (x, y)) + Console.WriteLine($""{i} {x} {y}""); + } + } +} + +// Provide a ValueTuple that DOES NOT implements ITuple or have a Deconstruct method +namespace System +{ + public struct ValueTuple + { + public T1 Item1; + public T2 Item2; + public ValueTuple(T1 item1, T2 item2) => (Item1, Item2) = (item1, item2); + } +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics(); + var expectedOutput = @""; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); + } + + [Fact] + [WorkItem(30906, "https://github.com/dotnet/roslyn/issues/30906")] + public void NullableTupleWithTuplePattern_04() + { + var source = @"using System; +struct C +{ + static C? Get(int i) + { + switch (i) + { + case 1: + return new C(1, 2); + case 2: + return new C(3, 4); + default: + return null; + } + } + + static void Main() + { + for (int i = 0; i < 6; i++) + { + if (Get(i) is var (x, y)) + Console.Write($""{i} {x} {y}; ""); + } + } + + public int Item1; + public int Item2; + public C(int item1, int item2) => (Item1, Item2) = (item1, item2); + public void Deconstruct(out int Item1, out int Item2) => (Item1, Item2) = (this.Item1, this.Item2); +}"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics(); + var expectedOutput = @"1 1 2; 2 3 4; "; + var compVerifier = CompileAndVerify(compilation, expectedOutput: expectedOutput); + } + + [Fact] + public void DiscardVsConstantInCase_01() + { + var source = @"using System; +class Program +{ + static void Main() + { + const int _ = 3; + for (int i = 0; i < 6; i++) + { + switch (i) + { + case _: + Console.Write(i); + break; + } + } + } +}"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (11,22): warning CS8512: The name '_' refers to the constant, not the discard pattern. Use 'var _' to discard the value, or '@_' to refer to a constant by that name. + // case _: + Diagnostic(ErrorCode.WRN_CaseConstantNamedUnderscore, "_").WithLocation(11, 22) + ); + CompileAndVerify(compilation, expectedOutput: "3"); + } + + [Fact] + public void DiscardVsConstantInCase_02() + { + var source = @"using System; +class Program +{ + static void Main() + { + const int _ = 3; + for (int i = 0; i < 6; i++) + { + switch (i) + { + case _ when true: + Console.Write(i); + break; + } + } + } +}"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (11,22): warning CS8512: The name '_' refers to the constant, not the discard pattern. Use 'var _' to discard the value, or '@_' to refer to a constant by that name. + // case _ when true: + Diagnostic(ErrorCode.WRN_CaseConstantNamedUnderscore, "_").WithLocation(11, 22) + ); + CompileAndVerify(compilation, expectedOutput: "3"); + } + + [Fact] + public void DiscardVsConstantInCase_03() + { + var source = @"using System; +class Program +{ + static void Main() + { + const int _ = 3; + for (int i = 0; i < 6; i++) + { + switch (i) + { + case var _: + Console.Write(i); + break; + } + } + } +}"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (6,19): warning CS0219: The variable '_' is assigned but its value is never used + // const int _ = 3; + Diagnostic(ErrorCode.WRN_UnreferencedVarAssg, "_").WithArguments("_").WithLocation(6, 19) + ); + CompileAndVerify(compilation, expectedOutput: "012345"); + } + + [Fact] + public void DiscardVsConstantInCase_04() + { + var source = @"using System; +class Program +{ + static void Main() + { + const int _ = 3; + for (int i = 0; i < 6; i++) + { + switch (i) + { + case var _ when true: + Console.Write(i); + break; + } + } + } +}"; var compilation = CreatePatternCompilation(source); compilation.VerifyDiagnostics( // (6,19): warning CS0219: The variable '_' is assigned but its value is never used @@ -1489,6 +1731,557 @@ static int M1(bool? b1, bool? b2) ); } + [Fact] + public void DeconstructVsITuple_01() + { + // From LDM 2018-11-05: + // 1. If the type is a tuple type (any arity >= 0; see below), then use the tuple semantics + // 2. If "binding" a Deconstruct invocation would find one or more applicable methods, use Deconstruct. + // 3. If the type satisfies the ITuple deconstruct constraints, use ITuple semantics + // Here we test the relative priority of steps 2 and 3. + // - Found one applicable Deconstruct method (even though the type implements ITuple): use it + var source = @"using System; +using System.Runtime.CompilerServices; +class Program +{ + static void Main() + { + IA a = new A(); + if (a is (var x, var y)) // tuple pattern containing var patterns + Console.Write($""{x} {y}""); + } +} +interface IA : ITuple +{ + void Deconstruct(out int X, out int Y); +} +class A: IA, ITuple +{ + void IA.Deconstruct(out int X, out int Y) => (X, Y) = (3, 4); + int ITuple.Length => throw null; + object ITuple.this[int i] => throw null; +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + ); + CompileAndVerify(compilation, expectedOutput: "3 4"); + } + + [Fact] + public void DeconstructVsITuple_01b() + { + // From LDM 2018-11-05: + // 1. If the type is a tuple type (any arity >= 0; see below), then use the tuple semantics + // 2. If "binding" a Deconstruct invocation would find one or more applicable methods, use Deconstruct. + // 3. If the type satisfies the ITuple deconstruct constraints, use ITuple semantics + // Here we test the relative priority of steps 2 and 3. + // - Found one applicable Deconstruct method (even though the type implements ITuple): use it + var source = @"using System; +using System.Runtime.CompilerServices; +class Program +{ + static void Main() + { + IA a = new A(); + if (a is var (x, y)) // var pattern containing tuple designator + Console.Write($""{x} {y}""); + } +} +interface IA : ITuple +{ + void Deconstruct(out int X, out int Y); +} +class A: IA, ITuple +{ + void IA.Deconstruct(out int X, out int Y) => (X, Y) = (3, 4); + int ITuple.Length => throw null; + object ITuple.this[int i] => throw null; +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + ); + CompileAndVerify(compilation, expectedOutput: "3 4"); + } + + [Fact] + public void DeconstructVsITuple_02() + { + // From LDM 2018-11-05: + // 1. If the type is a tuple type (any arity >= 0; see below), then use the tuple semantics + // 2. If "binding" a Deconstruct invocation would find one or more applicable methods, use Deconstruct. + // 3. If the type satisfies the ITuple deconstruct constraints, use ITuple semantics + // Here we test the relative priority of steps 2 and 3. + // - Found more than one applicable Deconstruct method (even though the type implements ITuple): error + + // var pattern with tuple designator + var source = @"using System; +using System.Runtime.CompilerServices; +class Program +{ + static void Main() + { + IA a = new A(); + if (a is var (x, y)) Console.Write($""{x} {y}""); + } +} +interface I1 +{ + void Deconstruct(out int X, out int Y); +} +interface I2 +{ + void Deconstruct(out int X, out int Y); +} +interface IA: I1, I2 {} +class A: IA, I1, I2, ITuple +{ + void I1.Deconstruct(out int X, out int Y) => (X, Y) = (3, 4); + void I2.Deconstruct(out int X, out int Y) => (X, Y) = (7, 8); + int ITuple.Length => 2; + object ITuple.this[int i] => i + 5; +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (8,22): error CS0121: The call is ambiguous between the following methods or properties: 'I1.Deconstruct(out int, out int)' and 'I2.Deconstruct(out int, out int)' + // if (a is var (x, y)) Console.Write($"{x} {y}"); + Diagnostic(ErrorCode.ERR_AmbigCall, "(x, y)").WithArguments("I1.Deconstruct(out int, out int)", "I2.Deconstruct(out int, out int)").WithLocation(8, 22) + ); + } + + [Fact] + public void DeconstructVsITuple_02b() + { + // From LDM 2018-11-05: + // 1. If the type is a tuple type (any arity >= 0; see below), then use the tuple semantics + // 2. If "binding" a Deconstruct invocation would find one or more applicable methods, use Deconstruct. + // 3. If the type satisfies the ITuple deconstruct constraints, use ITuple semantics + // Here we test the relative priority of steps 2 and 3. + // - Found more than one applicable Deconstruct method (even though the type implements ITuple): error + + // tuple pattern with var subpatterns + var source = @"using System; +using System.Runtime.CompilerServices; +class Program +{ + static void Main() + { + IA a = new A(); + if (a is (var x, var y)) Console.Write($""{x} {y}""); + } +} +interface I1 +{ + void Deconstruct(out int X, out int Y); +} +interface I2 +{ + void Deconstruct(out int X, out int Y); +} +interface IA: I1, I2 {} +class A: IA, I1, I2, ITuple +{ + void I1.Deconstruct(out int X, out int Y) => (X, Y) = (3, 4); + void I2.Deconstruct(out int X, out int Y) => (X, Y) = (7, 8); + int ITuple.Length => 2; + object ITuple.this[int i] => i + 5; +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + // (8,18): error CS0121: The call is ambiguous between the following methods or properties: 'I1.Deconstruct(out int, out int)' and 'I2.Deconstruct(out int, out int)' + // if (a is (var x, var y)) Console.Write($"{x} {y}"); + Diagnostic(ErrorCode.ERR_AmbigCall, "(var x, var y)").WithArguments("I1.Deconstruct(out int, out int)", "I2.Deconstruct(out int, out int)").WithLocation(8, 18) + ); + } + + [Fact] + public void DeconstructVsITuple_03() + { + // From LDM 2018-11-05: + // 1. If the type is a tuple type (any arity >= 0; see below), then use the tuple semantics + // 2. If "binding" a Deconstruct invocation would find one or more applicable methods, use Deconstruct. + // 3. If the type satisfies the ITuple deconstruct constraints, use ITuple semantics + // Here we test the relative priority of steps 2 and 3. + // - Found inapplicable Deconstruct method; use ITuple + var source = @"using System; +using System.Runtime.CompilerServices; +class Program +{ + static void Main() + { + IA a = new A(); + if (a is (var x, var y)) Console.Write($""{x} {y}""); + } +} +interface IA : ITuple +{ + void Deconstruct(out int X, out int Y, out int Z); +} +class A: IA, ITuple +{ + void IA.Deconstruct(out int X, out int Y, out int Z) => throw null; + int ITuple.Length => 2; + object ITuple.this[int i] => i + 5; +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + ); + CompileAndVerify(compilation, expectedOutput: "5 6"); + } + + [Fact] + public void DeconstructVsITuple_03b() + { + // From LDM 2018-11-05: + // 1. If the type is a tuple type (any arity >= 0; see below), then use the tuple semantics + // 2. If "binding" a Deconstruct invocation would find one or more applicable methods, use Deconstruct. + // 3. If the type satisfies the ITuple deconstruct constraints, use ITuple semantics + // Here we test the relative priority of steps 2 and 3. + // - Found inapplicable Deconstruct method; use ITuple + var source = @"using System; +using System.Runtime.CompilerServices; +class Program +{ + static void Main() + { + IA a = new A(); + if (a is var (x, y)) Console.Write($""{x} {y}""); + } +} +interface IA : ITuple +{ + void Deconstruct(out int X, out int Y, out int Z); +} +class A: IA, ITuple +{ + void IA.Deconstruct(out int X, out int Y, out int Z) => throw null; + int ITuple.Length => 2; + object ITuple.this[int i] => i + 5; +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + ); + CompileAndVerify(compilation, expectedOutput: "5 6"); + } + + [Fact] + public void ShortTuplePattern_01() + { + // test 0-element tuple pattern via ITuple + var source = @"using System; +using System.Runtime.CompilerServices; + +class Program +{ + static void Main() + { +#pragma warning disable CS0436 + var data = new object[] { null, new ValueTuple(), new C(), new object() }; + for (int i = 0; i < data.Length; i++) + { + var datum = data[i]; + if (datum is ()) Console.Write(i); + } + } +} + +public class C : ITuple +{ + int ITuple.Length => 0; + object ITuple.this[int i] => throw new NotImplementedException(); +} +namespace System +{ + struct ValueTuple : ITuple + { + int ITuple.Length => 0; + object ITuple.this[int i] => throw new NotImplementedException(); + } +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + ); + CompileAndVerify(compilation, expectedOutput: "12"); + } + + [Fact] + public void ShortTuplePattern_02() + { + // test 1-element tuple pattern via ITuple + var source = @"using System; +using System.Runtime.CompilerServices; + +class Program +{ + static void Main() + { +#pragma warning disable CS0436 + var data = new object[] { null, new ValueTuple('a'), new C(), new object() }; + for (int i = 0; i < data.Length; i++) + { + var datum = data[i]; + if (datum is (var x) _) Console.Write($""{i} {x} ""); + } + } +} + +public class C : ITuple +{ + int ITuple.Length => 1; + object ITuple.this[int i] => 'b'; +} +namespace System +{ + struct ValueTuple : ITuple + { + public TItem1 Item1; + public ValueTuple(TItem1 item1) => this.Item1 = item1; + int ITuple.Length => 1; + object ITuple.this[int i] => this.Item1; + } +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + ); + CompileAndVerify(compilation, expectedOutput: "1 a 2 b"); + } + + [Fact] + public void ShortTuplePattern_03() + { + // test 0-element tuple pattern via Deconstruct + var source = @"using System; + +class Program +{ + static void Main() + { + var data = new C[] { null, new C() }; + for (int i = 0; i < data.Length; i++) + { + var datum = data[i]; + if (datum is ()) Console.Write(i); + } + } +} + +public class C +{ + public void Deconstruct() {} +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + ); + CompileAndVerify(compilation, expectedOutput: "1"); + } + + [Fact] + public void ShortTuplePattern_03b() + { + // test 0-element tuple pattern via extension Deconstruct + var source = @"using System; + +class Program +{ + static void Main() + { + var data = new C[] { null, new C() }; + for (int i = 0; i < data.Length; i++) + { + var datum = data[i]; + if (datum is ()) Console.Write(i); + } + } +} + +public class C +{ +} +public static class Extension +{ + public static void Deconstruct(this C self) {} +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + ); + CompileAndVerify(compilation, expectedOutput: "1"); + } + + [Fact] + public void ShortTuplePattern_04() + { + // test 1-element tuple pattern via Deconstruct + var source = @"using System; + +class Program +{ + static void Main() + { + var data = new C[] { null, new C() }; + for (int i = 0; i < data.Length; i++) + { + var datum = data[i]; + if (datum is (var x) _) Console.Write($""{i} {x} ""); + } + } +} + +public class C +{ + public void Deconstruct(out char a) => a = 'a'; +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + ); + CompileAndVerify(compilation, expectedOutput: "1 a"); + } + + [Fact] + public void ShortTuplePattern_04b() + { + // test 1-element tuple pattern via extension Deconstruct + var source = @"using System; + +class Program +{ + static void Main() + { + var data = new C[] { null, new C() }; + for (int i = 0; i < data.Length; i++) + { + var datum = data[i]; + if (datum is (var x) _) Console.Write($""{i} {x} ""); + } + } +} + +public class C +{ +} +public static class Extension +{ + public static void Deconstruct(this C self, out char a) => a = 'a'; +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + ); + CompileAndVerify(compilation, expectedOutput: "1 a"); + } + + [Fact] + public void ShortTuplePattern_05() + { + // test 0-element tuple pattern via System.ValueTuple + var source = @"using System; + +class Program +{ + static void Main() + { +#pragma warning disable CS0436 + var data = new ValueTuple[] { new ValueTuple() }; + for (int i = 0; i < data.Length; i++) + { + var datum = data[i]; + if (datum is ()) Console.Write(i); + } + } +} + +namespace System +{ + struct ValueTuple + { + } +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + ); + CompileAndVerify(compilation, expectedOutput: "0"); + } + + [Fact] + public void ShortTuplePattern_06() + { + // test 1-element tuple pattern via System.ValueTuple + var source = @"using System; + +class Program +{ + static void Main() + { +#pragma warning disable CS0436 + var data = new ValueTuple[] { new ValueTuple('a') }; + for (int i = 0; i < data.Length; i++) + { + var datum = data[i]; + if (datum is (var x) _) Console.Write($""{i} {x} ""); + } + } +} + +namespace System +{ + struct ValueTuple + { + public TItem1 Item1; + public ValueTuple(TItem1 item1) => this.Item1 = item1; + } +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + ); + CompileAndVerify(compilation, expectedOutput: "0 a"); + } + + [Fact] + public void ShortTuplePattern_06b() + { + // test 1-element tuple pattern via System.ValueTuple + var source = @"using System; + +class Program +{ + static void Main() + { +#pragma warning disable CS0436 + var data = new ValueTuple[] { new ValueTuple('a') }; + for (int i = 0; i < data.Length; i++) + { + var datum = data[i]; + if (datum is var (x)) Console.Write($""{i} {x} ""); + } + } +} + +namespace System +{ + struct ValueTuple + { + public TItem1 Item1; + public ValueTuple(TItem1 item1) => this.Item1 = item1; + } +} +"; + var compilation = CreatePatternCompilation(source); + compilation.VerifyDiagnostics( + ); + CompileAndVerify(compilation, expectedOutput: "0 a"); + } + [Fact, WorkItem(31167, "https://github.com/dotnet/roslyn/issues/31167")] public void NonExhaustiveBoolSwitchExpression() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs index 3c79478eeba30..e5aad4643b8d1 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs @@ -2601,9 +2601,6 @@ public static class Extensions // (8,28): error CS8350: This combination of arguments to 'Extensions.Deconstruct(ref Span, out Span, out Span)' is disallowed because it may expose variables referenced by parameter 'x' outside of their declaration scope // (global, global) = global; Diagnostic(ErrorCode.ERR_CallArgMixing, "global").WithArguments("Extensions.Deconstruct(ref System.Span, out System.Span, out System.Span)", "x").WithLocation(8, 28), - // (8,28): error CS8129: No suitable Deconstruct instance or extension method was found for type 'Span', with 2 out parameters and a void return type. - // (global, global) = global; - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "global").WithArguments("System.Span", "2").WithLocation(8, 28), // warning CS1685: The predefined type 'ExtensionAttribute' is defined in multiple assemblies in the global alias; using definition from 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' Diagnostic(ErrorCode.WRN_MultiplePredefTypes).WithArguments("System.Runtime.CompilerServices.ExtensionAttribute", "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089").WithLocation(1, 1) ); diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests.cs index d248cb0d30d53..efe7501b29912 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests.cs @@ -1839,9 +1839,6 @@ public void ParenthesizedExpression_02() public void ParenthesizedExpression_03() { UsingStatement(@"switch (e) { case (x: ((3))): ; }", TestOptions.RegularWithoutRecursivePatterns, - // (1,19): error CS8407: A single-element deconstruct pattern requires a type before the open parenthesis. - // switch (e) { case (x: ((3))): ; } - Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresType, "(x: ((3)))").WithLocation(1, 19), // (1,19): error CS8370: Feature 'recursive patterns' is not available in C# 7.3. Please use language version 8.0 or greater. // switch (e) { case (x: ((3))): ; } Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "(x: ((3)))").WithArguments("recursive patterns", "8.0").WithLocation(1, 19) @@ -1915,16 +1912,13 @@ public void ParenthesizedExpression_04() UsingStatement(@"switch (e) { case (((x: 3))): ; }", TestOptions.RegularWithoutRecursivePatterns, // (1,19): error CS8407: A single-element deconstruct pattern requires a type before the open parenthesis. // switch (e) { case (((x: 3))): ; } - Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresType, "(((x: 3)))").WithLocation(1, 19), + Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresDisambiguation, "(((x: 3)))").WithLocation(1, 19), // (1,19): error CS8370: Feature 'recursive patterns' is not available in C# 7.3. Please use language version 8.0 or greater. // switch (e) { case (((x: 3))): ; } Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "(((x: 3)))").WithArguments("recursive patterns", "8.0").WithLocation(1, 19), // (1,20): error CS8407: A single-element deconstruct pattern requires a type before the open parenthesis. // switch (e) { case (((x: 3))): ; } - Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresType, "((x: 3))").WithLocation(1, 20), - // (1,21): error CS8407: A single-element deconstruct pattern requires a type before the open parenthesis. - // switch (e) { case (((x: 3))): ; } - Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresType, "(x: 3)").WithLocation(1, 21) + Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresDisambiguation, "((x: 3))").WithLocation(1, 20) ); N(SyntaxKind.SwitchStatement); { @@ -2242,13 +2236,10 @@ public void BrokenPattern_08() public void ParenthesizedExpression_05() { UsingStatement(@"switch (e) { case (x: ): ; }", TestOptions.RegularWithoutRecursivePatterns, - // (1,19): error CS8407: A single-element deconstruct pattern requires a type before the open parenthesis. - // switch (e) { case (x: ): ; } - Diagnostic(ErrorCode.ERR_SingleElementPositionalPatternRequiresType, "(x: )").WithLocation(1, 19), // (1,19): error CS8370: Feature 'recursive patterns' is not available in C# 7.3. Please use language version 8.0 or greater. // switch (e) { case (x: ): ; } Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "(x: )").WithArguments("recursive patterns", "8.0").WithLocation(1, 19), - // (1,23): error CS8400: Pattern missing + // (1,23): error CS8504: Pattern missing // switch (e) { case (x: ): ; } Diagnostic(ErrorCode.ERR_MissingPattern, ")").WithLocation(1, 23) ); @@ -5490,5 +5481,646 @@ public void NotDiscardInIsTypeExpression() } EOF(); } + + [Fact] + public void ShortTuplePatterns() + { + UsingExpression( +@"e switch { + var () => 1, + () => 2, + var (x) => 3, + (1) _ => 4, + (1) x => 5, + (1) {} => 6, + (Item1: 1) => 7, + C(1) => 8 +}", + expectedErrors: new DiagnosticDescription[0]); + N(SyntaxKind.SwitchExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.SwitchKeyword); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.VarPattern); + { + N(SyntaxKind.VarKeyword); + N(SyntaxKind.ParenthesizedVariableDesignation); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.DeconstructionPatternClause); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "2"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.VarPattern); + { + N(SyntaxKind.VarKeyword); + N(SyntaxKind.ParenthesizedVariableDesignation); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "3"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.DeconstructionPatternClause); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.DiscardDesignation); + { + N(SyntaxKind.UnderscoreToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "4"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.DeconstructionPatternClause); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "5"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.DeconstructionPatternClause); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.PropertyPatternClause); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "6"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.DeconstructionPatternClause); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.NameColon); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Item1"); + } + N(SyntaxKind.ColonToken); + } + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "7"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "C"); + } + N(SyntaxKind.DeconstructionPatternClause); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "8"); + } + } + N(SyntaxKind.CloseBraceToken); + } + EOF(); + } + + + [Fact] + public void NestedShortTuplePatterns() + { + UsingExpression( +@"e switch { + {X: var ()} => 1, + {X: ()} => 2, + {X: var (x)} => 3, + {X: (1) _} => 4, + {X: (1) x} => 5, + {X: (1) {}} => 6, + {X: (Item1: 1)} => 7, + {X: C(1)} => 8 +}", + expectedErrors: new DiagnosticDescription[0]); + N(SyntaxKind.SwitchExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.SwitchKeyword); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.PropertyPatternClause); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.NameColon); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.ColonToken); + } + N(SyntaxKind.VarPattern); + { + N(SyntaxKind.VarKeyword); + N(SyntaxKind.ParenthesizedVariableDesignation); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.PropertyPatternClause); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.NameColon); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.ColonToken); + } + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.DeconstructionPatternClause); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "2"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.PropertyPatternClause); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.NameColon); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.ColonToken); + } + N(SyntaxKind.VarPattern); + { + N(SyntaxKind.VarKeyword); + N(SyntaxKind.ParenthesizedVariableDesignation); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "x"); + } + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "3"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.PropertyPatternClause); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.NameColon); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.ColonToken); + } + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.DeconstructionPatternClause); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.DiscardDesignation); + { + N(SyntaxKind.UnderscoreToken); + } + } + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "4"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.PropertyPatternClause); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.NameColon); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.ColonToken); + } + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.DeconstructionPatternClause); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.SingleVariableDesignation); + { + N(SyntaxKind.IdentifierToken, "x"); + } + } + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "5"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.PropertyPatternClause); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.NameColon); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.ColonToken); + } + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.DeconstructionPatternClause); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + N(SyntaxKind.PropertyPatternClause); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.CloseBraceToken); + } + } + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "6"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.PropertyPatternClause); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.NameColon); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.ColonToken); + } + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.DeconstructionPatternClause); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.NameColon); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Item1"); + } + N(SyntaxKind.ColonToken); + } + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "7"); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.SwitchExpressionArm); + { + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.PropertyPatternClause); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.NameColon); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "X"); + } + N(SyntaxKind.ColonToken); + } + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "C"); + } + N(SyntaxKind.DeconstructionPatternClause); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "1"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + N(SyntaxKind.CloseBraceToken); + } + } + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "8"); + } + } + N(SyntaxKind.CloseBraceToken); + } + EOF(); + } } } diff --git a/src/Workspaces/CSharpTest/EmbeddedLanguages/VirtualChars/CSharpVirtualCharServiceTests.cs b/src/Workspaces/CSharpTest/EmbeddedLanguages/VirtualChars/CSharpVirtualCharServiceTests.cs index a39cb5755dff5..27c83781603a8 100644 --- a/src/Workspaces/CSharpTest/EmbeddedLanguages/VirtualChars/CSharpVirtualCharServiceTests.cs +++ b/src/Workspaces/CSharpTest/EmbeddedLanguages/VirtualChars/CSharpVirtualCharServiceTests.cs @@ -94,7 +94,7 @@ public void TestBracesInInterpolatedVerbatimSimpleString() Test("$@\"{{\"", "['{',[3,5]]"); } - [Fact(Skip = "https://github.com/dotnet/roslyn/issues/29172 https://github.com/dotnet/roslyn/issues/30135")] + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/29172")] public void TestReverseInterpolatedVerbatimString() { // This will need to be fixed once @$ strings come online.