diff --git a/docs/contributing/Compiler Test Plan.md b/docs/contributing/Compiler Test Plan.md index 3fcf98b6fc6e8..7f0e99341223f 100644 --- a/docs/contributing/Compiler Test Plan.md +++ b/docs/contributing/Compiler Test Plan.md @@ -92,7 +92,7 @@ This document provides guidance for thinking about language interactions and tes - Ref return, ref readonly return, ref ternary, ref readonly local, ref local re-assignment, ref foreach - `this = e;` in `struct` .ctor - Stackalloc (including initializers) -- Patterns (constant, declaration, `var`, positional, property, discard, parenthesized, type, relational, `and`/`or`/`not`) +- Patterns (constant, declaration, `var`, positional, property and extended property, discard, parenthesized, type, relational, `and`/`or`/`not`) - Switch expressions - With expressions (on record classes and on value types) - Nullability annotations (`?`, attributes) and analysis diff --git a/docs/features/patterns.work.md b/docs/features/patterns.work.md index 4ac2171d51778..6fdb48217267c 100644 --- a/docs/features/patterns.work.md +++ b/docs/features/patterns.work.md @@ -30,7 +30,7 @@ - [ ] Test all of the ways that a name would not be valid to name a property in a property pattern - [ ] Need to design the data representation for edit-and-continue for temps in patterns - [ ] Need to precisely share temps per the edit-and-continue spec - - [ ] Need bullets here or github issues for PROTOTYPE(patterns2) comments. + - [ ] Need bullets here or github issues for prototype comments. - [ ] Need to ensure good code quality, e.g. avoid redundant null checks preceding types tests - [ ] Need to ensure a good tradeoff between decision tree size explosion and execution of redundant tests. diff --git a/eng/targets/Settings.props b/eng/targets/Settings.props index 446f5d0c2c735..089e0df83de0c 100644 --- a/eng/targets/Settings.props +++ b/eng/targets/Settings.props @@ -208,8 +208,7 @@ and hence suppress this warning until we get closer to release and a more thorough documentation story --> - - $(NoWarn);1573;1591;1701;RS0016 + $(NoWarn);1573;1591;1701 $(DefineConstants);$(InitialDefineConstants) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs index 4926284fec538..13d0d455f612d 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs @@ -218,10 +218,33 @@ internal BoundPattern BindConstantPatternWithFallbackToTypePattern( if (!hasErrors) CheckFeatureAvailability(innerExpression, MessageID.IDS_FeatureTypePattern, diagnostics); + if (hasSuppression(expression)) + { + diagnostics.Add(ErrorCode.ERR_IllegalSuppression, expression.Location); + hasErrors = true; + } + var boundType = (BoundTypeExpression)convertedExpression; bool isExplicitNotNullTest = boundType.Type.SpecialType == SpecialType.System_Object; return new BoundTypePattern(node, boundType, isExplicitNotNullTest, inputType, boundType.Type, hasErrors); } + + static bool hasSuppression(ExpressionSyntax e) + { + while (true) + { + switch (e) + { + case ParenthesizedExpressionSyntax p: + e = p.Expression; + break; + case PostfixUnaryExpressionSyntax { RawKind: (int)SyntaxKind.SuppressNullableWarningExpression }: + return true; + default: + return false; + } + } + } } private ExpressionSyntax SkipParensAndNullSuppressions(ExpressionSyntax e) @@ -780,21 +803,27 @@ deconstructMethod is null && bool isError = hasErrors || outPlaceholders.IsDefaultOrEmpty || i >= outPlaceholders.Length; TypeSymbol elementType = isError ? CreateErrorType() : outPlaceholders[i].Type; ParameterSymbol? parameter = null; - // PROTOTYPE(extended-property-patterns) ExpressionColon - if (subPattern.NameColon != null && !isError) + if (!isError) { - // Check that the given name is the same as the corresponding parameter of the method. - int parameterIndex = i + skippedExtensionParameters; - if (parameterIndex < deconstructMethod!.ParameterCount) + if (subPattern.NameColon != null) { - parameter = deconstructMethod.Parameters[parameterIndex]; - string name = subPattern.NameColon.Name.Identifier.ValueText; - string parameterName = parameter.Name; - if (name != parameterName) + // Check that the given name is the same as the corresponding parameter of the method. + int parameterIndex = i + skippedExtensionParameters; + if (parameterIndex < deconstructMethod!.ParameterCount) { - diagnostics.Add(ErrorCode.ERR_DeconstructParameterNameMismatch, subPattern.NameColon.Name.Location, name, parameterName); + parameter = deconstructMethod.Parameters[parameterIndex]; + string name = subPattern.NameColon.Name.Identifier.ValueText; + string parameterName = parameter.Name; + if (name != parameterName) + { + diagnostics.Add(ErrorCode.ERR_DeconstructParameterNameMismatch, subPattern.NameColon.Name.Location, name, parameterName); + } } } + else if (subPattern.ExpressionColon != null) + { + diagnostics.Add(ErrorCode.ERR_IdentifierExpected, subPattern.ExpressionColon.Expression.Location); + } } var boundSubpattern = new BoundPositionalSubpattern( @@ -819,12 +848,15 @@ private void BindITupleSubpatterns( var objectType = Compilation.GetSpecialType(SpecialType.System_Object); foreach (var subpatternSyntax in node.Subpatterns) { - // PROTOTYPE(extended-property-patterns) ExpressionColon if (subpatternSyntax.NameColon != null) { // error: name not permitted in ITuple deconstruction diagnostics.Add(ErrorCode.ERR_ArgumentNameInITuplePattern, subpatternSyntax.NameColon.Location); } + else if (subpatternSyntax.ExpressionColon != null) + { + diagnostics.Add(ErrorCode.ERR_IdentifierExpected, subpatternSyntax.ExpressionColon.Expression.Location); + } var boundSubpattern = new BoundPositionalSubpattern( subpatternSyntax, @@ -876,11 +908,17 @@ private void BindValueTupleSubpatterns( bool isError = i >= elementTypesWithAnnotations.Length; TypeSymbol elementType = isError ? CreateErrorType() : elementTypesWithAnnotations[i].Type; FieldSymbol? foundField = null; - // PROTOTYPE(extended-property-patterns) ExpressionColon - if (subpatternSyntax.NameColon != null && !isError) + if (!isError) { - string name = subpatternSyntax.NameColon.Name.Identifier.ValueText; - foundField = CheckIsTupleElement(subpatternSyntax.NameColon.Name, (NamedTypeSymbol)declType, name, i, diagnostics); + if (subpatternSyntax.NameColon != null) + { + string name = subpatternSyntax.NameColon.Name.Identifier.ValueText; + foundField = CheckIsTupleElement(subpatternSyntax.NameColon.Name, (NamedTypeSymbol)declType, name, i, diagnostics); + } + else if (subpatternSyntax.ExpressionColon != null) + { + diagnostics.Add(ErrorCode.ERR_IdentifierExpected, subpatternSyntax.ExpressionColon.Expression.Location); + } } BoundPositionalSubpattern boundSubpattern = new BoundPositionalSubpattern( diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index a81c3ecccdafa..af32453b912f2 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -2035,7 +2035,6 @@ internal CSharpTypeInfo GetTypeInfoForNode( } if (lowestBoundNode is BoundPropertySubpatternMember member) { - // PROTOTYPE(extended-property-patterns) Should we capture unwrapped nullables in nested members as the ConvertedType? return new CSharpTypeInfo(member.Type, member.Type, nullability: default, convertedNullability: default, Conversion.Identity); } diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 93662aeb73d2e..31c3e8954ca50 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1932,15 +1932,13 @@ internal enum ErrorCode #region diagnostics introduced for C# 10.0 - // PROTOTYPE(extended-property-patterns) Temp values - ERR_InvalidNameInSubpattern = 9000, - ERR_InheritingFromRecordWithSealedToString = 8912, ERR_HiddenPositionalMember = 8913, ERR_GlobalUsingInNamespace = 8914, ERR_GlobalUsingOutOfOrder = 8915, ERR_AttributesRequireParenthesizedLambdaExpression = 8916, ERR_CannotInferDelegateType = 8917, + ERR_InvalidNameInSubpattern = 8918, #endregion diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index ef6d6999d4aa9..00a46eeec0a85 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -339,6 +339,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) case MessageID.IDS_FeatureGlobalUsing: case MessageID.IDS_FeatureInferredDelegateType: // semantic check case MessageID.IDS_FeatureLambdaAttributes: // semantic check + case MessageID.IDS_FeatureExtendedPropertyPatterns: return LanguageVersion.Preview; // C# 9.0 features. @@ -499,10 +500,6 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) case MessageID.IDS_FeatureSwitchOnBool: // Checked in the binder. return LanguageVersion.CSharp2; - // PROTOTYPE(extended-property-patterns) Move - case MessageID.IDS_FeatureExtendedPropertyPatterns: - return LanguageVersion.Preview; - // Special C# 2 feature: only a warning in C# 1. case MessageID.IDS_FeatureModuleAttrLoc: return LanguageVersion.CSharp1; diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs index f5ce80db5ba92..47f3e34b9a04e 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/DefiniteAssignment.cs @@ -1580,87 +1580,98 @@ public override void VisitPattern(BoundPattern pattern) base.VisitPattern(pattern); var whenFail = StateWhenFalse; SetState(StateWhenTrue); - AssignPatternVariables(pattern); + assignPatternVariablesAndMarkReadFields(pattern); SetConditionalState(this.State, whenFail); - } - /// - /// Find the pattern variables of the pattern, and make them definitely assigned if . - /// That would be false under "not" and "or" patterns. - /// - private void AssignPatternVariables(BoundPattern pattern, bool definitely = true) - { - switch (pattern.Kind) + // Find the pattern variables of the pattern, and make them definitely assigned if . + // That would be false under "not" and "or" patterns. + void assignPatternVariablesAndMarkReadFields(BoundPattern pattern, bool definitely = true) { - case BoundKind.DeclarationPattern: - { - var pat = (BoundDeclarationPattern)pattern; - if (definitely) - Assign(pat, value: null, isRef: false, read: false); - break; - } - case BoundKind.DiscardPattern: - break; - case BoundKind.ConstantPattern: - { - var pat = (BoundConstantPattern)pattern; - this.VisitRvalue(pat.Value); + switch (pattern.Kind) + { + case BoundKind.DeclarationPattern: + { + var pat = (BoundDeclarationPattern)pattern; + if (definitely) + Assign(pat, value: null, isRef: false, read: false); + break; + } + case BoundKind.DiscardPattern: break; - } - case BoundKind.RecursivePattern: - { - var pat = (BoundRecursivePattern)pattern; - if (!pat.Deconstruction.IsDefaultOrEmpty) + case BoundKind.ConstantPattern: + { + var pat = (BoundConstantPattern)pattern; + this.VisitRvalue(pat.Value); + break; + } + case BoundKind.RecursivePattern: { - foreach (var subpat in pat.Deconstruction) + var pat = (BoundRecursivePattern)pattern; + if (!pat.Deconstruction.IsDefaultOrEmpty) { - AssignPatternVariables(subpat.Pattern, definitely); + foreach (var subpat in pat.Deconstruction) + { + assignPatternVariablesAndMarkReadFields(subpat.Pattern, definitely); + } } + if (!pat.Properties.IsDefaultOrEmpty) + { + foreach (BoundSubpattern sub in pat.Properties) + { + if (sub is BoundPropertySubpattern { Member: var member } + && _sourceAssembly is not null) + { + while (member is not null) + { + if (member.Symbol is FieldSymbol field) + { + _sourceAssembly.NoteFieldAccess(field, read: true, write: false); + } + + member = member.Receiver; + } + } + assignPatternVariablesAndMarkReadFields(sub.Pattern, definitely); + } + } + if (definitely) + Assign(pat, null, false, false); + break; } - if (!pat.Properties.IsDefaultOrEmpty) + case BoundKind.ITuplePattern: { - foreach (BoundSubpattern sub in pat.Properties) + var pat = (BoundITuplePattern)pattern; + foreach (var subpat in pat.Subpatterns) { - AssignPatternVariables(sub.Pattern, definitely); + assignPatternVariablesAndMarkReadFields(subpat.Pattern, definitely); } + break; } - if (definitely) - Assign(pat, null, false, false); + case BoundKind.TypePattern: break; - } - case BoundKind.ITuplePattern: - { - var pat = (BoundITuplePattern)pattern; - foreach (var subpat in pat.Subpatterns) + case BoundKind.RelationalPattern: { - AssignPatternVariables(subpat.Pattern, definitely); + var pat = (BoundRelationalPattern)pattern; + this.VisitRvalue(pat.Value); + break; } - break; - } - case BoundKind.TypePattern: - break; - case BoundKind.RelationalPattern: - { - var pat = (BoundRelationalPattern)pattern; - this.VisitRvalue(pat.Value); - break; - } - case BoundKind.NegatedPattern: - { - var pat = (BoundNegatedPattern)pattern; - AssignPatternVariables(pat.Negated, definitely: false); - break; - } - case BoundKind.BinaryPattern: - { - var pat = (BoundBinaryPattern)pattern; - bool def = definitely && !pat.Disjunction; - AssignPatternVariables(pat.Left, def); - AssignPatternVariables(pat.Right, def); - break; - } - default: - throw ExceptionUtilities.UnexpectedValue(pattern.Kind); + case BoundKind.NegatedPattern: + { + var pat = (BoundNegatedPattern)pattern; + assignPatternVariablesAndMarkReadFields(pat.Negated, definitely: false); + break; + } + case BoundKind.BinaryPattern: + { + var pat = (BoundBinaryPattern)pattern; + bool def = definitely && !pat.Disjunction; + assignPatternVariablesAndMarkReadFields(pat.Left, def); + assignPatternVariablesAndMarkReadFields(pat.Right, def); + break; + } + default: + throw ExceptionUtilities.UnexpectedValue(pattern.Kind); + } } } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs index 0c348ddf2b1d6..5c7294dd20ef5 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker_Patterns.cs @@ -173,10 +173,9 @@ private void LearnFromAnyNullPatterns( { foreach (BoundPropertySubpattern subpattern in rp.Properties) { - // PROTOTYPE(extended-property-patterns): Investigate if we need to visit nested members; is there a test gap? - if (subpattern.Member is { Symbol: Symbol symbol } member) + if (subpattern.Member is BoundPropertySubpatternMember member) { - LearnFromAnyNullPatterns(GetOrCreateSlot(symbol, inputSlot), member.Type, subpattern.Pattern); + LearnFromAnyNullPatterns(getExtendedPropertySlot(member, inputSlot), member.Type, subpattern.Pattern); } } } @@ -192,6 +191,26 @@ private void LearnFromAnyNullPatterns( default: throw ExceptionUtilities.UnexpectedValue(pattern); } + + int getExtendedPropertySlot(BoundPropertySubpatternMember member, int inputSlot) + { + if (member.Symbol is null) + { + return -1; + } + + if (member.Receiver is not null) + { + inputSlot = getExtendedPropertySlot(member.Receiver, inputSlot); + } + + if (inputSlot < 0) + { + return inputSlot; + } + + return GetOrCreateSlot(member.Symbol, inputSlot); + } } protected override LocalState VisitSwitchStatementDispatch(BoundSwitchStatement node) diff --git a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs index 3460030f81ddc..6d238dc3e99df 100644 --- a/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs +++ b/src/Compilers/CSharp/Portable/Operations/CSharpOperationFactory.cs @@ -2286,48 +2286,81 @@ private IOperation CreateBoundDiscardPatternOperation(BoundDiscardPattern boundN internal IPropertySubpatternOperation CreatePropertySubpattern(BoundPropertySubpattern subpattern, ITypeSymbol matchedType) { - SyntaxNode syntax = subpattern.Syntax; - IOperation member = CreatePropertySubpatternMember(subpattern.Member, matchedType) ?? - OperationFactory.CreateInvalidOperation(_semanticModel, syntax, ImmutableArray.Empty, isImplicit: true); - IPatternOperation pattern = (IPatternOperation)Create(subpattern.Pattern); - return new PropertySubpatternOperation(member, pattern, _semanticModel, syntax, isImplicit: false); - } + // We treat `c is { ... .Prop: }` as `c is { ...: { Prop: } }` - internal IOperation? CreatePropertySubpatternMember(BoundPropertySubpatternMember? member, ITypeSymbol matchedType) - { + SyntaxNode subpatternSyntax = subpattern.Syntax; + BoundPropertySubpatternMember? member = subpattern.Member; + IPatternOperation pattern = (IPatternOperation)Create(subpattern.Pattern); if (member is null) - return null; + { + var reference = OperationFactory.CreateInvalidOperation(_semanticModel, subpatternSyntax, ImmutableArray.Empty, isImplicit: true); + return new PropertySubpatternOperation(reference, pattern, _semanticModel, subpatternSyntax, isImplicit: false); + } + + // Create an operation for last property access: + // `{ SingleProp: }` + // or + // `.LastProp: ` portion (treated as `{ LastProp: }`) + var nameSyntax = member.Syntax; + var inputType = getInputType(member, matchedType); + IPropertySubpatternOperation? result = createPropertySubpattern(member.Symbol, pattern, inputType, nameSyntax, isSingle: member.Receiver is null); - switch (member.Symbol) + while (member.Receiver is not null) { - case FieldSymbol field: - { - var constantValue = field.GetConstantValue(ConstantFieldsInProgress.Empty, earlyDecodingWellKnownAttributes: false); - return new FieldReferenceOperation(field.GetPublicSymbol(), isDeclaration: false, - // PROTOTYPE(extended-property-patterns) We shouldn't use StrippedType here, maybe only if this is a nested member? - createReceiver(), _semanticModel, member.Syntax, field.Type.StrippedType().GetPublicSymbol(), constantValue, isImplicit: false); - } - case PropertySymbol property: - { - return new PropertyReferenceOperation(property.GetPublicSymbol(), ImmutableArray.Empty, - // PROTOTYPE(extended-property-patterns) We shouldn't use StrippedType here, maybe only if this is a nested member? - createReceiver(), _semanticModel, member.Syntax, property.Type.StrippedType().GetPublicSymbol(), isImplicit: false); - } - default: - { - // We should expose the symbol in this case somehow: - // https://github.com/dotnet/roslyn/issues/33175 - IOperation? receiver = CreatePropertySubpatternMember(member.Receiver, matchedType); - var children = receiver is null ? ImmutableArray.Empty : ImmutableArray.Create(receiver); - return OperationFactory.CreateInvalidOperation(_semanticModel, member.Syntax, children, isImplicit: false); - } + member = member.Receiver; + nameSyntax = member.Syntax; + ITypeSymbol previousType = inputType; + inputType = getInputType(member, matchedType); + + // Create an operation for a preceding property access: + // { PrecedingProp: } + IPatternOperation nestedPattern = new RecursivePatternOperation( + matchedType: previousType, deconstructSymbol: null, deconstructionSubpatterns: ImmutableArray.Empty, + propertySubpatterns: ImmutableArray.Create(result), declaredSymbol: null, + previousType, narrowedType: previousType, semanticModel: _semanticModel, nameSyntax, isImplicit: true); + + result = createPropertySubpattern(member.Symbol, nestedPattern, inputType, nameSyntax, isSingle: false); + } + + return result; + + IPropertySubpatternOperation createPropertySubpattern(Symbol? symbol, IPatternOperation pattern, ITypeSymbol receiverType, SyntaxNode nameSyntax, bool isSingle) + { + Debug.Assert(nameSyntax is not null); + IOperation reference; + switch (symbol) + { + case FieldSymbol field: + { + var constantValue = field.GetConstantValue(ConstantFieldsInProgress.Empty, earlyDecodingWellKnownAttributes: false); + reference = new FieldReferenceOperation(field.GetPublicSymbol(), isDeclaration: false, + createReceiver(), _semanticModel, nameSyntax, type: field.Type.GetPublicSymbol(), constantValue, isImplicit: false); + break; + } + case PropertySymbol property: + { + reference = new PropertyReferenceOperation(property.GetPublicSymbol(), ImmutableArray.Empty, + createReceiver(), _semanticModel, nameSyntax, type: property.Type.GetPublicSymbol(), isImplicit: false); + break; + } + default: + { + // We should expose the symbol in this case somehow: + // https://github.com/dotnet/roslyn/issues/33175 + reference = OperationFactory.CreateInvalidOperation(_semanticModel, nameSyntax, ImmutableArray.Empty, isImplicit: false); + break; + } + } + + var syntaxForPropertySubpattern = isSingle ? subpatternSyntax : nameSyntax; + return new PropertySubpatternOperation(reference, pattern, _semanticModel, syntaxForPropertySubpattern, isImplicit: !isSingle); + + IOperation? createReceiver() + => symbol?.IsStatic == false ? new InstanceReferenceOperation(InstanceReferenceKind.PatternInput, _semanticModel, nameSyntax!, receiverType, isImplicit: true) : null; } - IOperation? createReceiver() - => CreatePropertySubpatternMember(member.Receiver, matchedType) ?? - (member.Symbol.IsStatic == false - ? new InstanceReferenceOperation(InstanceReferenceKind.PatternInput, _semanticModel, member.Syntax, matchedType, isImplicit: true) - : null); + static ITypeSymbol getInputType(BoundPropertySubpatternMember member, ITypeSymbol matchedType) + => member.Receiver?.Type.StrippedType().GetPublicSymbol() ?? matchedType; } private IInstanceReferenceOperation CreateCollectionValuePlaceholderOperation(BoundObjectOrCollectionValuePlaceholder placeholder) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser_Patterns.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser_Patterns.cs index 7eb53d7cdb002..e3966c4d923c3 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser_Patterns.cs @@ -315,8 +315,7 @@ private PatternSyntax ParsePatternContinued(TypeSyntax type, Precedence preceden designation0 == null && subPatterns.Count == 1 && subPatterns.SeparatorCount == 0 && - // PROTOTYPE(extended-property-patterns) ExpressionColon - subPatterns[0].NameColon == null) + subPatterns[0].ExpressionColon == null) { var subpattern = subPatterns[0].Pattern; switch (subpattern) diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index ba41992da8333..1ec8fc6c2c318 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -1,6 +1,23 @@ +abstract Microsoft.CodeAnalysis.CSharp.Syntax.BaseExpressionColonSyntax.ColonToken.get -> Microsoft.CodeAnalysis.SyntaxToken +abstract Microsoft.CodeAnalysis.CSharp.Syntax.BaseExpressionColonSyntax.Expression.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax Microsoft.CodeAnalysis.CSharp.Syntax.BaseExpressionColonSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.BaseExpressionColonSyntax.WithColonToken(Microsoft.CodeAnalysis.SyntaxToken colonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseExpressionColonSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.BaseExpressionColonSyntax.WithExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseExpressionColonSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.SyntaxToken colonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax.WithColonToken(Microsoft.CodeAnalysis.SyntaxToken colonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax.WithExpression(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax.ExpressionColon.get -> Microsoft.CodeAnalysis.CSharp.Syntax.BaseExpressionColonSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.BaseExpressionColonSyntax expressionColon, Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax.WithExpressionColon(Microsoft.CodeAnalysis.CSharp.Syntax.BaseExpressionColonSyntax expressionColon) -> Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax +Microsoft.CodeAnalysis.CSharp.SyntaxKind.ExpressionColon = 9069 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind Microsoft.CodeAnalysis.CSharp.SyntaxKind.RecordStructDeclaration = 9068 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind override Microsoft.CodeAnalysis.CSharp.CSharpCompilation.GetUsedAssemblyReferences(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Immutable.ImmutableArray +override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitExpressionColon(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax node) -> Microsoft.CodeAnalysis.SyntaxNode +override Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> void +override Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor visitor) -> TResult +override Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax.ColonToken.get -> Microsoft.CodeAnalysis.SyntaxToken +override Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax.Expression.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax override Microsoft.CodeAnalysis.CSharp.Syntax.NameColonSyntax.Expression.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax *REMOVED*Microsoft.CodeAnalysis.CSharp.Syntax.NameColonSyntax.ColonToken.get -> Microsoft.CodeAnalysis.SyntaxToken override Microsoft.CodeAnalysis.CSharp.Syntax.NameColonSyntax.ColonToken.get -> Microsoft.CodeAnalysis.SyntaxToken @@ -11,6 +28,7 @@ Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax.AddAttr override Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax.AttributeLists.get -> Microsoft.CodeAnalysis.SyntaxList Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax.Update(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.SyntaxToken arrowToken, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax.WithAttributeLists(Microsoft.CodeAnalysis.SyntaxList attributeLists) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ExpressionColon(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expression, Microsoft.CodeAnalysis.SyntaxToken colonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParenthesizedLambdaExpression(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ParenthesizedLambdaExpression(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax parameterList, Microsoft.CodeAnalysis.SyntaxToken arrowToken, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.ParenthesizedLambdaExpressionSyntax Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax.WithClassOrStructKeyword(Microsoft.CodeAnalysis.SyntaxToken classOrStructKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax @@ -26,9 +44,10 @@ Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax.Update(Microso Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax.WithAttributeLists(Microsoft.CodeAnalysis.SyntaxList attributeLists) -> Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SimpleLambdaExpression(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax parameter, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SimpleLambdaExpression(Microsoft.CodeAnalysis.SyntaxList attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterSyntax parameter, Microsoft.CodeAnalysis.SyntaxToken arrowToken, Microsoft.CodeAnalysis.CSharp.Syntax.BlockSyntax block, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax expressionBody) -> Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.Subpattern(Microsoft.CodeAnalysis.CSharp.Syntax.BaseExpressionColonSyntax expressionColon, Microsoft.CodeAnalysis.CSharp.Syntax.PatternSyntax pattern) -> Microsoft.CodeAnalysis.CSharp.Syntax.SubpatternSyntax static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.UsingDirective(Microsoft.CodeAnalysis.SyntaxToken globalKeyword, Microsoft.CodeAnalysis.SyntaxToken usingKeyword, Microsoft.CodeAnalysis.SyntaxToken staticKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.NameEqualsSyntax alias, Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax name, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax.GlobalKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken globalKeyword, Microsoft.CodeAnalysis.SyntaxToken usingKeyword, Microsoft.CodeAnalysis.SyntaxToken staticKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.NameEqualsSyntax alias, Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax name, Microsoft.CodeAnalysis.SyntaxToken semicolonToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax.WithGlobalKeyword(Microsoft.CodeAnalysis.SyntaxToken globalKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax - - +virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitExpressionColon(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax node) -> void +virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitExpressionColon(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionColonSyntax node) -> TResult diff --git a/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/SubpatternSyntax.cs b/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/SubpatternSyntax.cs deleted file mode 100644 index 71a3ed6e80468..0000000000000 --- a/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/SubpatternSyntax.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax -{ - partial class SubpatternSyntax - { - // PROTOTYPE(extended-property-patterns) Remove. We should always use ExpressionColon internally - public NameColonSyntax NameColon => ExpressionColon as NameColonSyntax; - } -} diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs index 52f958e86acef..cc20c1ed5e6ee 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs @@ -829,9 +829,6 @@ public enum SyntaxKind : ushort // Kinds between 9000 and 9039 are "reserved" for pattern matching. - // PROTOTYPE(extended-property-patterns) Temp value - ExpressionColon = 9039, - DeclarationExpression = 9040, RefExpression = 9050, RefType = 9051, @@ -859,5 +856,7 @@ public enum SyntaxKind : ushort FunctionPointerUnmanagedCallingConvention = 9067, RecordStructDeclaration = 9068, + + ExpressionColon = 9069, } } diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs index 1bef6e3e02daf..7fc105788d1fe 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IIsPatternExpression.cs @@ -1024,7 +1024,7 @@ public void IsPattern_RecursivePatternWithNestedPropertyPatterns() class C { C field; - C prop { get; } + C prop { get; } void M() { if (/**/this is { prop.field: null }/**/) { } @@ -1033,31 +1033,38 @@ void M() "; string expectedOperationTree = @" IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'this is { p ... eld: null }') - Value: + Value: IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') - Pattern: + Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null) (Syntax: '{ prop.field: null }') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): - IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null) (Syntax: 'prop.field: null') - Member: - IFieldReferenceOperation: C C.field (OperationKind.FieldReference, Type: C) (Syntax: 'prop.field') - Instance Receiver: - IPropertyReferenceOperation: C C.prop { get; } (OperationKind.PropertyReference, Type: C) (Syntax: 'prop') - Instance Receiver: - IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'prop') - Pattern: - IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: C, NarrowedType: C) - Value: - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: C, Constant: null, IsImplicit) (Syntax: 'null') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) - Operand: - ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'prop') + Member: + IPropertyReferenceOperation: C C.prop { get; } (OperationKind.PropertyReference, Type: C) (Syntax: 'prop') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'prop') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'prop') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'prop.field') + Member: + IFieldReferenceOperation: C C.field (OperationKind.FieldReference, Type: C) (Syntax: 'prop.field') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'prop.field') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: C, NarrowedType: C) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: C, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') "; var expectedDiagnostics = new DiagnosticDescription[] { - // file.cs(4,7): warning CS0169: The field 'C.field' is never used - // C field; - Diagnostic(ErrorCode.WRN_UnreferencedField, "field").WithArguments("C.field").WithLocation(4, 7) + // file.cs(4,7): warning CS0649: Field 'C.field' is never assigned to, and will always have its default value null + // C field; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "field").WithArguments("C.field", "null").WithLocation(4, 7) }; VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); @@ -1071,7 +1078,7 @@ public void IsPattern_RecursivePatternWithNestedPropertyPatterns_ControlFlow() class C { C field; - C prop { get; } + C prop { get; } void M() /**/ { @@ -1088,27 +1095,34 @@ void M() Statements (0) Jump if False (Regular) to Block[B2] IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'this is { p ... eld: null }') - Value: + Value: IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') - Pattern: + Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null) (Syntax: '{ prop.field: null }') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) - DeconstructionSubpatterns (0) - PropertySubpatterns (1): - IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null) (Syntax: 'prop.field: null') - Member: - IFieldReferenceOperation: C C.field (OperationKind.FieldReference, Type: C) (Syntax: 'prop.field') - Instance Receiver: - IPropertyReferenceOperation: C C.prop { get; } (OperationKind.PropertyReference, Type: C) (Syntax: 'prop') - Instance Receiver: - IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'prop') - Pattern: - IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: C, NarrowedType: C) - Value: - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: C, Constant: null, IsImplicit) (Syntax: 'null') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) - (ImplicitReference) - Operand: - ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'prop') + Member: + IPropertyReferenceOperation: C C.prop { get; } (OperationKind.PropertyReference, Type: C) (Syntax: 'prop') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'prop') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'prop') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'prop.field') + Member: + IFieldReferenceOperation: C C.field (OperationKind.FieldReference, Type: C) (Syntax: 'prop.field') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'prop.field') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: C, NarrowedType: C) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: C, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') Next (Regular) Block[B2] Block[B2] - Exit Predecessors: [B1*2] @@ -1116,9 +1130,9 @@ void M() "; var expectedDiagnostics = new DiagnosticDescription[] { - // file.cs(4,7): warning CS0169: The field 'C.field' is never used - // C field; - Diagnostic(ErrorCode.WRN_UnreferencedField, "field").WithArguments("C.field").WithLocation(4, 7) + // file.cs(4,7): warning CS0649: Field 'C.field' is never assigned to, and will always have its default value null + // C field; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "field").WithArguments("C.field", "null").WithLocation(4, 7) }; VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); @@ -1137,27 +1151,33 @@ void M() } } "; - string expectedOperationTree = @" + string expectedOperationTree = @" IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'this is { p ... d: null } y') - Value: + Value: IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') - Pattern: + Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsInvalid) (Syntax: '{ prop.field: null } y') (InputType: C, NarrowedType: C, DeclaredSymbol: C y, MatchedType: C, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): - IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid) (Syntax: 'prop.field: null') - Member: - IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'prop.field') - Children(1): - IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'prop') - Children(0) - Pattern: - IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: ?, NarrowedType: ?) - Value: - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, Constant: null, IsImplicit) (Syntax: 'null') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) - Operand: - ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid, IsImplicit) (Syntax: 'prop') + Member: + IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'prop') + Children(0) + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsInvalid, IsImplicit) (Syntax: 'prop') (InputType: ?, NarrowedType: ?, DeclaredSymbol: null, MatchedType: ?, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid, IsImplicit) (Syntax: 'prop.field') + Member: + IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'prop.field') + Children(0) + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: ?, NarrowedType: ?) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') "; var expectedDiagnostics = new DiagnosticDescription[] { // file.cs(6,33): error CS0117: 'C' does not contain a definition for 'prop' @@ -1170,7 +1190,7 @@ void M() [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Patterns)] [Fact] - public void IsPattern_BadRecursivePatternWithNestedPropertyPatterns_EventMember() + public void IsPattern_RecursivePatternWithNestedPropertyPatterns_EventMember() { string source = @" class C @@ -1183,30 +1203,36 @@ void M() } } "; - string expectedOperationTree = @" + string expectedOperationTree = @" IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'this is { p ... n: null } y') - Value: + Value: IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') - Pattern: + Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsInvalid) (Syntax: '{ prop.action: null } y') (InputType: C, NarrowedType: C, DeclaredSymbol: C y, MatchedType: C, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (1): - IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid) (Syntax: 'prop.action: null') - Member: - IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'prop.action') - Children(1): - IPropertyReferenceOperation: C C.prop { get; } (OperationKind.PropertyReference, Type: C) (Syntax: 'prop') - Instance Receiver: - IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'prop') - Pattern: - IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: ?, NarrowedType: ?) - Value: - IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, Constant: null, IsImplicit) (Syntax: 'null') - Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) - Operand: - ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') -"; - var expectedDiagnostics = new DiagnosticDescription[] { + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'prop') + Member: + IPropertyReferenceOperation: C C.prop { get; } (OperationKind.PropertyReference, Type: C) (Syntax: 'prop') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'prop') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'prop') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid, IsImplicit) (Syntax: 'prop.action') + Member: + IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'prop.action') + Children(0) + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: ?, NarrowedType: ?) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') +"; + var expectedDiagnostics = new DiagnosticDescription[] { // file.cs(5,32): warning CS0067: The event 'C.action' is never used // public event System.Action action; Diagnostic(ErrorCode.WRN_UnreferencedEvent, "action").WithArguments("C.action").WithLocation(5, 32), @@ -1311,23 +1337,23 @@ void M1(object o, bool b) var expectedOperationTree = @" IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'o is C1 { P ... 1]: var x }') - Value: + Value: IParameterReferenceOperation: o (OperationKind.ParameterReference, Type: System.Object) (Syntax: 'o') - Pattern: + Pattern: IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsInvalid) (Syntax: 'C1 { Prop[1]: var x }') (InputType: System.Object, NarrowedType: C1, DeclaredSymbol: null, MatchedType: C1, DeconstructSymbol: null) DeconstructionSubpatterns (0) PropertySubpatterns (2): IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid) (Syntax: 'Prop[1]') - Member: + Member: IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid, IsImplicit) (Syntax: 'Prop[1]') Children(0) - Pattern: + Pattern: ITypePatternOperation (OperationKind.TypePattern, Type: null, IsInvalid) (Syntax: 'Prop[1]') (InputType: ?, NarrowedType: Prop[], MatchedType: Prop[]) IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid) (Syntax: 'var x') - Member: + Member: IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid, IsImplicit) (Syntax: 'var x') Children(0) - Pattern: + Pattern: IDeclarationPatternOperation (OperationKind.DeclarationPattern, Type: null, IsInvalid) (Syntax: 'var x') (InputType: ?, NarrowedType: ?, DeclaredSymbol: ?? x, MatchesNull: True) "; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index ac9c9a9a40547..fa5bf902c6c58 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -75976,6 +75976,39 @@ static void F(E e) Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "e").WithLocation(11, 29)); } + [Theory] + [InlineData("null")] + [InlineData("not null")] + [InlineData("{}")] + public void OtherComparisonsAsPureNullTests_ExtendedProperties_PureNullTest(string pureTest) + { + var source = +$@"#nullable enable +class E +{{ + public E Property1 {{ get; set; }} = null!; + public object Property2 {{ get; set; }} = null!; +}} +class Program +{{ + static void F(E e) + {{ + switch (e) + {{ + case var x when e.Property1.Property2.ToString() == null: // 1 + break; + case {{ Property1.Property2: {pureTest} }}: + break; + }} + }} +}}"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + comp.VerifyDiagnostics( + // (13,29): warning CS8602: Dereference of a possibly null reference. + // case var x when e.Property1.Property2.ToString() == null: // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "e.Property1.Property2").WithLocation(13, 29)); + } + [Fact, WorkItem(33526, "https://github.com/dotnet/roslyn/issues/33526")] public void OtherComparisonsAreNotPureTest() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests5.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests5.cs index e4b0396cb364a..71f31d52c9ae9 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests5.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests5.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.UnitTests @@ -24,14 +25,14 @@ class C public C Prop1 { get; set; } public C Prop2 { get; set; } public C Prop3 { get; set; } - + static bool Test1(C o) => o is { Prop1.Prop2.Prop3: null }; static bool Test2(S o) => o is { Prop1.Prop2.Prop3: null }; static bool Test3(S? o) => o is { Prop1.Prop2.Prop3: null }; static bool Test4(S0 o) => o is { Prop1.Prop2.Prop3: 420 }; public static void Main() - { + { Console.WriteLine(Test1(new() { Prop1 = new() { Prop2 = new() { Prop3 = null }}})); Console.WriteLine(Test2(new() { Prop1 = new() { Prop2 = new() { Prop3 = null }}})); Console.WriteLine(Test3(new() { Prop1 = new() { Prop2 = new() { Prop3 = null }}})); @@ -161,7 +162,7 @@ class C public C Prop2 { get; set; } public static void Main() - { + { _ = new C() is { Prop1: null } and { Prop1.Prop2: null }; _ = new C() is { Prop1: null, Prop1.Prop2: null }; } @@ -200,7 +201,7 @@ C Prop2 } public static void Main() - { + { Test(null); Test(new()); Test(new() { Prop1 = new() }); @@ -276,7 +277,7 @@ public void ExtendedPropertyPatterns_04() class C { public static void Main() - { + { _ = new C() is { Prop1.Prop2: {} }; _ = new C() is { Prop1->Prop2: {} }; _ = new C() is { Prop1!.Prop2: {} }; @@ -288,16 +289,16 @@ public static void Main() "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns, options: TestOptions.ReleaseExe); compilation.VerifyDiagnostics( - // (6,26): error CS9000: Identifier or a simple member access expected. + // (6,26): error CS8918: Identifier or a simple member access expected. // _ = new C() is { Prop1.Prop2: {} }; Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "Prop1").WithLocation(6, 26), - // (7,26): error CS9000: Identifier or a simple member access expected. + // (7,26): error CS8918: Identifier or a simple member access expected. // _ = new C() is { Prop1->Prop2: {} }; Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "Prop1->Prop2").WithLocation(7, 26), - // (8,26): error CS9000: Identifier or a simple member access expected. + // (8,26): error CS8918: Identifier or a simple member access expected. // _ = new C() is { Prop1!.Prop2: {} }; Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "Prop1!").WithLocation(8, 26), - // (9,26): error CS9000: Identifier or a simple member access expected. + // (9,26): error CS8918: Identifier or a simple member access expected. // _ = new C() is { Prop1?.Prop2: {} }; Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "Prop1?.Prop2").WithLocation(9, 26), // (10,26): error CS8503: A property subpattern requires a reference to the property or field to be matched, e.g. '{ Name: Prop1[0] }' @@ -315,50 +316,671 @@ public static void Main() // (10,36): error CS1003: Syntax error, ',' expected // _ = new C() is { Prop1[0]: {} }; Diagnostic(ErrorCode.ERR_SyntaxError, "{").WithArguments(",", "{").WithLocation(10, 36), - // (11,26): error CS9000: Identifier or a simple member access expected. + // (11,26): error CS8918: Identifier or a simple member access expected. // _ = new C() is { 1: {} }; Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "1").WithLocation(11, 26)); } - [Fact] + [Fact, WorkItem(52956, "https://github.com/dotnet/roslyn/issues/52956")] public void ExtendedPropertyPatterns_05() { var program = @" class C { - C Prop1, Prop2; + C Field1, Field2, Field3, Field4; + public void M() + { + _ = this is { Field1.Field2.Field3: {} }; + _ = this is { Field4: {} }; + } +} +"; + var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + compilation.VerifyEmitDiagnostics( + // (4,7): warning CS0649: Field 'C.Field1' is never assigned to, and will always have its default value null + // C Field1, Field2, Field3, Field4; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field1").WithArguments("C.Field1", "null").WithLocation(4, 7), + // (4,15): warning CS0649: Field 'C.Field2' is never assigned to, and will always have its default value null + // C Field1, Field2, Field3, Field4; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field2").WithArguments("C.Field2", "null").WithLocation(4, 15), + // (4,23): warning CS0649: Field 'C.Field3' is never assigned to, and will always have its default value null + // C Field1, Field2, Field3, Field4; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field3").WithArguments("C.Field3", "null").WithLocation(4, 23), + // (4,31): warning CS0649: Field 'C.Field4' is never assigned to, and will always have its default value null + // C Field1, Field2, Field3, Field4; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field4").WithArguments("C.Field4", "null").WithLocation(4, 31) + ); + } + + [Fact, WorkItem(52956, "https://github.com/dotnet/roslyn/issues/52956")] + public void ExtendedPropertyPatterns_05_NestedRecursivePattern() + { + var program = @" +class C +{ + C Field1, Field2, Field3, Field4; + public void M() + { + _ = this is { Field1: { Field2.Field3.Field4: not null } }; + } +} +"; + var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + compilation.VerifyEmitDiagnostics( + // (4,7): warning CS0649: Field 'C.Field1' is never assigned to, and will always have its default value null + // C Field1, Field2, Field3, Field4; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field1").WithArguments("C.Field1", "null").WithLocation(4, 7), + // (4,15): warning CS0649: Field 'C.Field2' is never assigned to, and will always have its default value null + // C Field1, Field2, Field3, Field4; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field2").WithArguments("C.Field2", "null").WithLocation(4, 15), + // (4,23): warning CS0649: Field 'C.Field3' is never assigned to, and will always have its default value null + // C Field1, Field2, Field3, Field4; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field3").WithArguments("C.Field3", "null").WithLocation(4, 23), + // (4,31): warning CS0649: Field 'C.Field4' is never assigned to, and will always have its default value null + // C Field1, Field2, Field3, Field4; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field4").WithArguments("C.Field4", "null").WithLocation(4, 31) + ); + } + + [Fact, WorkItem(52956, "https://github.com/dotnet/roslyn/issues/52956")] + public void ExtendedPropertyPatterns_05_Properties() + { + var program = @" +class C +{ + C Prop1 { get; set; } + C Prop2 { get; set; } + C Prop3 { get; set; } + C Prop4 { get; set; } + public void M() + { + _ = this is { Prop1.Prop2.Prop3: {} }; + _ = this is { Prop4: {} }; + } +} +"; + var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + compilation.VerifyEmitDiagnostics(); + } + + [Fact] + public void ExtendedPropertyPatterns_IOperation_Properties() + { + var src = @" +class Program +{ + static void M(A a) + /**/{ + _ = a is { Prop1.Prop2.Prop3: null }; + }/**/ +} +class A { public B Prop1 => null; } +class B { public C Prop2 => null; } +class C { public object Prop3 => null; } +"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var isPattern = tree.GetRoot().DescendantNodes().OfType().Single(); + + VerifyOperationTree(comp, model.GetOperation(isPattern), @" +IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'a is { Prop ... op3: null }') + Value: + IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: A) (Syntax: 'a') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null) (Syntax: '{ Prop1.Pro ... op3: null }') (InputType: A, NarrowedType: A, DeclaredSymbol: null, MatchedType: A, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Prop1') + Member: + IPropertyReferenceOperation: B A.Prop1 { get; } (OperationKind.PropertyReference, Type: B) (Syntax: 'Prop1') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: A, IsImplicit) (Syntax: 'Prop1') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Prop1') (InputType: B, NarrowedType: B, DeclaredSymbol: null, MatchedType: B, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Prop1.Prop2') + Member: + IPropertyReferenceOperation: C B.Prop2 { get; } (OperationKind.PropertyReference, Type: C) (Syntax: 'Prop1.Prop2') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'Prop1.Prop2') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Prop1.Prop2') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Prop1.Prop2.Prop3') + Member: + IPropertyReferenceOperation: System.Object C.Prop3 { get; } (OperationKind.PropertyReference, Type: System.Object) (Syntax: 'Prop1.Prop2.Prop3') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'Prop1.Prop2.Prop3') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: System.Object, NarrowedType: System.Object) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') +"); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '_ = a is { ... p3: null };') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean) (Syntax: '_ = a is { ... op3: null }') + Left: + IDiscardOperation (Symbol: System.Boolean _) (OperationKind.Discard, Type: System.Boolean) (Syntax: '_') + Right: + IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'a is { Prop ... op3: null }') + Value: + IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: A) (Syntax: 'a') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null) (Syntax: '{ Prop1.Pro ... op3: null }') (InputType: A, NarrowedType: A, DeclaredSymbol: null, MatchedType: A, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Prop1') + Member: + IPropertyReferenceOperation: B A.Prop1 { get; } (OperationKind.PropertyReference, Type: B) (Syntax: 'Prop1') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: A, IsImplicit) (Syntax: 'Prop1') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Prop1') (InputType: B, NarrowedType: B, DeclaredSymbol: null, MatchedType: B, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Prop1.Prop2') + Member: + IPropertyReferenceOperation: C B.Prop2 { get; } (OperationKind.PropertyReference, Type: C) (Syntax: 'Prop1.Prop2') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'Prop1.Prop2') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Prop1.Prop2') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Prop1.Prop2.Prop3') + Member: + IPropertyReferenceOperation: System.Object C.Prop3 { get; } (OperationKind.PropertyReference, Type: System.Object) (Syntax: 'Prop1.Prop2.Prop3') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'Prop1.Prop2.Prop3') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: System.Object, NarrowedType: System.Object) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') + Next (Regular) Block[B2] +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, DiagnosticDescription.None, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void ExtendedPropertyPatterns_IOperation_FieldsInStructs() + { + var src = @" +class Program +{ + static void M(A a) + /**/{ + _ = a is { Field1.Field2.Field3: null, Field4: null }; + }/**/ +} +struct A { public B? Field1; public B? Field4; } +struct B { public C? Field2; } +struct C { public object Field3; } +"; + var expectedDiagnostics = new[] + { + // (9,22): warning CS0649: Field 'A.Field1' is never assigned to, and will always have its default value + // struct A { public B? Field1; public B? Field4; } + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field1").WithArguments("A.Field1", "").WithLocation(9, 22), + // (9,40): warning CS0649: Field 'A.Field4' is never assigned to, and will always have its default value + // struct A { public B? Field1; public B? Field4; } + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field4").WithArguments("A.Field4", "").WithLocation(9, 40), + // (10,22): warning CS0649: Field 'B.Field2' is never assigned to, and will always have its default value + // struct B { public C? Field2; } + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field2").WithArguments("B.Field2", "").WithLocation(10, 22), + // (11,26): warning CS0649: Field 'C.Field3' is never assigned to, and will always have its default value null + // struct C { public object Field3; } + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field3").WithArguments("C.Field3", "null").WithLocation(11, 26) + }; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + comp.VerifyDiagnostics(expectedDiagnostics); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var isPattern = tree.GetRoot().DescendantNodes().OfType().Single(); + + VerifyOperationTree(comp, model.GetOperation(isPattern), @" +IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'a is { Fiel ... ld4: null }') + Value: + IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: A) (Syntax: 'a') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null) (Syntax: '{ Field1.Fi ... ld4: null }') (InputType: A, NarrowedType: A, DeclaredSymbol: null, MatchedType: A, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (2): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Field1') + Member: + IFieldReferenceOperation: B? A.Field1 (OperationKind.FieldReference, Type: B?) (Syntax: 'Field1') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: A, IsImplicit) (Syntax: 'Field1') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Field1') (InputType: B, NarrowedType: B, DeclaredSymbol: null, MatchedType: B, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Field1.Field2') + Member: + IFieldReferenceOperation: C? B.Field2 (OperationKind.FieldReference, Type: C?) (Syntax: 'Field1.Field2') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'Field1.Field2') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Field1.Field2') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Field1.Field2.Field3') + Member: + IFieldReferenceOperation: System.Object C.Field3 (OperationKind.FieldReference, Type: System.Object) (Syntax: 'Field1.Field2.Field3') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'Field1.Field2.Field3') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: System.Object, NarrowedType: System.Object) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null) (Syntax: 'Field4: null') + Member: + IFieldReferenceOperation: B? A.Field4 (OperationKind.FieldReference, Type: B?) (Syntax: 'Field4') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: A, IsImplicit) (Syntax: 'Field4') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: B?, NarrowedType: B?) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: B?, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') +"); + + var expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '_ = a is { ... d4: null };') + Expression: + ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.Boolean) (Syntax: '_ = a is { ... ld4: null }') + Left: + IDiscardOperation (Symbol: System.Boolean _) (OperationKind.Discard, Type: System.Boolean) (Syntax: '_') + Right: + IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean) (Syntax: 'a is { Fiel ... ld4: null }') + Value: + IParameterReferenceOperation: a (OperationKind.ParameterReference, Type: A) (Syntax: 'a') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null) (Syntax: '{ Field1.Fi ... ld4: null }') (InputType: A, NarrowedType: A, DeclaredSymbol: null, MatchedType: A, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (2): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Field1') + Member: + IFieldReferenceOperation: B? A.Field1 (OperationKind.FieldReference, Type: B?) (Syntax: 'Field1') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: A, IsImplicit) (Syntax: 'Field1') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Field1') (InputType: B, NarrowedType: B, DeclaredSymbol: null, MatchedType: B, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Field1.Field2') + Member: + IFieldReferenceOperation: C? B.Field2 (OperationKind.FieldReference, Type: C?) (Syntax: 'Field1.Field2') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: B, IsImplicit) (Syntax: 'Field1.Field2') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Field1.Field2') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Field1.Field2.Field3') + Member: + IFieldReferenceOperation: System.Object C.Field3 (OperationKind.FieldReference, Type: System.Object) (Syntax: 'Field1.Field2.Field3') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'Field1.Field2.Field3') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: System.Object, NarrowedType: System.Object) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.Object, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + (ImplicitReference) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null) (Syntax: 'Field4: null') + Member: + IFieldReferenceOperation: B? A.Field4 (OperationKind.FieldReference, Type: B?) (Syntax: 'Field4') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: A, IsImplicit) (Syntax: 'Field4') + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: B?, NarrowedType: B?) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: B?, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + (NullLiteral) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') + Next (Regular) Block[B2] +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +"; + + VerifyFlowGraphAndDiagnosticsForTest(src, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [Fact] + public void ExtendedPropertyPatterns_Explainer() + { + var src = @" +class Program +{ + void M(A a) + { + _ = a switch // 1 + { + { BProp.BoolProp: true } => 1 + }; + + _ = a switch // 2 + { + { BProp.IntProp: <= 0 } => 1 + }; + } +} +class A { public B BProp => null; } +class B +{ + public bool BoolProp => true; + public int IntProp => 0; +} +"; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + comp.VerifyDiagnostics( + // (6,15): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ BProp: { BoolProp: false } }' is not covered. + // _ = a switch // 1 + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ BProp: { BoolProp: false } }").WithLocation(6, 15), + // (11,15): warning CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '{ BProp: { IntProp: 1 } }' is not covered. + // _ = a switch // 2 + Diagnostic(ErrorCode.WRN_SwitchExpressionNotExhaustive, "switch").WithArguments("{ BProp: { IntProp: 1 } }").WithLocation(11, 15) + ); + } + + [Fact, WorkItem(52956, "https://github.com/dotnet/roslyn/issues/52956")] + public void ExtendedPropertyPatterns_BadMemberAccess() + { + var program = @" +class C +{ + C Field1, Field2, Field3; public static void Main() - { - _ = new C() is { Prop1.Prop2: {} }; - _ = new C() is { Prop1?.Prop2: {} }; - _ = new C() is { Missing: null, Prop1.Prop2: {} }; + { + _ = new C() is { Field1?.Field2: {} }; // 1 + _ = new C() is { Field1!.Field2: {} }; // 2 + _ = new C() is { Missing: null }; // 3 + _ = new C() is { Field3.Missing: {} }; // 4 + _ = new C() is { Missing1.Missing2: {} }; // 5 } } "; - var compilation = CreateCompilation(program, parseOptions: TestOptions.Regular9, options: TestOptions.ReleaseExe); - // PROTOTYPE(extended-property-patterns) False warning: https://github.com/dotnet/roslyn/issues/52956 - compilation.VerifyDiagnostics( - // (4,7): warning CS0169: The field 'C.Prop1' is never used - // C Prop1, Prop2; - Diagnostic(ErrorCode.WRN_UnreferencedField, "Prop1").WithArguments("C.Prop1").WithLocation(4, 7), - // (4,14): warning CS0169: The field 'C.Prop2' is never used - // C Prop1, Prop2; - Diagnostic(ErrorCode.WRN_UnreferencedField, "Prop2").WithArguments("C.Prop2").WithLocation(4, 14), - // (7,26): error CS8652: The feature 'extended property patterns' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. - // _ = new C() is { Prop1.Prop2: {} }; - Diagnostic(ErrorCode.ERR_FeatureInPreview, "Prop1.Prop2").WithArguments("extended property patterns").WithLocation(7, 26), - // (8,26): error CS8652: The feature 'extended property patterns' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. - // _ = new C() is { Prop1?.Prop2: {} }; - Diagnostic(ErrorCode.ERR_FeatureInPreview, "Prop1?.Prop2").WithArguments("extended property patterns").WithLocation(8, 26), - // (8,26): error CS9000: Identifier or a simple member access expected. - // _ = new C() is { Prop1?.Prop2: {} }; - Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "Prop1?.Prop2").WithLocation(8, 26), - // (9,26): error CS0117: 'C' does not contain a definition for 'Missing' - // _ = new C() is { Missing: null, Prop1.Prop2: {} }; - Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing").WithArguments("C", "Missing").WithLocation(9, 26), - // (9,41): error CS8652: The feature 'extended property patterns' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. - // _ = new C() is { Missing: null, Prop1.Prop2: {} }; - Diagnostic(ErrorCode.ERR_FeatureInPreview, "Prop1.Prop2").WithArguments("extended property patterns").WithLocation(9, 41)); + var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + compilation.VerifyEmitDiagnostics( + // (4,7): warning CS0169: The field 'C.Field1' is never used + // C Field1, Field2, Field3; + Diagnostic(ErrorCode.WRN_UnreferencedField, "Field1").WithArguments("C.Field1").WithLocation(4, 7), + // (4,15): warning CS0169: The field 'C.Field2' is never used + // C Field1, Field2, Field3; + Diagnostic(ErrorCode.WRN_UnreferencedField, "Field2").WithArguments("C.Field2").WithLocation(4, 15), + // (4,23): warning CS0649: Field 'C.Field3' is never assigned to, and will always have its default value null + // C Field1, Field2, Field3; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Field3").WithArguments("C.Field3", "null").WithLocation(4, 23), + // (7,26): error CS8918: Identifier or a simple member access expected. + // _ = new C() is { Field1?.Field2: {} }; // 1 + Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "Field1?.Field2").WithLocation(7, 26), + // (8,26): error CS8918: Identifier or a simple member access expected. + // _ = new C() is { Field1!.Field2: {} }; // 2 + Diagnostic(ErrorCode.ERR_InvalidNameInSubpattern, "Field1!").WithLocation(8, 26), + // (9,26): error CS0117: 'C' does not contain a definition for 'Missing' + // _ = new C() is { Missing: null }; // 3 + Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing").WithArguments("C", "Missing").WithLocation(9, 26), + // (10,33): error CS0117: 'C' does not contain a definition for 'Missing' + // _ = new C() is { Field3.Missing: {} }; // 4 + Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing").WithArguments("C", "Missing").WithLocation(10, 33), + // (11,26): error CS0117: 'C' does not contain a definition for 'Missing1' + // _ = new C() is { Missing1.Missing2: {} }; // 5 + Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing1").WithArguments("C", "Missing1").WithLocation(11, 26) + ); + } + + [Fact] + public void ExtendedPropertyPatterns_IOperationOnMissing() + { + var program = @" +class C +{ + public void M() + { + _ = this is { Missing: null }; + } +} +"; + var comp = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + comp.VerifyEmitDiagnostics( + // (6,23): error CS0117: 'C' does not contain a definition for 'Missing' + // _ = this is { Missing: null }; + Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing").WithArguments("C", "Missing").WithLocation(6, 23) + ); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var isPattern = tree.GetRoot().DescendantNodes().OfType().Single(); + + VerifyOperationTree(comp, model.GetOperation(isPattern), @" +IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'this is { M ... ing: null }') + Value: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsInvalid) (Syntax: '{ Missing: null }') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid) (Syntax: 'Missing: null') + Member: + IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'Missing') + Children(0) + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: ?, NarrowedType: ?) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') +"); + } + + [Fact] + public void ExtendedPropertyPatterns_IOperationOnNestedMissing() + { + var program = @" +class C +{ + int Property { get; set; } + public void M() + { + _ = this is { Property.Missing: null }; + } +} +"; + var comp = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + comp.VerifyEmitDiagnostics( + // (7,32): error CS0117: 'int' does not contain a definition for 'Missing' + // _ = this is { Property.Missing: null }; + Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing").WithArguments("int", "Missing").WithLocation(7, 32) + ); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var isPattern = tree.GetRoot().DescendantNodes().OfType().Single(); + + VerifyOperationTree(comp, model.GetOperation(isPattern), @" +IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'this is { P ... ing: null }') + Value: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsInvalid) (Syntax: '{ Property. ... ing: null }') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsImplicit) (Syntax: 'Property') + Member: + IPropertyReferenceOperation: System.Int32 C.Property { get; set; } (OperationKind.PropertyReference, Type: System.Int32) (Syntax: 'Property') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: PatternInput) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'Property') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsImplicit) (Syntax: 'Property') (InputType: System.Int32, NarrowedType: System.Int32, DeclaredSymbol: null, MatchedType: System.Int32, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid, IsImplicit) (Syntax: 'Property.Missing') + Member: + IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'Property.Missing') + Children(0) + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: ?, NarrowedType: ?) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') +"); + } + + [Fact] + public void ExtendedPropertyPatterns_IOperationOnTwoMissing() + { + var program = @" +class C +{ + public void M() + { + _ = this is { Missing1.Missing2: null }; + } +} +"; + var comp = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + comp.VerifyEmitDiagnostics( + // (6,23): error CS0117: 'C' does not contain a definition for 'Missing1' + // _ = this is { Missing1.Missing2: null }; + Diagnostic(ErrorCode.ERR_NoSuchMember, "Missing1").WithArguments("C", "Missing1").WithLocation(6, 23) + ); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree, ignoreAccessibility: false); + var isPattern = tree.GetRoot().DescendantNodes().OfType().Single(); + + VerifyOperationTree(comp, model.GetOperation(isPattern), @" +IIsPatternOperation (OperationKind.IsPattern, Type: System.Boolean, IsInvalid) (Syntax: 'this is { M ... ng2: null }') + Value: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C) (Syntax: 'this') + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsInvalid) (Syntax: '{ Missing1. ... ng2: null }') (InputType: C, NarrowedType: C, DeclaredSymbol: null, MatchedType: C, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid, IsImplicit) (Syntax: 'Missing1') + Member: + IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'Missing1') + Children(0) + Pattern: + IRecursivePatternOperation (OperationKind.RecursivePattern, Type: null, IsInvalid, IsImplicit) (Syntax: 'Missing1') (InputType: ?, NarrowedType: ?, DeclaredSymbol: null, MatchedType: ?, DeconstructSymbol: null) + DeconstructionSubpatterns (0) + PropertySubpatterns (1): + IPropertySubpatternOperation (OperationKind.PropertySubpattern, Type: null, IsInvalid, IsImplicit) (Syntax: 'Missing1.Missing2') + Member: + IInvalidOperation (OperationKind.Invalid, Type: null, IsInvalid) (Syntax: 'Missing1.Missing2') + Children(0) + Pattern: + IConstantPatternOperation (OperationKind.ConstantPattern, Type: null) (Syntax: 'null') (InputType: ?, NarrowedType: ?) + Value: + IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: ?, Constant: null, IsImplicit) (Syntax: 'null') + Conversion: CommonConversion (Exists: True, IsIdentity: False, IsNumeric: False, IsReference: True, IsUserDefined: False) (MethodSymbol: null) + Operand: + ILiteralOperation (OperationKind.Literal, Type: null, Constant: null) (Syntax: 'null') +"); + } + + [Fact, WorkItem(53484, "https://github.com/dotnet/roslyn/issues/53484")] + public void ExtendedPropertyPatterns_SuppressionOnPattern() + { + var program = @" +#nullable enable +public class ContainerType +{ + public class Type + { + public void M() + { + const Type c = null!; + if (this is c!) {} + if (this is (c!)) {} + if (this is Type!) {} // 1 + if (this is ContainerType!.Type) {} // 2 + if (this is ContainerType.Type!) {} // 3 + } + } +} +"; + var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + compilation.VerifyEmitDiagnostics( + // (12,25): error CS8598: The suppression operator is not allowed in this context + // if (this is Type!) {} // 1 + Diagnostic(ErrorCode.ERR_IllegalSuppression, "Type!").WithLocation(12, 25), + // (13,25): error CS8598: The suppression operator is not allowed in this context + // if (this is ContainerType!.Type) {} // 2 + Diagnostic(ErrorCode.ERR_IllegalSuppression, "ContainerType").WithLocation(13, 25), + // (14,25): error CS8598: The suppression operator is not allowed in this context + // if (this is ContainerType.Type!) {} // 3 + Diagnostic(ErrorCode.ERR_IllegalSuppression, "ContainerType.Type!").WithLocation(14, 25) + ); + } + + [Fact, WorkItem(53484, "https://github.com/dotnet/roslyn/issues/53484")] + public void ExtendedPropertyPatterns_PointerAccessInPattern() + { + var program = @" +public class Type +{ + public unsafe void M(S* s) + { + if (0 is s->X) {} + } +} + +public struct S +{ + public int X; +} +"; + var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns, options: TestOptions.UnsafeDebugDll); + compilation.VerifyEmitDiagnostics( + // (6,18): error CS0150: A constant value is expected + // if (0 is s->X) {} + Diagnostic(ErrorCode.ERR_ConstantExpected, "s->X").WithLocation(6, 18) + ); } [Fact] @@ -382,7 +1004,7 @@ class P } "; var compilation = CreatePatternCompilation(source); - compilation.VerifyDiagnostics( + compilation.VerifyEmitDiagnostics( // (14,14): warning CS0649: Field 'P.Y' is never assigned to, and will always have its default value null // public P Y; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Y").WithArguments("P.Y", "null").WithLocation(14, 14) @@ -459,7 +1081,7 @@ interface P : I1, I2 } "; var compilation = CreatePatternCompilation(source); - compilation.VerifyDiagnostics( + compilation.VerifyEmitDiagnostics( // (8,34): error CS0229: Ambiguity between 'I1.X' and 'I2.X' // Console.WriteLine(p is { X.Y: {}, Y.X: {}, }); Diagnostic(ErrorCode.ERR_AmbigMember, "X").WithArguments("I1.X", "I2.X").WithLocation(8, 34), @@ -513,7 +1135,7 @@ class P } "; var compilation = CreatePatternCompilation(source); - compilation.VerifyDiagnostics( + compilation.VerifyEmitDiagnostics( // (8,34): error CS0117: 'P' does not contain a definition for 'X' // Console.WriteLine(p is { X: 3, Y: 4 }); Diagnostic(ErrorCode.ERR_NoSuchMember, "X").WithArguments("P", "X").WithLocation(8, 34) @@ -565,7 +1187,7 @@ struct S } "; var compilation = CreatePatternCompilation(source); - compilation.VerifyDiagnostics( + compilation.VerifyEmitDiagnostics( // (17,14): warning CS0649: Field 'S.Y' is never assigned to, and will always have its default value null // public C Y; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Y").WithArguments("S.Y", "null").WithLocation(17, 14) @@ -628,34 +1250,82 @@ struct S var xNameType = model.GetTypeInfo(xName); Assert.Equal("S?", xNameType.Type.ToTestDisplayString()); Assert.Equal("S?", xNameType.ConvertedType.ToTestDisplayString()); + + var verifier = CompileAndVerify(compilation); + verifier.VerifyIL("Program.Main", @" +{ + // Code size 92 (0x5c) + .maxstack 2 + .locals init (C V_0, + S? V_1, + S V_2) + IL_0000: nop + IL_0001: newobj ""C..ctor()"" + IL_0006: stloc.0 + IL_0007: ldloc.0 + IL_0008: brfalse.s IL_002b + IL_000a: ldloc.0 + IL_000b: callvirt ""S? C.X.get"" + IL_0010: stloc.1 + IL_0011: ldloca.s V_1 + IL_0013: call ""bool S?.HasValue.get"" + IL_0018: brfalse.s IL_002b + IL_001a: ldloca.s V_1 + IL_001c: call ""S S?.GetValueOrDefault()"" + IL_0021: ldfld ""C S.Y"" + IL_0026: ldnull + IL_0027: cgt.un + IL_0029: br.s IL_002c + IL_002b: ldc.i4.0 + IL_002c: call ""void System.Console.WriteLine(bool)"" + IL_0031: nop + IL_0032: ldloca.s V_2 + IL_0034: initobj ""S"" + IL_003a: ldloc.2 + IL_003b: ldfld ""C S.Y"" + IL_0040: stloc.0 + IL_0041: ldloc.0 + IL_0042: brfalse.s IL_0054 + IL_0044: ldloc.0 + IL_0045: callvirt ""S? C.X.get"" + IL_004a: stloc.1 + IL_004b: ldloca.s V_1 + IL_004d: call ""bool S?.HasValue.get"" + IL_0052: br.s IL_0055 + IL_0054: ldc.i4.0 + IL_0055: call ""void System.Console.WriteLine(bool)"" + IL_005a: nop + IL_005b: ret +} +"); } [Fact] - public void ExtendedPropertyPatterns_Nullability_01() + public void ExtendedPropertyPatterns_Nullability_Properties() { var program = @" #nullable enable class C { C? Prop { get; } public void M() { - if (this is { Prop.Prop: null }) + if (this is { Prop.Prop: null }) { this.Prop.ToString(); this.Prop.Prop.ToString(); // 1 } - if (this is { Prop.Prop: {} }) + if (this is { Prop.Prop: {} }) { this.Prop.ToString(); this.Prop.Prop.ToString(); } - if (this is { Prop: null } && - this is { Prop.Prop: null }) + if (this is { Prop: null } && + this is { Prop.Prop: null }) { this.Prop.ToString(); this.Prop.Prop.ToString(); // 2 } - if (this is { Prop: null } || - this is { Prop.Prop: null }) + if (this is { Prop: null } || + this is { Prop.Prop: null }) { this.Prop.ToString(); // 3 this.Prop.Prop.ToString(); // 4 @@ -664,7 +1334,7 @@ public void M() { } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); - compilation.VerifyDiagnostics( + compilation.VerifyEmitDiagnostics( // (9,13): warning CS8602: Dereference of a possibly null reference. // this.Prop.Prop.ToString(); // 1 Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "this.Prop.Prop").WithLocation(9, 13), @@ -680,59 +1350,401 @@ public void M() { } [Fact] - public void ExtendedPropertyPatterns_Nullability_02() + public void ExtendedPropertyPatterns_Nullability_AnnotatedFields() { var program = @" #nullable enable class C { public void M(C1 c1) { - if (c1 is { Prop.Prop: null }) + if (c1 is { Prop1.Prop2: null }) { - c1.Prop.ToString(); - c1.Prop.Prop.ToString(); // 1 + c1.Prop1.ToString(); + c1.Prop1.Prop2.ToString(); // 1 } - if (c1 is { Prop.Prop: {} }) + if (c1 is { Prop1.Prop2: {} }) { - c1.Prop.ToString(); - c1.Prop.Prop.ToString(); + c1.Prop1.ToString(); + c1.Prop1.Prop2.ToString(); } - if (c1 is { Prop: null } && - c1 is { Prop.Prop: null }) + if (c1 is { Prop1: null } && + c1 is { Prop1.Prop2: null }) { - c1.Prop.ToString(); - c1.Prop.Prop.ToString(); // 2 + c1.Prop1.ToString(); + c1.Prop1.Prop2.ToString(); // 2 } - if (c1 is { Prop: null } || - c1 is { Prop.Prop: null }) + if (c1 is { Prop1: null } || + c1 is { Prop1.Prop2: null }) { - c1.Prop.ToString(); // 3 - c1.Prop.Prop.ToString(); // 4 + c1.Prop1.ToString(); // 3 + c1.Prop1.Prop2.ToString(); // 4 } } } -class C1 { public C2? Prop; } -class C2 { public object? Prop; } +class C1 { public C2? Prop1 = null; } +class C2 { public object? Prop2 = null; } "; var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); - compilation.VerifyDiagnostics( - // (8,13): warning CS8602: Dereference of a possibly null reference. - // c1.Prop.Prop.ToString(); // 1 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop.Prop").WithLocation(8, 13), - // (19,13): warning CS8602: Dereference of a possibly null reference. - // c1.Prop.Prop.ToString(); // 2 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop.Prop").WithLocation(19, 13), - // (24,13): warning CS8602: Dereference of a possibly null reference. - // c1.Prop.ToString(); // 3 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop").WithLocation(24, 13), - // (25,13): warning CS8602: Dereference of a possibly null reference. - // c1.Prop.Prop.ToString(); // 4 - Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop.Prop").WithLocation(25, 13), - // (29,23): warning CS0649: Field 'C1.Prop' is never assigned to, and will always have its default value null - // class C1 { public C2? Prop; } - Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Prop").WithArguments("C1.Prop", "null").WithLocation(29, 23), - // (30,27): warning CS0649: Field 'C2.Prop' is never assigned to, and will always have its default value null - // class C2 { public object? Prop; } - Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Prop").WithArguments("C2.Prop", "null").WithLocation(30, 27)); + compilation.VerifyEmitDiagnostics( + // (8,13): warning CS8602: Dereference of a possibly null reference. + // c1.Prop1.Prop2.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1.Prop2").WithLocation(8, 13), + // (19,13): warning CS8602: Dereference of a possibly null reference. + // c1.Prop1.Prop2.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1.Prop2").WithLocation(19, 13), + // (24,13): warning CS8602: Dereference of a possibly null reference. + // c1.Prop1.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1").WithLocation(24, 13), + // (25,13): warning CS8602: Dereference of a possibly null reference. + // c1.Prop1.Prop2.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1.Prop2").WithLocation(25, 13) + ); + } + + [Fact] + public void ExtendedPropertyPatterns_Nullability_UnannotatedFields() + { + var program = @" +#nullable enable +class C +{ + public void M1(C1 c1) + { + if (c1 is { Prop1.Prop2: null }) + { + c1.Prop1.ToString(); + c1.Prop1.Prop2.ToString(); // 1 + } + else + { + c1.Prop1.ToString(); + c1.Prop1.Prop2.ToString(); + } + } + + public void M2(C1 c1) + { + if (c1 is { Prop1.Prop2: {} }) + { + c1.Prop1.ToString(); + c1.Prop1.Prop2.ToString(); + } + } + + public void M3(C1 c1) + { + if (c1 is { Prop1: null } && + c1 is { Prop1.Prop2: null }) + { + c1.Prop1.ToString(); + c1.Prop1.Prop2.ToString(); // 2 + } + else + { + c1.Prop1.ToString(); // 3 + c1.Prop1.Prop2.ToString(); + } + } + + public void M4(C1 c1) + { + if (c1 is { Prop1: null } || + c1 is { Prop1.Prop2: null }) + { + c1.Prop1.ToString(); // 4 + c1.Prop1.Prop2.ToString(); // 5 + } + else + { + c1.Prop1.ToString(); + c1.Prop1.Prop2.ToString(); + } + } +} + +class C1 { public C2 Prop1 = null!; } +class C2 { public object Prop2 = null!; } +"; + var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + compilation.VerifyEmitDiagnostics( + // (10,13): warning CS8602: Dereference of a possibly null reference. + // c1.Prop1.Prop2.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1.Prop2").WithLocation(10, 13), + // (34,13): warning CS8602: Dereference of a possibly null reference. + // c1.Prop1.Prop2.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1.Prop2").WithLocation(34, 13), + // (38,13): warning CS8602: Dereference of a possibly null reference. + // c1.Prop1.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1").WithLocation(38, 13), + // (48,13): warning CS8602: Dereference of a possibly null reference. + // c1.Prop1.ToString(); // 4 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1").WithLocation(48, 13), + // (49,13): warning CS8602: Dereference of a possibly null reference. + // c1.Prop1.Prop2.ToString(); // 5 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "c1.Prop1.Prop2").WithLocation(49, 13) + ); + } + + [Fact] + public void ExtendedPropertyPatterns_ExpressionColonInPositionalPattern() + { + var source = @" +class C +{ + C Property { get; set; } + + void M() + { + _ = this is (Property.Property: null, Property: null); + } + + public void Deconstruct(out C c1, out C c2) + => throw null; +} +"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.Regular9); + compilation.VerifyEmitDiagnostics( + // (8,22): error CS8652: The feature 'extended property patterns' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // _ = this is (Property.Property: null, Property: null); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Property.Property").WithArguments("extended property patterns").WithLocation(8, 22), + // (8,22): error CS1001: Identifier expected + // _ = this is (Property.Property: null, Property: null); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "Property.Property").WithLocation(8, 22), + // (8,47): error CS8517: The name 'Property' does not match the corresponding 'Deconstruct' parameter 'c2'. + // _ = this is (Property.Property: null, Property: null); + Diagnostic(ErrorCode.ERR_DeconstructParameterNameMismatch, "Property").WithArguments("Property", "c2").WithLocation(8, 47) + ); + } + + [Fact] + public void ExtendedPropertyPatterns_ExpressionColonInITuplePattern() + { + var source = @" +class C +{ + void M() + { + System.Runtime.CompilerServices.ITuple t = null; + var r = t is (X.Y: 3, Y.Z: 4); + } +} +namespace System.Runtime.CompilerServices +{ + public interface ITuple + { + int Length { get; } + object this[int index] { get; } + } +} +"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + compilation.VerifyEmitDiagnostics( + // (7,23): error CS1001: Identifier expected + // var r = t is (X.Y: 3, Y.Z: 4); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "X.Y").WithLocation(7, 23), + // (7,31): error CS1001: Identifier expected + // var r = t is (X.Y: 3, Y.Z: 4); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "Y.Z").WithLocation(7, 31) + ); + } + + [Fact] + public void ExtendedPropertyPatterns_ExpressionColonInValueTuplePattern() + { + var source = @" +class C +{ + void M() + { + _ = (1, 2) is (X.Y: 3, Y.Z: 4); + } +} +namespace System.Runtime.CompilerServices +{ + public interface ITuple + { + int Length { get; } + object this[int index] { get; } + } +} +"; + var compilation = CreateCompilation(source, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + compilation.VerifyEmitDiagnostics( + // (6,24): error CS1001: Identifier expected + // _ = (1, 2) is (X.Y: 3, Y.Z: 4); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "X.Y").WithLocation(6, 24), + // (6,32): error CS1001: Identifier expected + // _ = (1, 2) is (X.Y: 3, Y.Z: 4); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "Y.Z").WithLocation(6, 32) + ); + } + + [Fact] + public void ExtendedPropertyPatterns_ObsoleteProperty() + { + var program = @" +using System; +class C +{ + public void M1(C1 c1) + { + _ = c1 is { Prop1.Prop2: null }; + } +} + +class C1 +{ + [ObsoleteAttribute(""error Prop1"", true)] + public C2 Prop1 { get; set; } +} +class C2 +{ + [ObsoleteAttribute(""error Prop2"", true)] + public object Prop2 { get; set; } +} +"; + var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + compilation.VerifyEmitDiagnostics( + // (7,21): error CS0619: 'C1.Prop1' is obsolete: 'error Prop1' + // _ = c1 is { Prop1.Prop2: null }; + Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "Prop1").WithArguments("C1.Prop1", "error Prop1").WithLocation(7, 21), + // (7,27): error CS0619: 'C2.Prop2' is obsolete: 'error Prop2' + // _ = c1 is { Prop1.Prop2: null }; + Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "Prop2").WithArguments("C2.Prop2", "error Prop2").WithLocation(7, 27) + ); + } + + [Fact] + public void ExtendedPropertyPatterns_ObsoleteAccessor() + { + var program = @" +using System; +class C +{ + public void M1(C1 c1) + { + _ = c1 is { Prop1.Prop2: null }; + } +} + +class C1 +{ + public C2 Prop1 + { + [ObsoleteAttribute(""error Prop1"", true)] + get; + set; + } +} +class C2 +{ + public object Prop2 + { + get; + [ObsoleteAttribute(""error Prop2"", true)] + set; + } +} +"; + var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + compilation.VerifyEmitDiagnostics( + // (7,21): error CS0619: 'C1.Prop1.get' is obsolete: 'error Prop1' + // _ = c1 is { Prop1.Prop2: null }; + Diagnostic(ErrorCode.ERR_DeprecatedSymbolStr, "Prop1.Prop2").WithArguments("C1.Prop1.get", "error Prop1").WithLocation(7, 21) + ); + } + + [Fact] + public void ExtendedPropertyPatterns_InaccessibleProperty() + { + var program = @" +using System; +class C +{ + public void M1(C1 c1) + { + _ = c1 is { Prop1.Prop2: null }; + } +} + +class C1 +{ + private C2 Prop1 { get; set; } +} +class C2 +{ + private object Prop2 { get; set; } +} +"; + var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + compilation.VerifyEmitDiagnostics( + // (7,21): error CS0122: 'C1.Prop1' is inaccessible due to its protection level + // _ = c1 is { Prop1.Prop2: null }; + Diagnostic(ErrorCode.ERR_BadAccess, "Prop1").WithArguments("C1.Prop1").WithLocation(7, 21) + ); + } + + [Fact] + public void ExtendedPropertyPatterns_ExpressionTree() + { + var program = @" +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +class C +{ + public void M1(C1 c1) + { + Expression> f = (c1) => c1 is { Prop1.Prop2: null }; + } +} + +class C1 +{ + public C2 Prop1 { get; set; } +} +class C2 +{ + public object Prop2 { get; set; } +} +"; + var compilation = CreateCompilation(program, parseOptions: TestOptions.RegularWithExtendedPropertyPatterns); + compilation.VerifyEmitDiagnostics( + // (9,48): error CS8122: An expression tree may not contain an 'is' pattern-matching operator. + // Expression> f = (c1) => c1 is { Prop1.Prop2: null }; + Diagnostic(ErrorCode.ERR_ExpressionTreeContainsIsMatch, "c1 is { Prop1.Prop2: null }").WithLocation(9, 48) + ); + } + + public class FlowAnalysisTests : FlowTestBase + { + [Fact] + public void RegionInIsPattern01() + { + var dataFlowAnalysisResults = CompileAndAnalyzeDataFlowExpression(@" +class C +{ + static void M(object o) + { + _ = o switch + { + string { Length: 0 } s => /**/s.ToString()/**/, + _ = throw null + }; + } +}"); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared)); + Assert.Equal("s", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned)); + Assert.Equal("s", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside)); + Assert.Equal("o", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside)); + Assert.Equal("o, s", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.Captured)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedInside)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedOutside)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.UnsafeAddressTaken)); + } } } } diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests2.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests2.cs index ee47e59f6cbde..c5ad3311d7edb 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests2.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/PatternParsingTests2.cs @@ -865,5 +865,63 @@ public void ExtendedPropertySubpattern_15() } EOF(); } + + [Fact] + public void ExtendedPropertySubpattern_InPositionalPattern() + { + UsingExpression(@"e is ( a.b.c: p )"); + + N(SyntaxKind.IsPatternExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "e"); + } + N(SyntaxKind.IsKeyword); + N(SyntaxKind.RecursivePattern); + { + N(SyntaxKind.PositionalPatternClause); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.Subpattern); + { + N(SyntaxKind.ExpressionColon); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.SimpleMemberAccessExpression); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "a"); + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "b"); + } + } + N(SyntaxKind.DotToken); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "c"); + } + } + N(SyntaxKind.ColonToken); + } + N(SyntaxKind.ConstantPattern); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "p"); + } + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + EOF(); + } } } diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNormalizerTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNormalizerTests.cs index 02482050386d7..0e87076b9dafd 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNormalizerTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNormalizerTests.cs @@ -971,10 +971,9 @@ public void TestNormalizeBlockAnonymousFunctions(string actual, string expected) TestNormalizeStatement(actual, expected); } - [Fact(Skip = "PROTOTYPE")] + [Fact] public void TestNormalizeExtendedPropertyPattern() { - // PROTOTYPE: we may need to flow https://github.com/dotnet/roslyn/pull/52680 into the feature branch first var text = "_ = this is{Property . Property :2};"; var expected = @"_ = this is { Property.Property: 2 };"; diff --git a/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs b/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs index 3be5f5a769a20..d690671b2b71e 100644 --- a/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs +++ b/src/Compilers/Test/Core/Compilation/OperationTreeVerifier.cs @@ -57,9 +57,9 @@ public static void Verify(string expectedOperationTree, string actualOperationTr { char[] newLineChars = Environment.NewLine.ToCharArray(); string actual = actualOperationTree.Trim(newLineChars); - actual = actual.Replace("\"", "\"\""); + actual = actual.Replace("\"", "\"\"").Replace(" \n", "\n").Replace(" \r", "\r"); expectedOperationTree = expectedOperationTree.Trim(newLineChars); - expectedOperationTree = expectedOperationTree.Replace("\r\n", "\n").Replace("\n", Environment.NewLine); + expectedOperationTree = expectedOperationTree.Replace("\r\n", "\n").Replace(" \n", "\n").Replace("\n", Environment.NewLine); expectedOperationTree = expectedOperationTree.Replace("\"", "\"\""); AssertEx.AssertEqualToleratingWhitespaceDifferences(expectedOperationTree, actual); diff --git a/src/EditorFeatures/CSharpTest/GenerateVariable/GenerateVariableTests.cs b/src/EditorFeatures/CSharpTest/GenerateVariable/GenerateVariableTests.cs index f5a0a5770ae48..25aa680d6c23d 100644 --- a/src/EditorFeatures/CSharpTest/GenerateVariable/GenerateVariableTests.cs +++ b/src/EditorFeatures/CSharpTest/GenerateVariable/GenerateVariableTests.cs @@ -8891,6 +8891,49 @@ class Blah }"); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateVariable)] + public async Task TestExtendedPropertyPatternInIsPattern() + { + await TestInRegularAndScriptAsync( +@" +class C +{ + Blah SomeBlah { get; set; } + + void M2() + { + object o = null; + if (o is C { SomeBlah.[|X|]: (y: 1, z: 2) }) + { + } + } + + class Blah + { + } +} +" + TestResources.NetFX.ValueTuple.tuplelib_cs, +@" +class C +{ + Blah SomeBlah { get; set; } + + void M2() + { + object o = null; + if (o is C { SomeBlah.X: (y: 1, z: 2) }) + { + } + } + + class Blah + { + public (int y, int z) X { get; internal set; } + } +} +" + TestResources.NetFX.ValueTuple.tuplelib_cs, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview)); + } + [WorkItem(9090, "https://github.com/dotnet/roslyn/issues/9090")] [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsGenerateVariable)] public async Task TestPropertyPatternInIsPattern9() diff --git a/src/EditorFeatures/CSharpTest/TypeInferrer/TypeInferrerTests.cs b/src/EditorFeatures/CSharpTest/TypeInferrer/TypeInferrerTests.cs index f087434156dab..f9b2bb79947a6 100644 --- a/src/EditorFeatures/CSharpTest/TypeInferrer/TypeInferrerTests.cs +++ b/src/EditorFeatures/CSharpTest/TypeInferrer/TypeInferrerTests.cs @@ -3210,6 +3210,60 @@ public void M() { Color: [||] } } +"; + await TestAsync(markup, "global::Color", TestMode.Position); + } + + [Fact] + [Trait(Traits.Feature, Traits.Features.TypeInferenceService)] + public async Task TestEnumInPatterns_SwitchStatement_ExtendedPropertyPattern() + { + var markup = @" +public enum Color +{ + Red, + Green, +} + +class C +{ + public C AnotherC { get; } + public Color Color { get; } + + public void M() + { + switch (this) + { + case { AnotherC.Color: [||] + } +} +"; + await TestAsync(markup, "global::Color", TestMode.Position); + } + + [Fact] + [Trait(Traits.Feature, Traits.Features.TypeInferenceService)] + public async Task TestEnumInPatterns_SwitchStatement_ExtendedPropertyPattern_Field() + { + var markup = @" +public enum Color +{ + Red, + Green, +} + +class C +{ + public C AnotherC { get; } + public Color Color; + + public void M() + { + switch (this) + { + case { AnotherC.Color: [||] + } +} "; await TestAsync(markup, "global::Color", TestMode.Position); } diff --git a/src/Features/CSharp/Portable/GenerateMember/GenerateVariable/CSharpGenerateVariableService.cs b/src/Features/CSharp/Portable/GenerateMember/GenerateVariable/CSharpGenerateVariableService.cs index 900aaafd20c99..e5e4740c80d80 100644 --- a/src/Features/CSharp/Portable/GenerateMember/GenerateVariable/CSharpGenerateVariableService.cs +++ b/src/Features/CSharp/Portable/GenerateMember/GenerateVariable/CSharpGenerateVariableService.cs @@ -160,7 +160,7 @@ private static bool IsLegal( return true; } - if (expression.IsParentKind(SyntaxKind.NameColon) && + if (expression.IsParentKind(SyntaxKind.NameColon, SyntaxKind.ExpressionColon) && expression.Parent.IsParentKind(SyntaxKind.Subpattern)) { return true; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs index 7caa73b8e5150..b72e782fead03 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/LanguageServices/CSharpTypeInferenceService.TypeInferrer.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -159,6 +160,7 @@ protected override IEnumerable InferTypesWorker_DoNotCallDire ConstantPatternSyntax constantPattern => InferTypeInConstantPattern(constantPattern), DoStatementSyntax doStatement => InferTypeInDoStatement(doStatement), EqualsValueClauseSyntax equalsValue => InferTypeInEqualsValueClause(equalsValue), + ExpressionColonSyntax expressionColon => InferTypeInExpressionColon(expressionColon), ExpressionStatementSyntax _ => InferTypeInExpressionStatement(), ForEachStatementSyntax forEachStatement => InferTypeInForEachStatement(forEachStatement, expression), ForStatementSyntax forStatement => InferTypeInForStatement(forStatement, expression), @@ -224,6 +226,7 @@ protected override IEnumerable InferTypesWorker_DoNotCallDire DefaultExpressionSyntax defaultExpression => InferTypeInDefaultExpression(defaultExpression), DoStatementSyntax doStatement => InferTypeInDoStatement(doStatement, token), EqualsValueClauseSyntax equalsValue => InferTypeInEqualsValueClause(equalsValue, token), + ExpressionColonSyntax expressionColon => InferTypeInExpressionColon(expressionColon, token), ExpressionStatementSyntax _ => InferTypeInExpressionStatement(token), ForEachStatementSyntax forEachStatement => InferTypeInForEachStatement(forEachStatement, previousToken: token), ForStatementSyntax forStatement => InferTypeInForStatement(forStatement, previousToken: token), @@ -1458,12 +1461,11 @@ private IEnumerable InferTypeInSubpattern( // parent type. So look up the parent type first, then find the X member in it // and use that type. if (child == subpattern.Pattern && - // PROTOTYPE(extended-property-patterns) ExpressionColon - subpattern.NameColon != null) + subpattern.ExpressionColon != null) { - var result = ArrayBuilder.GetInstance(); + using var result = TemporaryArray.Empty; - foreach (var symbol in this.SemanticModel.GetSymbolInfo(subpattern.NameColon.Name).GetAllSymbols()) + foreach (var symbol in this.SemanticModel.GetSymbolInfo(subpattern.ExpressionColon.Expression).GetAllSymbols()) { switch (symbol) { @@ -1476,7 +1478,7 @@ private IEnumerable InferTypeInSubpattern( } } - return result.ToImmutableAndFree(); + return result.ToImmutableAndClear(); } return SpecializedCollections.EmptyEnumerable(); @@ -1559,7 +1561,6 @@ private IEnumerable GetTypesForRecursivePattern(RecursivePatt foreach (var subPattern in positionalPart.Subpatterns) { - // PROTOTYPE(extended-property-patterns) ExpressionColon elementNamesBuilder.Add(subPattern.NameColon?.Name.Identifier.ValueText); var patternType = GetPatternTypes(subPattern.Pattern).FirstOrDefault(); @@ -1665,6 +1666,21 @@ private IEnumerable InferTypeInNameColon(NameColonSyntax name }; } + private IEnumerable InferTypeInExpressionColon(ExpressionColonSyntax expressionColon, SyntaxToken previousToken) + { + if (previousToken != expressionColon.ColonToken) + { + // Must follow the colon token. + return SpecializedCollections.EmptyEnumerable(); + } + + return expressionColon.Parent switch + { + SubpatternSyntax subPattern => InferTypeInSubpattern(subPattern, subPattern.Pattern), + _ => SpecializedCollections.EmptyEnumerable() + }; + } + private IEnumerable InferTypeInMemberAccessExpression( MemberAccessExpressionSyntax memberAccessExpression, ExpressionSyntax expressionOpt = null, @@ -1843,6 +1859,16 @@ private IEnumerable InferTypeInNameColon(NameColonSyntax name return SpecializedCollections.EmptyEnumerable(); } + private IEnumerable InferTypeInExpressionColon(ExpressionColonSyntax expressionColon) + { + if (expressionColon.Parent is SubpatternSyntax subpattern) + { + return GetPatternTypes(subpattern.Pattern); + } + + return SpecializedCollections.EmptyEnumerable(); + } + private IEnumerable InferTypeInNameEquals(NameEqualsSyntax nameEquals, SyntaxToken? previousToken = null) { if (previousToken == nameEquals.EqualsToken)