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
13 changes: 11 additions & 2 deletions src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2169,6 +2169,7 @@ public static ExpressionSyntax GetStandaloneExpression(ExpressionSyntax expressi
case SyntaxKind.IndexerMemberCref:
case SyntaxKind.OperatorMemberCref:
case SyntaxKind.ConversionOperatorMemberCref:
case SyntaxKind.ExtensionMemberCref:
case SyntaxKind.ArrayType:
case SyntaxKind.NullableType:
// Adjustment may be required.
Expand Down Expand Up @@ -2225,8 +2226,8 @@ public static ExpressionSyntax GetStandaloneExpression(ExpressionSyntax expressi
if (((NameMemberCrefSyntax)parent).Name == node)
{
CSharpSyntaxNode? grandparent = parent.Parent;
return grandparent != null && grandparent.Kind() == SyntaxKind.QualifiedCref
? grandparent
return grandparent != null && grandparent is CrefSyntax
? GetStandaloneNode(grandparent)
: parent;
}

Expand All @@ -2240,6 +2241,14 @@ public static ExpressionSyntax GetStandaloneExpression(ExpressionSyntax expressi

break;

case SyntaxKind.ExtensionMemberCref:
if (((ExtensionMemberCrefSyntax)parent).Member == node)
{
return GetStandaloneNode(parent);
}

break;

case SyntaxKind.ArrayCreationExpression:
if (((ArrayCreationExpressionSyntax)parent).Type == node)
{
Expand Down
116 changes: 116 additions & 0 deletions src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7160,6 +7160,122 @@ class Nested
Diagnostic(ErrorCode.WRN_BadXMLRef, "extension(int).M()").WithArguments("extension(int).M()").WithLocation(10, 28));
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81710")]
public void Cref_68()
{
var src = """
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi Dec 16, 2025

Choose a reason for hiding this comment

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

raw string? #Resolved

Copy link
Contributor

Choose a reason for hiding this comment

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

(indented raw string, rather).

Copy link
Member Author

Choose a reason for hiding this comment

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

The formatting is intentional

/// <see cref="E.extension(int).M(string)"/>
Copy link
Contributor

@AlekseyTs AlekseyTs Dec 17, 2025

Choose a reason for hiding this comment

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

E

Consider testing with additional qualifications (more dotted parts) #Closed

static class E
{
extension(int i)
{
public void M(string s) => throw null!;
}
}
""";
var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments);
comp.VerifyEmitDiagnostics();

var tree = comp.SyntaxTrees.Single();
var extensionCref = GetSyntax<ExtensionMemberCrefSyntax>(tree, "extension(int).M(string)", descendIntoTrivia: true);
var model = comp.GetSemanticModel(tree);
AssertEx.Equal("E.extension(int).M(string)", model.GetSymbolInfo(extensionCref).Symbol.ToDisplayString());
AssertEx.Equal("E.extension(int).M(string)", model.GetSymbolInfo(extensionCref.Member).Symbol.ToDisplayString());

var m = ((NameMemberCrefSyntax)extensionCref.Member).Name;
Copy link
Contributor

@AlekseyTs AlekseyTs Dec 17, 2025

Choose a reason for hiding this comment

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

m

Consider also testing for extensionCref.Member #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks. That uncovered another problem

Assert.Equal("M", m.ToString());
Copy link
Member

@jjonescz jjonescz Dec 17, 2025

Choose a reason for hiding this comment

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

Consider asserting those found symbols are the same (reference equals) between themselves and also with the E.M symbol found outside the doc comment. #ByDesign

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, I don't think there's a requirement that the symbols from semantic model be reference equal. Definitions should be reference equal, but that's not about the semantic model and here we're not dealing with definitions in this test anyways.

AssertEx.Equal("E.extension(int).M(string)", model.GetSymbolInfo(m).Symbol.ToDisplayString());
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81710")]
public void Cref_69()
{
var src = """
/// <see cref="E.extension(int).Property"/>
static class E
{
extension(int i)
{
public int Property => throw null!;
}
}
""";
var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments);
comp.VerifyEmitDiagnostics();

var tree = comp.SyntaxTrees.Single();
var extensionCref = GetSyntax<ExtensionMemberCrefSyntax>(tree, "extension(int).Property", descendIntoTrivia: true);
var model = comp.GetSemanticModel(tree);
AssertEx.Equal("E.extension(int).Property", model.GetSymbolInfo(extensionCref).Symbol.ToDisplayString());
AssertEx.Equal("E.extension(int).Property", model.GetSymbolInfo(extensionCref.Member).Symbol.ToDisplayString());
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81710")]
public void Cref_70()
{
var src = """
namespace N;

/// <see cref="N.E.extension(int).M(string)"/>
static class E
{
extension(int i)
{
public void M(string s) => throw null!;
}
}
""";
var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments);
comp.VerifyEmitDiagnostics();

var tree = comp.SyntaxTrees.Single();
var model = comp.GetSemanticModel(tree);

var qualifiedCref = GetSyntax<QualifiedCrefSyntax>(tree, "N.E.extension(int).M(string)", descendIntoTrivia: true);
AssertEx.Equal("N.E.extension(int).M(string)", model.GetSymbolInfo(qualifiedCref).Symbol.ToDisplayString());

var extensionCref = GetSyntax<ExtensionMemberCrefSyntax>(tree, "extension(int).M(string)", descendIntoTrivia: true);
AssertEx.Equal("N.E.extension(int).M(string)", model.GetSymbolInfo(extensionCref).Symbol.ToDisplayString());
AssertEx.Equal("N.E.extension(int).M(string)", model.GetSymbolInfo(extensionCref.Member).Symbol.ToDisplayString());

var m = ((NameMemberCrefSyntax)extensionCref.Member).Name;
Assert.Equal("M", m.ToString());
AssertEx.Equal("N.E.extension(int).M(string)", model.GetSymbolInfo(m).Symbol.ToDisplayString());
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81710")]
public void Cref_71()
{
var src = """
/// <see cref="extension(int).M(string)"/>
extension(int i)
{
public void M(string s) => throw null!;
}
""";
var comp = CreateCompilation(src, parseOptions: TestOptions.RegularPreviewWithDocumentationComments);
comp.VerifyEmitDiagnostics(
// (1,16): warning CS1574: XML comment has cref attribute 'extension(int).M(string)' that could not be resolved
// /// <see cref="extension(int).M(string)"/>
Diagnostic(ErrorCode.WRN_BadXMLRef, "extension(int).M(string)").WithArguments("extension(int).M(string)").WithLocation(1, 16),
// (2,1): error CS9283: Extensions must be declared in a top-level, non-generic, static class
// extension(int i)
Diagnostic(ErrorCode.ERR_BadExtensionContainingType, "extension").WithLocation(2, 1),
// (4,17): warning CS1591: Missing XML comment for publicly visible type or member 'extension(int).M(string)'
// public void M(string s) => throw null!;
Diagnostic(ErrorCode.WRN_MissingXMLComment, "M").WithArguments("extension(int).M(string)").WithLocation(4, 17));

var tree = comp.SyntaxTrees.Single();
var model = comp.GetSemanticModel(tree);

var extensionCref = GetSyntax<ExtensionMemberCrefSyntax>(tree, "extension(int).M(string)", descendIntoTrivia: true);
Assert.Null(model.GetSymbolInfo(extensionCref).Symbol);
Assert.Null(model.GetSymbolInfo(extensionCref.Member).Symbol);

var m = ((NameMemberCrefSyntax)extensionCref.Member).Name;
Assert.Null(model.GetSymbolInfo(m).Symbol);
}

[Fact]
public void PropertyAccess_Set_01()
{
Expand Down
8 changes: 4 additions & 4 deletions src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1295,16 +1295,16 @@ namespace System.Runtime.CompilerServices;
public class RuntimeAsyncMethodGenerationAttribute(bool runtimeAsync) : Attribute();
""";

protected static T GetSyntax<T>(SyntaxTree tree, string text)
protected static T GetSyntax<T>(SyntaxTree tree, string text, bool descendIntoTrivia = false)
where T : notnull
{
return GetSyntaxes<T>(tree, text).Single();
return GetSyntaxes<T>(tree, text, descendIntoTrivia).Single();
}

protected static IEnumerable<T> GetSyntaxes<T>(SyntaxTree tree, string text)
protected static IEnumerable<T> GetSyntaxes<T>(SyntaxTree tree, string text, bool descendIntoTrivia = false)
where T : notnull
{
return tree.GetRoot().DescendantNodes().OfType<T>().Where(e => e.ToString() == text);
return tree.GetRoot().DescendantNodes(descendIntoTrivia: descendIntoTrivia).OfType<T>().Where(e => e.ToString() == text);
}

protected static CSharpCompilationOptions WithNullableEnable(CSharpCompilationOptions? options = null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,27 @@ public static class E
Await TestAPIAndFeature(input, kind, host)
End Function

<WpfTheory, CombinatorialData>
<WorkItem("https://github.com/dotnet/roslyn/issues/81710")>
Public Async Function FindReferences_ExtensionBlockMethod_Cref(kind As TestKind, host As TestHost) As Task
Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi Dec 17, 2025

Choose a reason for hiding this comment

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

nice. i'm glad this just fell out. #Resolved

Copy link
Contributor

@AlekseyTs AlekseyTs Dec 17, 2025

Choose a reason for hiding this comment

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

FindReferences_ExtensionBlockMethod_Cref

Consider covering all scenarios covered in added compiler test(s)

Copy link
Contributor

Choose a reason for hiding this comment

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

Consider covering all scenarios covered in added compiler test(s)

This thread was resolved, but I do not see any changes made in response. Since these are not compiler tests, I am going to sign off, but I will reactivate the thread in case you resolved it by mistake.

Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true" LanguageVersion="Preview">
<Document>
/// &lt;see cref="E.extension(int).[|M|]()"/>
Copy link
Member

@jjonescz jjonescz Dec 17, 2025

Choose a reason for hiding this comment

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

Consider wrapping the code in <![CDATA[]]> instead of xml-encoding parts of it. #ByDesign

Copy link
Member Author

Choose a reason for hiding this comment

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

I leaned towards this encoding

public static class E
{
extension(int i)
{
public void {|Definition:$$M|}() { }
}
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function

<WorkItem("https://github.com/dotnet/roslyn/issues/81507")>
<WpfTheory, CombinatorialData>
Public Async Function FindReferences_ExtensionBlockProperty(kind As TestKind, host As TestHost) As Task
Expand Down Expand Up @@ -429,6 +450,12 @@ class C7
{
_ = new C() { [|P|] = 1 };
}
}
</Document>
<Document>
/// &lt;see cref="E.extension(C).[|P|]"/>
class C8
{
}
</Document>
<Document>
Expand All @@ -445,6 +472,27 @@ public static class E
Await TestAPIAndFeature(input, kind, host)
End Function

<WorkItem("https://github.com/dotnet/roslyn/issues/81710")>
<WpfTheory, CombinatorialData>
Public Async Function FindReferences_ExtensionBlockProperty_FromAccess_Cref(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true" LanguageVersion="Preview">
<Document>
/// &lt;see cref="E.extension(int).[|P|]"/>
public static class E
{
extension(int i)
{
public int {|Definition:$$P|} { get => i; set { } }
}
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function

<WorkItem("https://github.com/dotnet/roslyn/issues/81507")>
<WpfTheory, CombinatorialData>
Public Async Function FindReferences_ExtensionBlockProperty_FromImplementationMethodInvocation(kind As TestKind, host As TestHost) As Task
Expand Down Expand Up @@ -688,5 +736,25 @@ public static class E
Await TestAPIAndFeature(input, kind, host)
End Function

<WpfTheory, CombinatorialData>
Public Async Function FindReferences_ExtensionBlockMethod_GenericCref(kind As TestKind, host As TestHost) As Task
Dim input =
<Workspace>
<Project Language="C#" CommonReferences="true" LanguageVersion="Preview">
<Document>
/// &lt;see cref="E.extension{$${|Definition:U|}}([|U|]).M([|U|])"/>
public static class E
{
extension&lt;T>(T t1)
{
public void M(T t2) { }
}
}
</Document>
</Project>
</Workspace>
Await TestAPIAndFeature(input, kind, host)
End Function

End Class
End Namespace