Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1236,7 +1238,7 @@ public override Binder VisitXmlNameAttribute(XmlNameAttributeSyntax parent)
/// </summary>
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);
Expand All @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down
157 changes: 156 additions & 1 deletion src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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]
Expand Down Expand Up @@ -51465,7 +51473,7 @@ static class E
/// <summary>Summary for extension block</summary>
extension<T>(T t)
{
/// <summary>Summary for M</summary>
/// <summary>Summary for M <typeparamref name="T"/> </summary>
/// <typeparam name="T">Description for T</typeparam>
public static void M<U>(U u) => throw null!;
}
Expand Down Expand Up @@ -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)
{
/// <returns><paramref name="o"/></returns>
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)
{
/// <summary><paramref name="o"/></summary>
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()
{
// <params> preceeds <paramref>
var src = """
using System;

static class E
{
/// <param name="value">Param value</param>
extension(ReadOnlySpan<char> value)
{
/// <param name="n">Param n</param>
/// <param name="delimiter">Param delimiter</param>
/// <returns><paramref name="value"/></returns>
public ReadOnlySpan<char> GetNthDelimitedItem(int n, ReadOnlySpan<char> 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<System.Char> value)",
"(n, System.Int32 n)",
"(delimiter, System.ReadOnlySpan<System.Char> delimiter)",
"(value, System.ReadOnlySpan<System.Char> value)"],
PrintXmlNameSymbols(tree, model));
}

[Fact]
public void XmlDoc_TypeParamRef_01()
{
Expand Down Expand Up @@ -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
{
/// <typeparam name="T1"/>
extension<T1>(int)
{
/// <summary><typeparamref name="T1"/></summary>
Copy link
Contributor

@AlekseyTs AlekseyTs Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

///

Do we have a similar test with a non generic method? With a property? #Closed

/// <typeparam name="T2"/>
public static void M<T2>() => 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
{
/// <typeparam name="T1"/>
extension<T1>(int)
{
/// <summary><typeparamref name="T1"/></summary>
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
{
/// <typeparam name="T1"/>
extension<T1>(T1)
{
/// <summary><typeparamref name="T1"/></summary>
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()
{
Expand Down
Loading