From d2c720560263c88447e0a08bb364995b054c6c87 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Wed, 19 Jul 2023 14:34:08 +0200 Subject: [PATCH 1/9] 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 | 5 + .../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, 341 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..4d578d514e243 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! @@ -31,6 +35,7 @@ override Microsoft.CodeAnalysis.CSharp.Syntax.SpreadElementSyntax.Accept(Microso override Microsoft.CodeAnalysis.CSharp.Syntax.SpreadElementSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor! visitor) -> TResult? static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CollectionExpression(Microsoft.CodeAnalysis.SeparatedSyntaxList elements = default(Microsoft.CodeAnalysis.SeparatedSyntaxList)) -> Microsoft.CodeAnalysis.CSharp.Syntax.CollectionExpressionSyntax! static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CollectionExpression(Microsoft.CodeAnalysis.SyntaxToken openBracketToken, Microsoft.CodeAnalysis.SeparatedSyntaxList elements, Microsoft.CodeAnalysis.SyntaxToken closeBracketToken) -> Microsoft.CodeAnalysis.CSharp.Syntax.CollectionExpressionSyntax! +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.CrefParameter(Microsoft.CodeAnalysis.SyntaxToken refKindKeyword, Microsoft.CodeAnalysis.SyntaxToken readOnlyKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax! type) -> Microsoft.CodeAnalysis.CSharp.Syntax.CrefParameterSyntax! static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.ExpressionElement(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax! expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionElementSyntax! static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SpreadElement(Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax! expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.SpreadElementSyntax! static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.SpreadElement(Microsoft.CodeAnalysis.SyntaxToken operatorToken, Microsoft.CodeAnalysis.CSharp.Syntax.ExpressionSyntax! expression) -> Microsoft.CodeAnalysis.CSharp.Syntax.SpreadElementSyntax! 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() { From 4ec46b22f40c7772f776b5022236cbb907f6dc85 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Thu, 20 Jul 2023 11:12:35 +0200 Subject: [PATCH 2/9] Test `readonly ref` in `cref` --- .../Symbol/DocumentationComments/CrefTests.cs | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs b/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs index 95b9599d89cd1..8ad06c9388b35 100644 --- a/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs @@ -6750,6 +6750,145 @@ static void verify(CSharpCompilation compilation) } } + [Fact] + public void CRef_RefReadonlyParameter_ReadonlyRef() + { + 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), + // (8,20): warning CS1584: XML comment has syntactically incorrect cref attribute 'M(readonly ref int)' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "M(").WithArguments("M(readonly ref int)").WithLocation(8, 20), + // (8,22): warning CS1658: ) expected. See also error CS1026. + // /// + Diagnostic(ErrorCode.WRN_ErrorOverride, "readonly").WithArguments(") expected", "1026").WithLocation(8, 22))); + + var expectedDiagnostics = new[] + { + // (8,20): warning CS1584: XML comment has syntactically incorrect cref attribute 'M(readonly ref int)' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "M(").WithArguments("M(readonly ref int)").WithLocation(8, 20), + // (8,22): warning CS1658: ) expected. See also error CS1026. + // /// + Diagnostic(ErrorCode.WRN_ErrorOverride, "readonly").WithArguments(") expected", "1026").WithLocation(8, 22) + }; + + verify(CreateCompilation(source, parseOptions: TestOptions.RegularNext.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics(expectedDiagnostics)); + verify(CreateCompilation(source, parseOptions: TestOptions.RegularPreview.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics(expectedDiagnostics)); + + static void verify(CSharpCompilation compilation) + { + var cref = (NameMemberCrefSyntax)GetCrefSyntaxes(compilation).Single(); + Assert.Empty(cref.Parameters.Parameters); + } + } + + [Fact] + public void CRef_ReadonlyRefParameter() + { + var source = """ + class Test + { + void M(readonly ref int x) + { + } + + /// + /// + /// + void S() + { + } + } + """; + + var expectedDiagnostics = new[] + { + // (3,12): error CS9501: 'readonly' modifier must be specified after 'ref'. + // void M(readonly ref int x) + Diagnostic(ErrorCode.ERR_RefReadOnlyWrongOrdering, "readonly").WithLocation(3, 12), + // (8,20): warning CS1584: XML comment has syntactically incorrect cref attribute 'M(readonly ref int)' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "M(").WithArguments("M(readonly ref int)").WithLocation(8, 20), + // (8,22): warning CS1658: ) expected. See also error CS1026. + // /// + Diagnostic(ErrorCode.WRN_ErrorOverride, "readonly").WithArguments(") expected", "1026").WithLocation(8, 22) + }; + + verify(CreateCompilation(source, parseOptions: TestOptions.Regular11.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics(expectedDiagnostics)); + verify(CreateCompilation(source, parseOptions: TestOptions.RegularNext.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics(expectedDiagnostics)); + verify(CreateCompilation(source, parseOptions: TestOptions.RegularPreview.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics(expectedDiagnostics)); + + static void verify(CSharpCompilation compilation) + { + var cref = (NameMemberCrefSyntax)GetCrefSyntaxes(compilation).Single(); + Assert.Empty(cref.Parameters.Parameters); + } + } + + [Fact] + public void CRef_ReadonlyRefParameter_RefReadonly() + { + var source = """ + class Test + { + void M(readonly ref int x) + { + } + + /// + /// + /// + void S() + { + } + } + """; + + var expectedDiagnostics = new[] + { + // (3,12): error CS9501: 'readonly' modifier must be specified after 'ref'. + // void M(readonly ref int x) + Diagnostic(ErrorCode.ERR_RefReadOnlyWrongOrdering, "readonly").WithLocation(3, 12), + // (8,20): warning CS1574: XML comment has cref attribute 'M(ref readonly int)' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "M(ref readonly int)").WithArguments("M(ref readonly int)").WithLocation(8, 20) + }; + + verify(CreateCompilation(source, parseOptions: TestOptions.Regular11.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics(expectedDiagnostics)); + verify(CreateCompilation(source, parseOptions: TestOptions.RegularNext.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics(expectedDiagnostics)); + verify(CreateCompilation(source, parseOptions: TestOptions.RegularPreview.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics(expectedDiagnostics)); + + 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()); + + Assert.True(model.GetSymbolInfo(cref).IsEmpty); + } + } + [Fact] public void Cref_TupleType() { From 3cf36715faadf6819388e5b2819fde475b406c1f Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Thu, 20 Jul 2023 17:14:05 +0200 Subject: [PATCH 3/9] Attach unexpected readonly in cref as trivia --- .../Portable/Parser/DocumentationCommentParser.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs b/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs index ce607ce81fdb7..e6b80f670a8bb 100644 --- a/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs @@ -1263,9 +1263,18 @@ private CrefParameterSyntax ParseCrefParameter() } SyntaxToken readOnlyOpt = null; - if (CurrentToken.Kind == SyntaxKind.ReadOnlyKeyword && refKindOpt is { Kind: SyntaxKind.RefKeyword }) + if (CurrentToken.Kind == SyntaxKind.ReadOnlyKeyword && refKindOpt is not null) { - readOnlyOpt = EatToken(); + if (refKindOpt.Kind != SyntaxKind.RefKeyword) + { + // if we encounter `readonly` after `in` or `out`, we place the `readonly` as skipped trivia on the previous keyword + var misplacedToken = AddError(EatToken(), ErrorCode.ERR_RefReadOnlyWrongOrdering); + refKindOpt = AddTrailingSkippedSyntax(refKindOpt, misplacedToken); + } + else + { + readOnlyOpt = EatToken(); + } } TypeSyntax type = ParseCrefType(typeArgumentsMustBeIdentifiers: false); From c64e631061b44f441deb6c14e2fcc11ffdc466da Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Fri, 21 Jul 2023 11:25:19 +0200 Subject: [PATCH 4/9] Preserve previous Update method --- src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt | 1 - .../CSharp/Portable/Syntax/CrefParameterSyntax.cs | 9 +++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index 4d578d514e243..81e47ffac86a1 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -11,7 +11,6 @@ Microsoft.CodeAnalysis.CSharp.Syntax.CollectionExpressionSyntax.WithElements(Mic 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 diff --git a/src/Compilers/CSharp/Portable/Syntax/CrefParameterSyntax.cs b/src/Compilers/CSharp/Portable/Syntax/CrefParameterSyntax.cs index 3d8b3f0ba79e2..2c6980d7b46d9 100644 --- a/src/Compilers/CSharp/Portable/Syntax/CrefParameterSyntax.cs +++ b/src/Compilers/CSharp/Portable/Syntax/CrefParameterSyntax.cs @@ -15,12 +15,17 @@ public sealed partial class CrefParameterSyntax public SyntaxToken RefOrOutKeyword => this.RefKindKeyword; /// - /// Pre C# 7.2 back-compat overload, which simply calls the replacement method . + /// Pre C# 7.2 back-compat overload, which simply calls the replacement method . /// [EditorBrowsable(EditorBrowsableState.Never)] public CrefParameterSyntax WithRefOrOutKeyword(SyntaxToken refOrOutKeyword) { - return this.Update(refKindKeyword: refOrOutKeyword, readOnlyKeyword: default, this.Type); + return this.Update(refOrOutKeyword, this.Type); + } + + public CrefParameterSyntax Update(SyntaxToken refKindKeyword, TypeSyntax type) + { + return this.Update(refKindKeyword: refKindKeyword, readOnlyKeyword: this.ReadOnlyKeyword, type: type); } } } From ad4962dec42d41cf56a58fe90627ca936899925a Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Fri, 21 Jul 2023 11:27:11 +0200 Subject: [PATCH 5/9] Remove assert to support manually created invalid nodes --- src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs index 442ebe111b33e..61a2b281d3d05 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs @@ -933,8 +933,6 @@ 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; From c10bd30f99a5778ea8371b3057bee1f92931e713 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Fri, 21 Jul 2023 11:33:15 +0200 Subject: [PATCH 6/9] Check feature availability when binding `cref` --- .../CSharp/Portable/Binder/Binder_Crefs.cs | 1 + .../Symbol/DocumentationComments/CrefTests.cs | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs index 61a2b281d3d05..9c40f14b6a474 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs @@ -935,6 +935,7 @@ private ImmutableArray BindCrefParameters(BaseCrefParameterList RefKind refKind = parameter.RefKindKeyword.Kind().GetRefKind(); if (refKind == RefKind.Ref && parameter.ReadOnlyKeyword.IsKind(SyntaxKind.ReadOnlyKeyword)) { + CheckFeatureAvailability(parameter.ReadOnlyKeyword, MessageID.IDS_FeatureRefReadonlyParameters, diagnostics); refKind = RefKind.RefReadOnlyParameter; } diff --git a/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs b/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs index 8ad06c9388b35..dc0f1ac46bb5d 100644 --- a/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs @@ -6731,7 +6731,10 @@ 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))); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "readonly").WithArguments("ref readonly parameters").WithLocation(3, 16), + // (8,26): error CS8652: The feature 'ref readonly parameters' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // /// + Diagnostic(ErrorCode.ERR_FeatureInPreview, "readonly").WithArguments("ref readonly parameters").WithLocation(8, 26))); verify(CreateCompilation(source, parseOptions: TestOptions.RegularNext.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics()); verify(CreateCompilation(source, parseOptions: TestOptions.RegularPreview.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics()); @@ -6862,6 +6865,17 @@ void S() } """; + verify(CreateCompilation(source, parseOptions: TestOptions.Regular11.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics( + // (3,12): error CS9501: 'readonly' modifier must be specified after 'ref'. + // void M(readonly ref int x) + Diagnostic(ErrorCode.ERR_RefReadOnlyWrongOrdering, "readonly").WithLocation(3, 12), + // (8,20): warning CS1574: XML comment has cref attribute 'M(ref readonly int)' that could not be resolved + // /// + Diagnostic(ErrorCode.WRN_BadXMLRef, "M(ref readonly int)").WithArguments("M(ref readonly int)").WithLocation(8, 20), + // (8,26): error CS8652: The feature 'ref readonly parameters' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // /// + Diagnostic(ErrorCode.ERR_FeatureInPreview, "readonly").WithArguments("ref readonly parameters").WithLocation(8, 26))); + var expectedDiagnostics = new[] { // (3,12): error CS9501: 'readonly' modifier must be specified after 'ref'. @@ -6872,7 +6886,6 @@ void S() Diagnostic(ErrorCode.WRN_BadXMLRef, "M(ref readonly int)").WithArguments("M(ref readonly int)").WithLocation(8, 20) }; - verify(CreateCompilation(source, parseOptions: TestOptions.Regular11.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics(expectedDiagnostics)); verify(CreateCompilation(source, parseOptions: TestOptions.RegularNext.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics(expectedDiagnostics)); verify(CreateCompilation(source, parseOptions: TestOptions.RegularPreview.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics(expectedDiagnostics)); From 01edf5508d9817c4a53af281d2e34d4a1a95da56 Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Fri, 21 Jul 2023 11:42:08 +0200 Subject: [PATCH 7/9] Test more invalid cref permutations --- .../Test/Syntax/Parsing/CrefParsingTests.cs | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs index 484c472ebb156..5eddfbd92a09a 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs @@ -1489,6 +1489,169 @@ public void ParameterRefReadonly_05( EOF(); } + [Theory, CombinatorialData] + public void ParameterRefReadonly_06( + [CombinatorialValues(LanguageVersion.CSharp11, LanguageVersionFacts.CSharpNext, LanguageVersion.Preview)] LanguageVersion languageVersion) + { + UsingNode("A(readonly B)", TestOptions.Regular.WithLanguageVersion(languageVersion).WithDocumentationMode(DocumentationMode.Diagnose), + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'A(readonly B)' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "A(").WithArguments("A(readonly 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(); + } + + [Theory, CombinatorialData] + public void ParameterRefReadonly_07( + [CombinatorialValues(LanguageVersion.CSharp11, LanguageVersionFacts.CSharpNext, LanguageVersion.Preview)] LanguageVersion languageVersion) + { + UsingNode("A(in readonly B)", TestOptions.Regular.WithLanguageVersion(languageVersion).WithDocumentationMode(DocumentationMode.Diagnose), + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'A(in readonly B)' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "A(in readonly B)").WithArguments("A(in readonly B)").WithLocation(1, 16), + // (1,21): error CS9501: 'readonly' modifier must be specified after 'ref'. + // /// + Diagnostic(ErrorCode.ERR_RefReadOnlyWrongOrdering, "readonly").WithLocation(1, 21)); + + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.InKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + EOF(); + } + + [Theory, CombinatorialData] + public void ParameterRefReadonly_08( + [CombinatorialValues(LanguageVersion.CSharp11, LanguageVersionFacts.CSharpNext, LanguageVersion.Preview)] LanguageVersion languageVersion) + { + UsingNode("A(out readonly B)", TestOptions.Regular.WithLanguageVersion(languageVersion).WithDocumentationMode(DocumentationMode.Diagnose), + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'A(out readonly B)' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "A(out readonly B)").WithArguments("A(out readonly B)").WithLocation(1, 16), + // (1,22): error CS9501: 'readonly' modifier must be specified after 'ref'. + // /// + Diagnostic(ErrorCode.ERR_RefReadOnlyWrongOrdering, "readonly").WithLocation(1, 22)); + + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.OutKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "B"); + } + } + N(SyntaxKind.CloseParenToken); + } + } + EOF(); + } + + [Theory, CombinatorialData] + public void ParameterRefReadonly_09( + [CombinatorialValues(LanguageVersion.CSharp11, LanguageVersionFacts.CSharpNext, LanguageVersion.Preview)] LanguageVersion languageVersion) + { + UsingNode("A(ref readonly readonly B)", TestOptions.Regular.WithLanguageVersion(languageVersion).WithDocumentationMode(DocumentationMode.Diagnose), + // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'A(ref readonly readonly B)' + // /// + Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "A(ref readonly").WithArguments("A(ref readonly readonly B)").WithLocation(1, 16), + // (1,31): warning CS1658: Identifier expected; 'readonly' is a keyword. See also error CS1041. + // /// + Diagnostic(ErrorCode.WRN_ErrorOverride, "readonly").WithArguments("Identifier expected; 'readonly' is a keyword", "1041").WithLocation(1, 31), + // (1,31): warning CS1658: ) expected. See also error CS1026. + // /// + Diagnostic(ErrorCode.WRN_ErrorOverride, "readonly").WithArguments(") expected", "1026").WithLocation(1, 31)); + + N(SyntaxKind.NameMemberCref); + { + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "A"); + } + N(SyntaxKind.CrefParameterList); + { + N(SyntaxKind.OpenParenToken); + N(SyntaxKind.CrefParameter); + { + N(SyntaxKind.RefKeyword); + N(SyntaxKind.ReadOnlyKeyword); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + M(SyntaxKind.CloseParenToken); + } + } + EOF(); + } + + [Theory, CombinatorialData] + public void ParameterRefReadonly_10( + [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() { From e6309342a817742c7f0535818392350edd3decfa Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Fri, 21 Jul 2023 15:47:47 +0200 Subject: [PATCH 8/9] Turn errors into warnings for doc comments --- .../CSharp/Portable/Binder/Binder_Crefs.cs | 2 +- .../CSharp/Portable/Binder/Binder_Symbols.cs | 20 +++++++++++++------ .../Parser/DocumentationCommentParser.cs | 2 +- .../CSharp/Portable/Parser/SyntaxParser.cs | 5 +++++ .../Symbol/DocumentationComments/CrefTests.cs | 8 ++++---- .../Test/Syntax/Parsing/CrefParsingTests.cs | 8 ++++---- 6 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs index 9c40f14b6a474..8f66622c6de9b 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs @@ -935,7 +935,7 @@ private ImmutableArray BindCrefParameters(BaseCrefParameterList RefKind refKind = parameter.RefKindKeyword.Kind().GetRefKind(); if (refKind == RefKind.Ref && parameter.ReadOnlyKeyword.IsKind(SyntaxKind.ReadOnlyKeyword)) { - CheckFeatureAvailability(parameter.ReadOnlyKeyword, MessageID.IDS_FeatureRefReadonlyParameters, diagnostics); + CheckFeatureAvailability(parameter.ReadOnlyKeyword, MessageID.IDS_FeatureRefReadonlyParameters, diagnostics, forceWarning: true); refKind = RefKind.RefReadOnlyParameter; } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs index 12ffd218658da..043958a8adb76 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs @@ -2696,8 +2696,8 @@ protected AssemblySymbol GetForwardedToAssembly(string name, int arity, ref Name internal static bool CheckFeatureAvailability(SyntaxNode syntax, MessageID feature, BindingDiagnosticBag diagnostics, Location? location = null) => CheckFeatureAvailability(syntax, feature, diagnostics.DiagnosticBag, location); - internal static bool CheckFeatureAvailability(SyntaxToken syntax, MessageID feature, BindingDiagnosticBag diagnostics, Location? location = null) - => CheckFeatureAvailability(syntax, feature, diagnostics.DiagnosticBag, location); + internal static bool CheckFeatureAvailability(SyntaxToken syntax, MessageID feature, BindingDiagnosticBag diagnostics, Location? location = null, bool forceWarning = false) + => CheckFeatureAvailability(syntax, feature, diagnostics.DiagnosticBag, location, forceWarning: forceWarning); internal static bool CheckFeatureAvailability(SyntaxNodeOrToken syntax, MessageID feature, BindingDiagnosticBag diagnostics, Location? location = null) => CheckFeatureAvailability(syntax, feature, diagnostics.DiagnosticBag, location); @@ -2708,8 +2708,8 @@ internal static bool CheckFeatureAvailability(SyntaxTree tree, MessageID feature private static bool CheckFeatureAvailability(SyntaxNode syntax, MessageID feature, DiagnosticBag? diagnostics, Location? location = null) => CheckFeatureAvailability(syntax.SyntaxTree, feature, diagnostics, (location, syntax), static tuple => tuple.location ?? tuple.syntax.GetLocation()); - private static bool CheckFeatureAvailability(SyntaxToken syntax, MessageID feature, DiagnosticBag? diagnostics, Location? location = null) - => CheckFeatureAvailability(syntax.SyntaxTree!, feature, diagnostics, (location, syntax), static tuple => tuple.location ?? tuple.syntax.GetLocation()); + private static bool CheckFeatureAvailability(SyntaxToken syntax, MessageID feature, DiagnosticBag? diagnostics, Location? location = null, bool forceWarning = false) + => CheckFeatureAvailability(syntax.SyntaxTree!, feature, diagnostics, (location, syntax), static tuple => tuple.location ?? tuple.syntax.GetLocation(), forceWarning: forceWarning); private static bool CheckFeatureAvailability(SyntaxNodeOrToken syntax, MessageID feature, DiagnosticBag? diagnostics, Location? location = null) => CheckFeatureAvailability(syntax.SyntaxTree!, feature, diagnostics, (location, syntax), static tuple => tuple.location ?? tuple.syntax.GetLocation()!); @@ -2720,11 +2720,19 @@ private static bool CheckFeatureAvailability(SyntaxTree tree, MessageID feature, /// Callback function that computes the location to report the diagnostics at /// if a diagnostic should be reported. Should always be passed a static/cached callback to prevent /// allocations of the delegate. - private static bool CheckFeatureAvailability(SyntaxTree tree, MessageID feature, DiagnosticBag? diagnostics, TData data, Func getLocation) + private static bool CheckFeatureAvailability(SyntaxTree tree, MessageID feature, DiagnosticBag? diagnostics, TData data, Func getLocation, bool forceWarning = false) { if (feature.GetFeatureAvailabilityDiagnosticInfo((CSharpParseOptions)tree.Options) is { } diagInfo) { - diagnostics?.Add(diagInfo, getLocation(data)); + if (forceWarning) + { + diagnostics?.Add(ErrorCode.WRN_ErrorOverride, getLocation(data), diagInfo, (int)diagInfo.Code); + } + else + { + diagnostics?.Add(diagInfo, getLocation(data)); + } + return false; } return true; diff --git a/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs b/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs index e6b80f670a8bb..bea7e175cb401 100644 --- a/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/DocumentationCommentParser.cs @@ -1268,7 +1268,7 @@ private CrefParameterSyntax ParseCrefParameter() if (refKindOpt.Kind != SyntaxKind.RefKeyword) { // if we encounter `readonly` after `in` or `out`, we place the `readonly` as skipped trivia on the previous keyword - var misplacedToken = AddError(EatToken(), ErrorCode.ERR_RefReadOnlyWrongOrdering); + var misplacedToken = AddErrorAsWarning(EatToken(), ErrorCode.ERR_RefReadOnlyWrongOrdering); refKindOpt = AddTrailingSkippedSyntax(refKindOpt, misplacedToken); } else diff --git a/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs b/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs index 89acc5fa304d7..abee3af007509 100644 --- a/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs @@ -725,6 +725,11 @@ protected TNode AddError(TNode node, ErrorCode code) where TNode : GreenN return AddError(node, code, Array.Empty()); } + protected TNode AddErrorAsWarning(TNode node, ErrorCode code, params object[] args) where TNode : GreenNode + { + return AddError(node, ErrorCode.WRN_ErrorOverride, MakeError(node, code, args), (int)code); + } + protected TNode AddError(TNode node, ErrorCode code, params object[] args) where TNode : GreenNode { if (!node.IsMissing) diff --git a/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs b/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs index dc0f1ac46bb5d..4a2043275108d 100644 --- a/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/DocumentationComments/CrefTests.cs @@ -6732,9 +6732,9 @@ void S() // (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), - // (8,26): error CS8652: The feature 'ref readonly parameters' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // (8,26): warning CS1658: The feature 'ref readonly parameters' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.. See also error CS8652. // /// - Diagnostic(ErrorCode.ERR_FeatureInPreview, "readonly").WithArguments("ref readonly parameters").WithLocation(8, 26))); + Diagnostic(ErrorCode.WRN_ErrorOverride, "readonly").WithArguments("The feature 'ref readonly parameters' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.", "8652").WithLocation(8, 26))); verify(CreateCompilation(source, parseOptions: TestOptions.RegularNext.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics()); verify(CreateCompilation(source, parseOptions: TestOptions.RegularPreview.WithDocumentationMode(DocumentationMode.Diagnose)).VerifyDiagnostics()); @@ -6872,9 +6872,9 @@ void S() // (8,20): warning CS1574: XML comment has cref attribute 'M(ref readonly int)' that could not be resolved // /// Diagnostic(ErrorCode.WRN_BadXMLRef, "M(ref readonly int)").WithArguments("M(ref readonly int)").WithLocation(8, 20), - // (8,26): error CS8652: The feature 'ref readonly parameters' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // (8,26): warning CS1658: The feature 'ref readonly parameters' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.. See also error CS8652. // /// - Diagnostic(ErrorCode.ERR_FeatureInPreview, "readonly").WithArguments("ref readonly parameters").WithLocation(8, 26))); + Diagnostic(ErrorCode.WRN_ErrorOverride, "readonly").WithArguments("The feature 'ref readonly parameters' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.", "8652").WithLocation(8, 26))); var expectedDiagnostics = new[] { diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs index 5eddfbd92a09a..ac7a1beb0932e 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/CrefParsingTests.cs @@ -1524,9 +1524,9 @@ public void ParameterRefReadonly_07( // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'A(in readonly B)' // /// Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "A(in readonly B)").WithArguments("A(in readonly B)").WithLocation(1, 16), - // (1,21): error CS9501: 'readonly' modifier must be specified after 'ref'. + // (1,21): warning CS1658: 'readonly' modifier must be specified after 'ref'.. See also error CS9501. // /// - Diagnostic(ErrorCode.ERR_RefReadOnlyWrongOrdering, "readonly").WithLocation(1, 21)); + Diagnostic(ErrorCode.WRN_ErrorOverride, "readonly").WithArguments("'readonly' modifier must be specified after 'ref'.", "9501").WithLocation(1, 21)); N(SyntaxKind.NameMemberCref); { @@ -1559,9 +1559,9 @@ public void ParameterRefReadonly_08( // (1,16): warning CS1584: XML comment has syntactically incorrect cref attribute 'A(out readonly B)' // /// Diagnostic(ErrorCode.WRN_BadXMLRefSyntax, "A(out readonly B)").WithArguments("A(out readonly B)").WithLocation(1, 16), - // (1,22): error CS9501: 'readonly' modifier must be specified after 'ref'. + // (1,22): warning CS1658: 'readonly' modifier must be specified after 'ref'.. See also error CS9501. // /// - Diagnostic(ErrorCode.ERR_RefReadOnlyWrongOrdering, "readonly").WithLocation(1, 22)); + Diagnostic(ErrorCode.WRN_ErrorOverride, "readonly").WithArguments("'readonly' modifier must be specified after 'ref'.", "9501").WithLocation(1, 22)); N(SyntaxKind.NameMemberCref); { From e1a748062476da42e0cda2b1532d7c3caff5ee2a Mon Sep 17 00:00:00 2001 From: Jan Jones Date: Fri, 21 Jul 2023 16:07:16 +0200 Subject: [PATCH 9/9] Assert that errors are not added to missing nodes via the new overload --- src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs b/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs index abee3af007509..c7b13e3b85852 100644 --- a/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/SyntaxParser.cs @@ -727,6 +727,7 @@ protected TNode AddError(TNode node, ErrorCode code) where TNode : GreenN protected TNode AddErrorAsWarning(TNode node, ErrorCode code, params object[] args) where TNode : GreenNode { + Debug.Assert(!node.IsMissing); return AddError(node, ErrorCode.WRN_ErrorOverride, MakeError(node, code, args), (int)code); }