From cd16b9132bbd8313eaa6bc7e54b23f46c0b86b09 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Wed, 19 Jul 2023 14:34:08 +0200 Subject: [PATCH] Allow parsing `ref readonly` parameters in `cref` --- .../CSharp/Portable/Binder/Binder_Crefs.cs | 6 + .../Portable/Generated/CSharp.Generated.g4 | 6 +- .../Syntax.xml.Internal.Generated.cs | 86 ++++++--- .../Syntax.xml.Main.Generated.cs | 18 +- .../Syntax.xml.Syntax.Generated.cs | 29 +++- .../Parser/DocumentationCommentParser.cs | 10 +- .../CSharp/Portable/PublicAPI.Unshipped.txt | 4 + .../Portable/Syntax/CrefParameterSyntax.cs | 2 +- .../CSharp/Portable/Syntax/Syntax.xml | 6 +- .../Semantics/RefReadonlyParameterTests.cs | 6 +- .../Symbol/DocumentationComments/CrefTests.cs | 42 +++++ .../Generated/Syntax.Test.xml.Generated.cs | 8 +- .../Test/Syntax/Parsing/CrefParsingTests.cs | 164 ++++++++++++++++++ 13 files changed, 340 insertions(+), 47 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs index 5e0d61da99601..442ebe111b33e 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs @@ -933,6 +933,12 @@ private ImmutableArray BindCrefParameters(BaseCrefParameterList foreach (CrefParameterSyntax parameter in parameterListSyntax.Parameters) { RefKind refKind = parameter.RefKindKeyword.Kind().GetRefKind(); + Debug.Assert(parameter.ReadOnlyKeyword.IsKind(SyntaxKind.None) || + (parameter.ReadOnlyKeyword.IsKind(SyntaxKind.ReadOnlyKeyword) && refKind == RefKind.Ref)); + if (refKind == RefKind.Ref && parameter.ReadOnlyKeyword.IsKind(SyntaxKind.ReadOnlyKeyword)) + { + refKind = RefKind.RefReadOnlyParameter; + } Debug.Assert(parameterListSyntax.Parent is object); TypeSymbol type = BindCrefParameterOrReturnType(parameter.Type, (MemberCrefSyntax)parameterListSyntax.Parent, diagnostics); diff --git a/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 b/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 index fde4150129664..a5ce638524661 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 +++ b/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 @@ -1166,9 +1166,9 @@ cref_parameter_list ; cref_parameter - : 'in'? type - | 'out'? type - | 'ref'? type + : 'in'? 'readonly'? type + | 'out'? 'readonly'? type + | 'ref'? 'readonly'? type ; indexer_member_cref diff --git a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs index df1194a15301f..14350b3cccc8e 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs @@ -30195,62 +30195,81 @@ static CrefBracketedParameterListSyntax() /// /// An element of a BaseCrefParameterListSyntax. - /// Unlike a regular parameter, a cref parameter has only an optional ref or out keyword and a type - + /// Unlike a regular parameter, a cref parameter has only an optional ref, in, out keyword, + /// an optional readonly keyword, and a type - /// there is no name and there are no attributes or other modifiers. /// internal sealed partial class CrefParameterSyntax : CSharpSyntaxNode { internal readonly SyntaxToken? refKindKeyword; + internal readonly SyntaxToken? readOnlyKeyword; internal readonly TypeSyntax type; - internal CrefParameterSyntax(SyntaxKind kind, SyntaxToken? refKindKeyword, TypeSyntax type, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations) + internal CrefParameterSyntax(SyntaxKind kind, SyntaxToken? refKindKeyword, SyntaxToken? readOnlyKeyword, TypeSyntax type, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations) : base(kind, diagnostics, annotations) { - this.SlotCount = 2; + this.SlotCount = 3; if (refKindKeyword != null) { this.AdjustFlagsAndWidth(refKindKeyword); this.refKindKeyword = refKindKeyword; } + if (readOnlyKeyword != null) + { + this.AdjustFlagsAndWidth(readOnlyKeyword); + this.readOnlyKeyword = readOnlyKeyword; + } this.AdjustFlagsAndWidth(type); this.type = type; } - internal CrefParameterSyntax(SyntaxKind kind, SyntaxToken? refKindKeyword, TypeSyntax type, SyntaxFactoryContext context) + internal CrefParameterSyntax(SyntaxKind kind, SyntaxToken? refKindKeyword, SyntaxToken? readOnlyKeyword, TypeSyntax type, SyntaxFactoryContext context) : base(kind) { this.SetFactoryContext(context); - this.SlotCount = 2; + this.SlotCount = 3; if (refKindKeyword != null) { this.AdjustFlagsAndWidth(refKindKeyword); this.refKindKeyword = refKindKeyword; } + if (readOnlyKeyword != null) + { + this.AdjustFlagsAndWidth(readOnlyKeyword); + this.readOnlyKeyword = readOnlyKeyword; + } this.AdjustFlagsAndWidth(type); this.type = type; } - internal CrefParameterSyntax(SyntaxKind kind, SyntaxToken? refKindKeyword, TypeSyntax type) + internal CrefParameterSyntax(SyntaxKind kind, SyntaxToken? refKindKeyword, SyntaxToken? readOnlyKeyword, TypeSyntax type) : base(kind) { - this.SlotCount = 2; + this.SlotCount = 3; if (refKindKeyword != null) { this.AdjustFlagsAndWidth(refKindKeyword); this.refKindKeyword = refKindKeyword; } + if (readOnlyKeyword != null) + { + this.AdjustFlagsAndWidth(readOnlyKeyword); + this.readOnlyKeyword = readOnlyKeyword; + } this.AdjustFlagsAndWidth(type); this.type = type; } public SyntaxToken? RefKindKeyword => this.refKindKeyword; + public SyntaxToken? ReadOnlyKeyword => this.readOnlyKeyword; public TypeSyntax Type => this.type; internal override GreenNode? GetSlot(int index) => index switch { 0 => this.refKindKeyword, - 1 => this.type, + 1 => this.readOnlyKeyword, + 2 => this.type, _ => null, }; @@ -30259,11 +30278,11 @@ internal CrefParameterSyntax(SyntaxKind kind, SyntaxToken? refKindKeyword, TypeS public override void Accept(CSharpSyntaxVisitor visitor) => visitor.VisitCrefParameter(this); public override TResult Accept(CSharpSyntaxVisitor visitor) => visitor.VisitCrefParameter(this); - public CrefParameterSyntax Update(SyntaxToken refKindKeyword, TypeSyntax type) + public CrefParameterSyntax Update(SyntaxToken refKindKeyword, SyntaxToken readOnlyKeyword, TypeSyntax type) { - if (refKindKeyword != this.RefKindKeyword || type != this.Type) + if (refKindKeyword != this.RefKindKeyword || readOnlyKeyword != this.ReadOnlyKeyword || type != this.Type) { - var newNode = SyntaxFactory.CrefParameter(refKindKeyword, type); + var newNode = SyntaxFactory.CrefParameter(refKindKeyword, readOnlyKeyword, type); var diags = GetDiagnostics(); if (diags?.Length > 0) newNode = newNode.WithDiagnosticsGreen(diags); @@ -30277,21 +30296,27 @@ public CrefParameterSyntax Update(SyntaxToken refKindKeyword, TypeSyntax type) } internal override GreenNode SetDiagnostics(DiagnosticInfo[]? diagnostics) - => new CrefParameterSyntax(this.Kind, this.refKindKeyword, this.type, diagnostics, GetAnnotations()); + => new CrefParameterSyntax(this.Kind, this.refKindKeyword, this.readOnlyKeyword, this.type, diagnostics, GetAnnotations()); internal override GreenNode SetAnnotations(SyntaxAnnotation[]? annotations) - => new CrefParameterSyntax(this.Kind, this.refKindKeyword, this.type, GetDiagnostics(), annotations); + => new CrefParameterSyntax(this.Kind, this.refKindKeyword, this.readOnlyKeyword, this.type, GetDiagnostics(), annotations); internal CrefParameterSyntax(ObjectReader reader) : base(reader) { - this.SlotCount = 2; + this.SlotCount = 3; var refKindKeyword = (SyntaxToken?)reader.ReadValue(); if (refKindKeyword != null) { AdjustFlagsAndWidth(refKindKeyword); this.refKindKeyword = refKindKeyword; } + var readOnlyKeyword = (SyntaxToken?)reader.ReadValue(); + if (readOnlyKeyword != null) + { + AdjustFlagsAndWidth(readOnlyKeyword); + this.readOnlyKeyword = readOnlyKeyword; + } var type = (TypeSyntax)reader.ReadValue(); AdjustFlagsAndWidth(type); this.type = type; @@ -30301,6 +30326,7 @@ internal override void WriteTo(ObjectWriter writer) { base.WriteTo(writer); writer.WriteValue(this.refKindKeyword); + writer.WriteValue(this.readOnlyKeyword); writer.WriteValue(this.type); } @@ -35985,7 +36011,7 @@ public override CSharpSyntaxNode VisitCrefBracketedParameterList(CrefBracketedPa => node.Update((SyntaxToken)Visit(node.OpenBracketToken), VisitList(node.Parameters), (SyntaxToken)Visit(node.CloseBracketToken)); public override CSharpSyntaxNode VisitCrefParameter(CrefParameterSyntax node) - => node.Update((SyntaxToken)Visit(node.RefKindKeyword), (TypeSyntax)Visit(node.Type)); + => node.Update((SyntaxToken)Visit(node.RefKindKeyword), (SyntaxToken)Visit(node.ReadOnlyKeyword), (TypeSyntax)Visit(node.Type)); public override CSharpSyntaxNode VisitXmlElement(XmlElementSyntax node) => node.Update((XmlElementStartTagSyntax)Visit(node.StartTag), VisitList(node.Content), (XmlElementEndTagSyntax)Visit(node.EndTag)); @@ -40645,7 +40671,7 @@ public CrefBracketedParameterListSyntax CrefBracketedParameterList(SyntaxToken o return result; } - public CrefParameterSyntax CrefParameter(SyntaxToken? refKindKeyword, TypeSyntax type) + public CrefParameterSyntax CrefParameter(SyntaxToken? refKindKeyword, SyntaxToken? readOnlyKeyword, TypeSyntax type) { #if DEBUG if (refKindKeyword != null) @@ -40659,14 +40685,23 @@ public CrefParameterSyntax CrefParameter(SyntaxToken? refKindKeyword, TypeSyntax default: throw new ArgumentException(nameof(refKindKeyword)); } } + if (readOnlyKeyword != null) + { + switch (readOnlyKeyword.Kind) + { + case SyntaxKind.ReadOnlyKeyword: + case SyntaxKind.None: break; + default: throw new ArgumentException(nameof(readOnlyKeyword)); + } + } if (type == null) throw new ArgumentNullException(nameof(type)); #endif int hash; - var cached = CSharpSyntaxNodeCache.TryGetNode((int)SyntaxKind.CrefParameter, refKindKeyword, type, this.context, out hash); + var cached = CSharpSyntaxNodeCache.TryGetNode((int)SyntaxKind.CrefParameter, refKindKeyword, readOnlyKeyword, type, this.context, out hash); if (cached != null) return (CrefParameterSyntax)cached; - var result = new CrefParameterSyntax(SyntaxKind.CrefParameter, refKindKeyword, type, this.context); + var result = new CrefParameterSyntax(SyntaxKind.CrefParameter, refKindKeyword, readOnlyKeyword, type, this.context); if (hash >= 0) { SyntaxNodeCache.AddNode(result, hash); @@ -45852,7 +45887,7 @@ public static CrefBracketedParameterListSyntax CrefBracketedParameterList(Syntax return result; } - public static CrefParameterSyntax CrefParameter(SyntaxToken? refKindKeyword, TypeSyntax type) + public static CrefParameterSyntax CrefParameter(SyntaxToken? refKindKeyword, SyntaxToken? readOnlyKeyword, TypeSyntax type) { #if DEBUG if (refKindKeyword != null) @@ -45866,14 +45901,23 @@ public static CrefParameterSyntax CrefParameter(SyntaxToken? refKindKeyword, Typ default: throw new ArgumentException(nameof(refKindKeyword)); } } + if (readOnlyKeyword != null) + { + switch (readOnlyKeyword.Kind) + { + case SyntaxKind.ReadOnlyKeyword: + case SyntaxKind.None: break; + default: throw new ArgumentException(nameof(readOnlyKeyword)); + } + } if (type == null) throw new ArgumentNullException(nameof(type)); #endif int hash; - var cached = SyntaxNodeCache.TryGetNode((int)SyntaxKind.CrefParameter, refKindKeyword, type, out hash); + var cached = SyntaxNodeCache.TryGetNode((int)SyntaxKind.CrefParameter, refKindKeyword, readOnlyKeyword, type, out hash); if (cached != null) return (CrefParameterSyntax)cached; - var result = new CrefParameterSyntax(SyntaxKind.CrefParameter, refKindKeyword, type); + var result = new CrefParameterSyntax(SyntaxKind.CrefParameter, refKindKeyword, readOnlyKeyword, type); if (hash >= 0) { SyntaxNodeCache.AddNode(result, hash); diff --git a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs index 3c618d03fad44..0ae229d498502 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs @@ -2089,7 +2089,7 @@ public partial class CSharpSyntaxRewriter : CSharpSyntaxVisitor => node.Update(VisitToken(node.OpenBracketToken), VisitList(node.Parameters), VisitToken(node.CloseBracketToken)); public override SyntaxNode? VisitCrefParameter(CrefParameterSyntax node) - => node.Update(VisitToken(node.RefKindKeyword), (TypeSyntax?)Visit(node.Type) ?? throw new ArgumentNullException("type")); + => node.Update(VisitToken(node.RefKindKeyword), VisitToken(node.ReadOnlyKeyword), (TypeSyntax?)Visit(node.Type) ?? throw new ArgumentNullException("type")); public override SyntaxNode? VisitXmlElement(XmlElementSyntax node) => node.Update((XmlElementStartTagSyntax?)Visit(node.StartTag) ?? throw new ArgumentNullException("startTag"), VisitList(node.Content), (XmlElementEndTagSyntax?)Visit(node.EndTag) ?? throw new ArgumentNullException("endTag")); @@ -5860,7 +5860,7 @@ public static CrefBracketedParameterListSyntax CrefBracketedParameterList(Separa => SyntaxFactory.CrefBracketedParameterList(SyntaxFactory.Token(SyntaxKind.OpenBracketToken), parameters, SyntaxFactory.Token(SyntaxKind.CloseBracketToken)); /// Creates a new CrefParameterSyntax instance. - public static CrefParameterSyntax CrefParameter(SyntaxToken refKindKeyword, TypeSyntax type) + public static CrefParameterSyntax CrefParameter(SyntaxToken refKindKeyword, SyntaxToken readOnlyKeyword, TypeSyntax type) { switch (refKindKeyword.Kind()) { @@ -5870,13 +5870,23 @@ public static CrefParameterSyntax CrefParameter(SyntaxToken refKindKeyword, Type case SyntaxKind.None: break; default: throw new ArgumentException(nameof(refKindKeyword)); } + switch (readOnlyKeyword.Kind()) + { + case SyntaxKind.ReadOnlyKeyword: + case SyntaxKind.None: break; + default: throw new ArgumentException(nameof(readOnlyKeyword)); + } if (type == null) throw new ArgumentNullException(nameof(type)); - return (CrefParameterSyntax)Syntax.InternalSyntax.SyntaxFactory.CrefParameter((Syntax.InternalSyntax.SyntaxToken?)refKindKeyword.Node, (Syntax.InternalSyntax.TypeSyntax)type.Green).CreateRed(); + return (CrefParameterSyntax)Syntax.InternalSyntax.SyntaxFactory.CrefParameter((Syntax.InternalSyntax.SyntaxToken?)refKindKeyword.Node, (Syntax.InternalSyntax.SyntaxToken?)readOnlyKeyword.Node, (Syntax.InternalSyntax.TypeSyntax)type.Green).CreateRed(); } + /// Creates a new CrefParameterSyntax instance. + public static CrefParameterSyntax CrefParameter(SyntaxToken refKindKeyword, TypeSyntax type) + => SyntaxFactory.CrefParameter(refKindKeyword, default, type); + /// Creates a new CrefParameterSyntax instance. public static CrefParameterSyntax CrefParameter(TypeSyntax type) - => SyntaxFactory.CrefParameter(default, type); + => SyntaxFactory.CrefParameter(default, default, type); /// Creates a new XmlElementSyntax instance. public static XmlElementSyntax XmlElement(XmlElementStartTagSyntax startTag, SyntaxList content, XmlElementEndTagSyntax endTag) diff --git a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs index cfa8cd2b6e433..52ff2c190949a 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs @@ -14332,7 +14332,8 @@ public CrefBracketedParameterListSyntax Update(SyntaxToken openBracketToken, Sep /// /// An element of a BaseCrefParameterListSyntax. - /// Unlike a regular parameter, a cref parameter has only an optional ref or out keyword and a type - + /// Unlike a regular parameter, a cref parameter has only an optional ref, in, out keyword, + /// an optional readonly keyword, and a type - /// there is no name and there are no attributes or other modifiers. /// /// @@ -14359,20 +14360,29 @@ public SyntaxToken RefKindKeyword } } - public TypeSyntax Type => GetRed(ref this.type, 1)!; + public SyntaxToken ReadOnlyKeyword + { + get + { + var slot = ((Syntax.InternalSyntax.CrefParameterSyntax)this.Green).readOnlyKeyword; + return slot != null ? new SyntaxToken(this, slot, GetChildPosition(1), GetChildIndex(1)) : default; + } + } - internal override SyntaxNode? GetNodeSlot(int index) => index == 1 ? GetRed(ref this.type, 1)! : null; + public TypeSyntax Type => GetRed(ref this.type, 2)!; - internal override SyntaxNode? GetCachedSlot(int index) => index == 1 ? this.type : null; + internal override SyntaxNode? GetNodeSlot(int index) => index == 2 ? GetRed(ref this.type, 2)! : null; + + internal override SyntaxNode? GetCachedSlot(int index) => index == 2 ? this.type : null; public override void Accept(CSharpSyntaxVisitor visitor) => visitor.VisitCrefParameter(this); public override TResult? Accept(CSharpSyntaxVisitor visitor) where TResult : default => visitor.VisitCrefParameter(this); - public CrefParameterSyntax Update(SyntaxToken refKindKeyword, TypeSyntax type) + public CrefParameterSyntax Update(SyntaxToken refKindKeyword, SyntaxToken readOnlyKeyword, TypeSyntax type) { - if (refKindKeyword != this.RefKindKeyword || type != this.Type) + if (refKindKeyword != this.RefKindKeyword || readOnlyKeyword != this.ReadOnlyKeyword || type != this.Type) { - var newNode = SyntaxFactory.CrefParameter(refKindKeyword, type); + var newNode = SyntaxFactory.CrefParameter(refKindKeyword, readOnlyKeyword, type); var annotations = GetAnnotations(); return annotations?.Length > 0 ? newNode.WithAnnotations(annotations) : newNode; } @@ -14380,8 +14390,9 @@ public CrefParameterSyntax Update(SyntaxToken refKindKeyword, TypeSyntax type) return this; } - public CrefParameterSyntax WithRefKindKeyword(SyntaxToken refKindKeyword) => Update(refKindKeyword, this.Type); - public CrefParameterSyntax WithType(TypeSyntax type) => Update(this.RefKindKeyword, type); + public CrefParameterSyntax WithRefKindKeyword(SyntaxToken refKindKeyword) => Update(refKindKeyword, this.ReadOnlyKeyword, this.Type); + public CrefParameterSyntax WithReadOnlyKeyword(SyntaxToken readOnlyKeyword) => Update(this.RefKindKeyword, readOnlyKeyword, this.Type); + public CrefParameterSyntax WithType(TypeSyntax type) => Update(this.RefKindKeyword, this.ReadOnlyKeyword, type); } public abstract partial class XmlNodeSyntax : CSharpSyntaxNode diff --git a/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs b/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs index f41df2c5623da..ce607ce81fdb7 100644 --- a/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs @@ -1248,7 +1248,7 @@ private bool IsPossibleCrefParameter() /// Parse an element of a cref parameter list. /// /// - /// "ref" and "out" work, but "params", "this", and "__arglist" don't. + /// "ref", "ref readonly", "in", "out" work, but "params", "this", and "__arglist" don't. /// private CrefParameterSyntax ParseCrefParameter() { @@ -1262,8 +1262,14 @@ private CrefParameterSyntax ParseCrefParameter() break; } + SyntaxToken readOnlyOpt = null; + if (CurrentToken.Kind == SyntaxKind.ReadOnlyKeyword && refKindOpt is { Kind: SyntaxKind.RefKeyword }) + { + readOnlyOpt = EatToken(); + } + TypeSyntax type = ParseCrefType(typeArgumentsMustBeIdentifiers: false); - return SyntaxFactory.CrefParameter(refKindOpt, type); + return SyntaxFactory.CrefParameter(refKindKeyword: refKindOpt, readOnlyKeyword: readOnlyOpt, type); } /// diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index 225bc3b4430ca..28bdbd5a7d69b 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -10,6 +10,10 @@ Microsoft.CodeAnalysis.CSharp.Syntax.CollectionExpressionSyntax.WithCloseBracket Microsoft.CodeAnalysis.CSharp.Syntax.CollectionExpressionSyntax.WithElements(Microsoft.CodeAnalysis.SeparatedSyntaxList elements) -> Microsoft.CodeAnalysis.CSharp.Syntax.CollectionExpressionSyntax! Microsoft.CodeAnalysis.CSharp.Syntax.CollectionExpressionSyntax.WithOpenBracketToken(Microsoft.CodeAnalysis.SyntaxToken openBracketToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.CollectionExpressionSyntax! Microsoft.CodeAnalysis.CSharp.Syntax.CollectionElementSyntax +Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.ReadOnlyKeyword.get -> Microsoft.CodeAnalysis.SyntaxToken +*REMOVED*Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken refKindKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax! type) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax! +Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken refKindKeyword, Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax! type) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax! +Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax.WithReadOnlyKeyword(Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax! Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionElementSyntax Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionElementSyntax.Expression.get -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax! Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionElementSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax! expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionElementSyntax! diff --git a/src/Compilers/CSharp/Portable/Syntax/CrefParameterSyntax.cs b/src/Compilers/CSharp/Portable/Syntax/CrefParameterSyntax.cs index d45df4f20ea1f..3d8b3f0ba79e2 100644 --- a/src/Compilers/CSharp/Portable/Syntax/CrefParameterSyntax.cs +++ b/src/Compilers/CSharp/Portable/Syntax/CrefParameterSyntax.cs @@ -20,7 +20,7 @@ public sealed partial class CrefParameterSyntax [EditorBrowsable(EditorBrowsableState.Never)] public CrefParameterSyntax WithRefOrOutKeyword(SyntaxToken refOrOutKeyword) { - return this.Update(refOrOutKeyword, this.Type); + return this.Update(refKindKeyword: refOrOutKeyword, readOnlyKeyword: default, this.Type); } } } diff --git a/src/Compilers/CSharp/Portable/Syntax/Syntax.xml b/src/Compilers/CSharp/Portable/Syntax/Syntax.xml index 8918693e93d26..d3ae1190b9c23 100644 --- a/src/Compilers/CSharp/Portable/Syntax/Syntax.xml +++ b/src/Compilers/CSharp/Portable/Syntax/Syntax.xml @@ -4519,7 +4519,8 @@ An element of a BaseCrefParameterListSyntax. - Unlike a regular parameter, a cref parameter has only an optional ref or out keyword and a type - + Unlike a regular parameter, a cref parameter has only an optional ref, in, out keyword, + an optional readonly keyword, and a type - there is no name and there are no attributes or other modifiers. @@ -4529,6 +4530,9 @@ + + + diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs index 5756fdc623ed1..4435f89e1bdd8 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/RefReadonlyParameterTests.cs @@ -4693,7 +4693,7 @@ void M({{modifier}} int x) { } Assert.Same(methodFromCref, methodFromClass.GetPublicSymbol()); } - [Theory(Skip = "PROTOTYPE: cref parsing of ref readonly doesn't work"), CombinatorialData] + [Theory, CombinatorialData] public void CrefComparer_RefReadonly([CombinatorialValues("ref", "in")] string modifier) { var source = $$""" @@ -4716,8 +4716,8 @@ void M({{modifier}} int x) { } var cref = docComment.DescendantNodes().OfType().Select(attr => attr.Cref).Single(); var info = model.GetSymbolInfo(cref); var methodFromCref = info.Symbol as IMethodSymbol; - Assert.Equal(RefKind.RefReadOnly, methodFromCref!.Parameters.Single().RefKind); - var methodFromClass = comp.GetMembers("C.M").Cast().Single(m => m.Parameters.Single().RefKind == RefKind.RefReadOnly); + Assert.Equal(RefKind.RefReadOnlyParameter, methodFromCref!.Parameters.Single().RefKind); + var methodFromClass = comp.GetMembers("C.M").Cast().Single(m => m.Parameters.Single().RefKind == RefKind.RefReadOnlyParameter); Assert.Same(methodFromCref, methodFromClass.GetPublicSymbol()); } } diff --git a/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs b/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs index 5afb19fbf2390..95b9599d89cd1 100644 --- a/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs @@ -6703,11 +6703,53 @@ void S() var parameter = cref.Parameters.Parameters.Single(); Assert.Equal(SyntaxKind.InKeyword, parameter.RefKindKeyword.Kind()); + Assert.Equal(SyntaxKind.None, parameter.ReadOnlyKeyword.Kind()); var parameterSymbol = ((IMethodSymbol)model.GetSymbolInfo(cref).Symbol).Parameters.Single(); Assert.Equal(RefKind.In, parameterSymbol.RefKind); } + [Fact] + public void CRef_RefReadonlyParameter() + { + var source = """ + class Test + { + void M(ref readonly int x) + { + } + + /// + /// + /// + void S() + { + } + } + """; + + verify(CreateCompilation(source, parseOptions: TestOptions.Regular11.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics( + // (3,16): error CS8652: The feature 'ref readonly parameters' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // void M(ref readonly int x) + Diagnostic(ErrorCode.ERR_FeatureInPreview, "readonly").WithArguments("ref readonly parameters").WithLocation(3, 16))); + + verify(CreateCompilation(source, parseOptions: TestOptions.RegularNext.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics()); + verify(CreateCompilation(source, parseOptions: TestOptions.RegularPreview.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics()); + + static void verify(CSharpCompilation compilation) + { + var model = compilation.GetSemanticModel(compilation.SyntaxTrees.Single()); + var cref = (NameMemberCrefSyntax)GetCrefSyntaxes(compilation).Single(); + + var parameter = cref.Parameters.Parameters.Single(); + Assert.Equal(SyntaxKind.RefKeyword, parameter.RefKindKeyword.Kind()); + Assert.Equal(SyntaxKind.ReadOnlyKeyword, parameter.ReadOnlyKeyword.Kind()); + + var parameterSymbol = ((IMethodSymbol)model.GetSymbolInfo(cref).Symbol).Parameters.Single(); + Assert.Equal(RefKind.RefReadOnlyParameter, parameterSymbol.RefKind); + } + } + [Fact] public void Cref_TupleType() { diff --git a/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs b/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs index 709415fa8fd9a..fe0d6a3f15fbf 100644 --- a/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs +++ b/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs @@ -632,7 +632,7 @@ private static Syntax.InternalSyntax.CrefBracketedParameterListSyntax GenerateCr => InternalSyntaxFactory.CrefBracketedParameterList(InternalSyntaxFactory.Token(SyntaxKind.OpenBracketToken), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SeparatedSyntaxList(), InternalSyntaxFactory.Token(SyntaxKind.CloseBracketToken)); private static Syntax.InternalSyntax.CrefParameterSyntax GenerateCrefParameter() - => InternalSyntaxFactory.CrefParameter(null, GenerateIdentifierName()); + => InternalSyntaxFactory.CrefParameter(null, null, GenerateIdentifierName()); private static Syntax.InternalSyntax.XmlElementSyntax GenerateXmlElement() => InternalSyntaxFactory.XmlElement(GenerateXmlElementStartTag(), new Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList(), GenerateXmlElementEndTag()); @@ -3379,6 +3379,7 @@ public void TestCrefParameterFactoryAndProperties() var node = GenerateCrefParameter(); Assert.Null(node.RefKindKeyword); + Assert.Null(node.ReadOnlyKeyword); Assert.NotNull(node.Type); AttachAndCheckDiagnostics(node); @@ -10728,7 +10729,7 @@ private static CrefBracketedParameterListSyntax GenerateCrefBracketedParameterLi => SyntaxFactory.CrefBracketedParameterList(SyntaxFactory.Token(SyntaxKind.OpenBracketToken), new SeparatedSyntaxList(), SyntaxFactory.Token(SyntaxKind.CloseBracketToken)); private static CrefParameterSyntax GenerateCrefParameter() - => SyntaxFactory.CrefParameter(default(SyntaxToken), GenerateIdentifierName()); + => SyntaxFactory.CrefParameter(default(SyntaxToken), default(SyntaxToken), GenerateIdentifierName()); private static XmlElementSyntax GenerateXmlElement() => SyntaxFactory.XmlElement(GenerateXmlElementStartTag(), new SyntaxList(), GenerateXmlElementEndTag()); @@ -13475,8 +13476,9 @@ public void TestCrefParameterFactoryAndProperties() var node = GenerateCrefParameter(); Assert.Equal(SyntaxKind.None, node.RefKindKeyword.Kind()); + Assert.Equal(SyntaxKind.None, node.ReadOnlyKeyword.Kind()); Assert.NotNull(node.Type); - var newNode = node.WithRefKindKeyword(node.RefKindKeyword).WithType(node.Type); + var newNode = node.WithRefKindKeyword(node.RefKindKeyword).WithReadOnlyKeyword(node.ReadOnlyKeyword).WithType(node.Type); Assert.Equal(node, newNode); } diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs index d7850d7a4095d..484c472ebb156 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs @@ -1325,6 +1325,170 @@ public void ParameterRefKind() } } + [Fact] + public void ParameterRefReadonly_01() + { + UsingNode("A(ref readonly B)"); + + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.ReadOnlyKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + + [Fact] + public void ParameterRefReadonly_02() + { + UsingNode("A(ref readonly B, C)"); + + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.ReadOnlyKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + + [Fact] + public void ParameterRefReadonly_03() + { + UsingNode("A(B, ref readonly C)"); + + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.ReadOnlyKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + + [Fact] + public void ParameterRefReadonly_04() + { + UsingNode("A(out B, ref readonly C)"); + + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.OutKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.CommaToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.ReadOnlyKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken); + } + } + N(SyntaxKind.CloseParenToken); + } + } + } + + [Theory, CombinatorialData] + public void ParameterRefReadonly_05( + [CombinatorialValues(LanguageVersion.CSharp11, LanguageVersionFacts.CSharpNext, LanguageVersion.Preview)] LanguageVersion languageVersion) + { + UsingNode("A(readonly ref B)", TestOptions.Regular.WithLanguageVersion(languageVersion).WithDocumentationMode(DocumentationMode.Diagnose), + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'A(readonly ref B)' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "A(").WithArguments("A(readonly ref B)").WithLocation(1, 16), + // (1,18): warning CS1658: ) expected. See also error CS1026. + // /// + Diagnostic(ErrorCode.WRN_ErrorOverride, "readonly").WithArguments(") expected", "1026").WithLocation(1, 18)); + + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + M(SyntaxKind.CloseParenToken); + } + } + EOF(); + } + [Fact] public void ParameterNullableType() {