diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs index a84a68327cc4c..bec14793b4f3a 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.BinderFactoryVisitor.cs @@ -1175,9 +1175,11 @@ public override Binder VisitXmlNameAttribute(XmlNameAttributeSyntax parent) switch (elementKind) { case XmlNameAttributeElementKind.Parameter: - case XmlNameAttributeElementKind.ParameterReference: extraInfo = NodeUsage.DocumentationCommentParameter; break; + case XmlNameAttributeElementKind.ParameterReference: + extraInfo = NodeUsage.DocumentationCommentParameterReference; + break; case XmlNameAttributeElementKind.TypeParameter: extraInfo = NodeUsage.DocumentationCommentTypeParameter; break; @@ -1236,7 +1238,7 @@ public override Binder VisitXmlNameAttribute(XmlNameAttributeSyntax parent) /// private Binder GetParameterNameAttributeValueBinder(MemberDeclarationSyntax memberSyntax, bool isParamRef, Binder nextBinder) { - if (memberSyntax is BaseMethodDeclarationSyntax { ParameterList: { ParameterCount: > 0 } } baseMethodDeclSyntax) + if (memberSyntax is BaseMethodDeclarationSyntax baseMethodDeclSyntax) { Binder outerBinder = VisitCore(memberSyntax.Parent); MethodSymbol method = GetMethodSymbol(baseMethodDeclSyntax, outerBinder); @@ -1246,7 +1248,14 @@ private Binder GetParameterNameAttributeValueBinder(MemberDeclarationSyntax memb nextBinder = new WithExtensionParameterBinder(method.ContainingType, nextBinder); } - return new WithParametersBinder(method.Parameters, nextBinder); + if (method.ParameterCount > 0) + { + return new WithParametersBinder(method.Parameters, nextBinder); + } + else + { + return nextBinder; + } } else if (memberSyntax is ExtensionBlockDeclarationSyntax extensionDeclaration) { diff --git a/src/Compilers/CSharp/Portable/Binder/BinderFactory.NodeUsage.cs b/src/Compilers/CSharp/Portable/Binder/BinderFactory.NodeUsage.cs index 6f97d8ae01491..19f7da4bad93d 100644 --- a/src/Compilers/CSharp/Portable/Binder/BinderFactory.NodeUsage.cs +++ b/src/Compilers/CSharp/Portable/Binder/BinderFactory.NodeUsage.cs @@ -27,8 +27,9 @@ internal enum NodeUsage : byte CompilationUnitScriptUsings = 1 << 2, DocumentationCommentParameter = 1 << 0, - DocumentationCommentTypeParameter = 1 << 1, - DocumentationCommentTypeParameterReference = 1 << 2, + DocumentationCommentParameterReference = 1 << 1, + DocumentationCommentTypeParameter = 1 << 2, + DocumentationCommentTypeParameterReference = 1 << 3, CrefParameterOrReturnType = 1 << 0, } diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index 9c0dd360ae364..29a536b9b0ae0 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -51327,6 +51327,10 @@ static class E // (9,30): warning CS1573: Parameter 'o2' has no matching param tag in the XML comment for 'E.extension(object).M(object)' (but other parameters do) // public void M(object o2) => throw null!; Diagnostic(ErrorCode.WRN_MissingParamTag, "o2").WithArguments("o2", "E.extension(object).M(object)").WithLocation(9, 30)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.SequenceEqual(["(o, null)"], PrintXmlNameSymbols(tree, model)); } [Fact] @@ -51347,6 +51351,10 @@ static class E """; var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.SequenceEqual(["(o, System.Object o)"], PrintXmlNameSymbols(tree, model)); } [Fact] @@ -51465,7 +51473,7 @@ static class E /// Summary for extension block extension(T t) { - /// Summary for M + /// Summary for M /// Description for T public static void M(U u) => throw null!; } @@ -51691,6 +51699,86 @@ public static int P4 { set { } } Diagnostic(ErrorCode.WRN_UnmatchedParamRefTag, "value").WithArguments("value", "E.extension(object).P2").WithLocation(8, 53)); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81217")] + public void XmlDoc_ParamRef_04() + { + // No parameter on method + var src = """ +static class E +{ + extension(object o) + { + /// + public object M() => o; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments, + assemblyName: "paramref_04"); + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.SequenceEqual(["(o, System.Object o)"], PrintXmlNameSymbols(tree, model)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81217")] + public void XmlDoc_ParamRef_05() + { + // One parameter on method + var src = """ +static class E +{ + extension(object o) + { + /// + public object M(int i) => o; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments, + assemblyName: "paramref_05"); + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.SequenceEqual(["(o, System.Object o)"], PrintXmlNameSymbols(tree, model)); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81217")] + public void XmlDoc_ParamRef_06() + { + // preceeds + var src = """ +using System; + +static class E +{ + /// Param value + extension(ReadOnlySpan value) + { + /// Param n + /// Param delimiter + /// + public ReadOnlySpan GetNthDelimitedItem(int n, ReadOnlySpan delimiter) => throw null !; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments, + assemblyName: "paramref_06", targetFramework: TargetFramework.Net100); + + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.SequenceEqual([ + "(value, System.ReadOnlySpan value)", + "(n, System.Int32 n)", + "(delimiter, System.ReadOnlySpan delimiter)", + "(value, System.ReadOnlySpan value)"], + PrintXmlNameSymbols(tree, model)); + } + [Fact] public void XmlDoc_TypeParamRef_01() { @@ -51747,6 +51835,73 @@ static class E AssertEx.SequenceEqual(["(T, null)", "(T, T)"], PrintXmlNameSymbols(tree, model)); } + [Fact] + public void XmlDoc_TypeParamRef_03() + { + var src = """ +static class E +{ + /// + extension(int) + { + /// + /// + public static void M() => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.SequenceEqual(["(T1, T1)", "(T1, T1)", "(T2, T2)"], PrintXmlNameSymbols(tree, model)); + } + + [Fact] + public void XmlDoc_TypeParamRef_04() + { + var src = """ +static class E +{ + /// + extension(int) + { + /// + public static void M() => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.SequenceEqual(["(T1, T1)", "(T1, T1)"], PrintXmlNameSymbols(tree, model)); + } + + [Fact] + public void XmlDoc_TypeParamRef_05() + { + var src = """ +static class E +{ + /// + extension(T1) + { + /// + public static int Property => throw null!; + } +} +"""; + var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments); + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + AssertEx.SequenceEqual(["(T1, T1)", "(T1, T1)"], PrintXmlNameSymbols(tree, model)); + } + [Fact] public void AnalyzerActions_01() {