Skip to content

Commit 5ca519f

Browse files
Add FAR support for modern-extension parameters (#77503)
Blocked on #77502 Relates to test plan #76130
2 parents 2d87b25 + 72af99a commit 5ca519f

File tree

4 files changed

+97
-44
lines changed

4 files changed

+97
-44
lines changed

src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ExtensionSymbols.vb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,56 @@ public static class MyExtension
9999
public int {|Definition:ExtensionProp|} => 0;
100100
}
101101
}
102+
]]>
103+
</Document>
104+
</Project>
105+
</Workspace>
106+
Await TestAPIAndFeature(input, kind, host)
107+
End Function
108+
109+
<WpfTheory, CombinatorialData>
110+
Public Async Function TestModernExtensionMethodParameter1(kind As TestKind, host As TestHost) As Task
111+
Dim input =
112+
<Workspace>
113+
<Project Language="C#" CommonReferences="true" LanguageVersion="preview">
114+
<Document><![CDATA[
115+
using System;
116+
117+
public static class MyExtension
118+
{
119+
extension(string $${|Definition:s|})
120+
{
121+
public int ExtensionMethod()
122+
{
123+
return [|s|].Length;
124+
}
125+
}
126+
}
127+
]]>
128+
</Document>
129+
</Project>
130+
</Workspace>
131+
Await TestAPIAndFeature(input, kind, host)
132+
End Function
133+
134+
<WpfTheory, CombinatorialData>
135+
Public Async Function TestModernExtensionMethodTypeParameter1(kind As TestKind, host As TestHost) As Task
136+
Dim input =
137+
<Workspace>
138+
<Project Language="C#" CommonReferences="true" LanguageVersion="preview">
139+
<Document><![CDATA[
140+
using System;
141+
142+
public static class MyExtension
143+
{
144+
extension<$${|Definition:T|}>(string s)
145+
{
146+
public int ExtensionMethod([|T|] t)
147+
{
148+
return s.Length;
149+
}
150+
}
151+
}
102152
]]>
103153
</Document>
104154
</Project>

src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/TypeParameterSymbolReferenceFinder.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ protected override Task DetermineDocumentsToSearchAsync<TData>(
3232
// parameter has a different name in different parts that we won't find it. However,
3333
// this only happens in error situations. It is not legal in C# to use a different
3434
// name for a type parameter in different parts.
35-
return FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name, symbol.ContainingType.Name);
35+
return symbol.ContainingType is { IsExtension: true, ContainingType.Name: var staticClassName }
36+
? FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name, staticClassName)
37+
: FindDocumentsAsync(project, documents, processResult, processResultData, cancellationToken, symbol.Name, symbol.ContainingType.Name);
3638
}
3739
}

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.NamedTypeSymbolKey.cs

Lines changed: 41 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using System;
65
using System.Collections.Immutable;
7-
using Roslyn.Utilities;
86

97
namespace Microsoft.CodeAnalysis;
108

@@ -19,21 +17,16 @@ public sealed override void Create(INamedTypeSymbol symbol, SymbolKeyWriter visi
1917
visitor.WriteSymbolKey(symbol.ContainingSymbol);
2018
visitor.WriteString(symbol.Name);
2119
visitor.WriteInteger(symbol.Arity);
22-
visitor.WriteString(symbol.IsFileLocal
23-
? symbol.DeclaringSyntaxReferences[0].SyntaxTree.FilePath
24-
: null);
20+
// Include the metadata name for extensions. We need this to uniquely find it when resolving.
21+
visitor.WriteString(symbol.IsExtension ? symbol.MetadataName : null);
22+
// Include the file path for 'file-local' types. We need this to uniquely find it when resolving.
23+
visitor.WriteString(symbol.IsFileLocal ? symbol.DeclaringSyntaxReferences[0].SyntaxTree.FilePath : null);
2524
visitor.WriteBoolean(symbol.IsUnboundGenericType);
2625
visitor.WriteBoolean(symbol.IsNativeIntegerType);
2726
visitor.WriteBoolean(symbol.SpecialType == SpecialType.System_IntPtr);
2827

29-
if (!symbol.Equals(symbol.ConstructedFrom) && !symbol.IsUnboundGenericType)
30-
{
31-
visitor.WriteSymbolKeyArray(symbol.TypeArguments);
32-
}
33-
else
34-
{
35-
visitor.WriteSymbolKeyArray(ImmutableArray<ITypeSymbol>.Empty);
36-
}
28+
visitor.WriteSymbolKeyArray(
29+
symbol.Equals(symbol.ConstructedFrom) || symbol.IsUnboundGenericType ? [] : symbol.TypeArguments);
3730
}
3831

3932
protected sealed override SymbolKeyResolution Resolve(
@@ -42,6 +35,7 @@ protected sealed override SymbolKeyResolution Resolve(
4235
var containingSymbolResolution = reader.ReadSymbolKey(contextualSymbol?.ContainingSymbol, out var containingSymbolFailureReason);
4336
var name = reader.ReadRequiredString();
4437
var arity = reader.ReadInteger();
38+
var extensionMetadataName = reader.ReadString();
4539
var filePath = reader.ReadString();
4640
var isUnboundGenericType = reader.ReadBoolean();
4741
var isNativeIntegerType = reader.ReadBoolean();
@@ -73,7 +67,8 @@ protected sealed override SymbolKeyResolution Resolve(
7367

7468
var normalResolution = ResolveNormalNamedType(
7569
containingSymbolResolution, containingSymbolFailureReason,
76-
name, arity, filePath, isUnboundGenericType, typeArgumentsArray,
70+
name, extensionMetadataName, arity, filePath,
71+
isUnboundGenericType, typeArgumentsArray,
7772
out failureReason);
7873

7974
if (normalResolution.SymbolCount > 0)
@@ -140,10 +135,11 @@ private static SymbolKeyResolution ResolveNormalNamedType(
140135
SymbolKeyResolution containingSymbolResolution,
141136
string? containingSymbolFailureReason,
142137
string name,
138+
string? extensionMetadataName,
143139
int arity,
144140
string? filePath,
145141
bool isUnboundGenericType,
146-
ITypeSymbol[] typeArgumentsArray,
142+
ITypeSymbol[] typeArguments,
147143
out string? failureReason)
148144
{
149145
if (containingSymbolFailureReason != null)
@@ -154,43 +150,46 @@ private static SymbolKeyResolution ResolveNormalNamedType(
154150

155151
using var result = PooledArrayBuilder<INamedTypeSymbol>.GetInstance();
156152
foreach (var nsOrType in containingSymbolResolution.OfType<INamespaceOrTypeSymbol>())
157-
{
158-
Resolve(
159-
result, nsOrType, name, arity, filePath,
160-
isUnboundGenericType, typeArgumentsArray);
161-
}
153+
Resolve(nsOrType, result);
162154

163155
return CreateResolution(result, $"({nameof(NamedTypeSymbolKey)} failed)", out failureReason);
164-
}
165156

166-
private static void Resolve(
167-
PooledArrayBuilder<INamedTypeSymbol> result,
168-
INamespaceOrTypeSymbol container,
169-
string name,
170-
int arity,
171-
string? filePath,
172-
bool isUnboundGenericType,
173-
ITypeSymbol[] typeArguments)
174-
{
175-
foreach (var type in container.GetTypeMembers(name, arity))
157+
void Resolve(
158+
INamespaceOrTypeSymbol container,
159+
PooledArrayBuilder<INamedTypeSymbol> result)
176160
{
177-
// if this is a file-local type, then only resolve to a file-local type from this same file
178-
if (filePath != null)
161+
if (extensionMetadataName != null)
179162
{
180-
if (!type.IsFileLocal ||
181-
// note: if we found 'IsFile' returned true, we can assume DeclaringSyntaxReferences is non-empty.
182-
type.DeclaringSyntaxReferences[0].SyntaxTree.FilePath != filePath)
163+
// Unfortunately, no fast index from metadata name to type, so we have to iterate all nested types.
164+
foreach (var type in container.GetTypeMembers())
183165
{
184-
continue;
166+
if (type.MetadataName == extensionMetadataName)
167+
result.AddIfNotNull(Construct(type, isUnboundGenericType, typeArguments));
185168
}
186169
}
187-
else if (type.IsFileLocal)
170+
else
188171
{
189-
// since this key lacks a file path it can't match against a file-local type
190-
continue;
172+
foreach (var type in container.GetTypeMembers(name, arity))
173+
{
174+
// if this is a file-local type, then only resolve to a file-local type from this same file
175+
if (filePath != null)
176+
{
177+
if (!type.IsFileLocal ||
178+
// note: if we found 'IsFile' returned true, we can assume DeclaringSyntaxReferences is non-empty.
179+
type.DeclaringSyntaxReferences[0].SyntaxTree.FilePath != filePath)
180+
{
181+
continue;
182+
}
183+
}
184+
else if (type.IsFileLocal)
185+
{
186+
// since this key lacks a file path it can't match against a file-local type
187+
continue;
188+
}
189+
190+
result.AddIfNotNull(Construct(type, isUnboundGenericType, typeArguments));
191+
}
191192
}
192-
193-
result.AddIfNotNull(Construct(type, isUnboundGenericType, typeArguments));
194193
}
195194
}
196195

src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.ParameterSymbolKey.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Collections.Immutable;
6-
using System.Diagnostics;
76

87
namespace Microsoft.CodeAnalysis;
98

@@ -69,6 +68,9 @@ protected sealed override SymbolKeyResolution Resolve(
6968
Resolve(result, reader, metadataName, ordinal, delegateInvoke.Parameters);
7069
}
7170

71+
break;
72+
case INamedTypeSymbol { IsExtension: true, ExtensionParameter: { } extensionParameter }:
73+
Resolve(result, reader, metadataName, ordinal, [extensionParameter]);
7274
break;
7375
}
7476
}

0 commit comments

Comments
 (0)