Skip to content

Commit 53b193b

Browse files
RikkiGibsonCyrusNajmabadiYoussef1313
authored
File types IDE changes (#62215)
Co-authored-by: Cyrus Najmabadi <[email protected]> Co-authored-by: Youssef Victor <[email protected]>
1 parent a540733 commit 53b193b

File tree

18 files changed

+523
-22
lines changed

18 files changed

+523
-22
lines changed

src/Compilers/CSharp/Portable/Symbols/PublicModel/NamedTypeSymbol.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ INamedTypeSymbol INamedTypeSymbol.TupleUnderlyingType
194194

195195
bool INamedTypeSymbol.IsSerializable => UnderlyingNamedTypeSymbol.IsSerializable;
196196

197+
bool INamedTypeSymbol.IsFile => UnderlyingNamedTypeSymbol.AssociatedSyntaxTree is not null;
198+
197199
INamedTypeSymbol INamedTypeSymbol.NativeIntegerUnderlyingType => UnderlyingNamedTypeSymbol.NativeIntegerUnderlyingType.GetPublicSymbol();
198200

199201
#region ISymbol Members

src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3245,9 +3245,22 @@ void M(C c)
32453245
var tree = comp.SyntaxTrees[0];
32463246
var model = comp.GetSemanticModel(tree);
32473247
var node = tree.GetRoot().DescendantNodes().OfType<ParameterSyntax>().Single();
3248-
var type = model.GetTypeInfo(node.Type!).Type;
3248+
var type = (INamedTypeSymbol)model.GetTypeInfo(node.Type!).Type!;
32493249
Assert.Equal("C@<tree 0>", type.ToTestDisplayString());
3250-
Assert.Equal(tree, type.GetSymbol<NamedTypeSymbol>()!.AssociatedSyntaxTree);
3250+
Assert.Equal(tree, type.GetSymbol()!.AssociatedSyntaxTree);
3251+
Assert.True(type.IsFile);
3252+
3253+
var referencingMetadataComp = CreateCompilation("", new[] { comp.ToMetadataReference() });
3254+
type = ((Compilation)referencingMetadataComp).GetTypeByMetadataName("<>F0__C")!;
3255+
Assert.Equal("C@<tree 0>", type.ToTestDisplayString());
3256+
Assert.Equal(tree, type.GetSymbol()!.AssociatedSyntaxTree);
3257+
Assert.True(type.IsFile);
3258+
3259+
var referencingImageComp = CreateCompilation("", new[] { comp.EmitToImageReference() });
3260+
type = ((Compilation)referencingImageComp).GetTypeByMetadataName("<>F0__C")!;
3261+
Assert.Equal("<>F0__C", type.ToTestDisplayString());
3262+
Assert.Null(type.GetSymbol()!.AssociatedSyntaxTree);
3263+
Assert.False(type.IsFile);
32513264
}
32523265

32533266
[Fact]
@@ -3267,9 +3280,10 @@ void M(C c)
32673280
var tree = comp.SyntaxTrees[0];
32683281
var model = comp.GetSemanticModel(tree);
32693282
var node = tree.GetRoot().DescendantNodes().OfType<ParameterSyntax>().Single();
3270-
var type = model.GetTypeInfo(node.Type!).Type;
3283+
var type = (INamedTypeSymbol)model.GetTypeInfo(node.Type!).Type!;
32713284
Assert.Equal("C", type.ToTestDisplayString());
3272-
Assert.Null(type.GetSymbol<NamedTypeSymbol>()!.AssociatedSyntaxTree);
3285+
Assert.Null(type.GetSymbol()!.AssociatedSyntaxTree);
3286+
Assert.False(type.IsFile);
32733287
}
32743288

32753289
[Fact]
@@ -3289,8 +3303,9 @@ void M(C<int> c)
32893303
var tree = comp.SyntaxTrees[0];
32903304
var model = comp.GetSemanticModel(tree);
32913305
var node = tree.GetRoot().DescendantNodes().OfType<ParameterSyntax>().Single();
3292-
var type = model.GetTypeInfo(node.Type!).Type;
3306+
var type = (INamedTypeSymbol)model.GetTypeInfo(node.Type!).Type!;
32933307
Assert.Equal("C<System.Int32>@<tree 0>", type.ToTestDisplayString());
3294-
Assert.Equal(tree, type.GetSymbol<NamedTypeSymbol>()!.AssociatedSyntaxTree);
3308+
Assert.Equal(tree, type.GetSymbol()!.AssociatedSyntaxTree);
3309+
Assert.True(type.IsFile);
32953310
}
32963311
}

src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ public interface INamedTypeSymbol : ITypeSymbol
5656
/// </summary>
5757
bool IsComImport { get; }
5858

59+
/// <summary>
60+
/// Indicates the type is declared in source and is only visible in the file it is declared in.
61+
/// </summary>
62+
bool IsFile { get; }
63+
5964
/// <summary>
6065
/// Returns collection of names of members declared within this type.
6166
/// </summary>

src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,6 +1216,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols
12161216
End Get
12171217
End Property
12181218

1219+
Private ReadOnly Property INamedTypeSymbol_IsFile As Boolean Implements INamedTypeSymbol.IsFile
1220+
Get
1221+
Return False
1222+
End Get
1223+
End Property
1224+
12191225
Private ReadOnly Property INamedTypeSymbol_NativeIntegerUnderlyingType As INamedTypeSymbol Implements INamedTypeSymbol.NativeIntegerUnderlyingType
12201226
Get
12211227
Return Nothing

src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,5 +684,151 @@ class C
684684

685685
await VerifyItemIsAbsentAsync(markup, "required");
686686
}
687+
688+
[Fact]
689+
public async Task SuggestFileOnTypes()
690+
{
691+
var markup = $$"""
692+
$$ class C { }
693+
""";
694+
695+
await VerifyItemExistsAsync(markup, "file");
696+
}
697+
698+
[Fact]
699+
public async Task DoNotSuggestFileAfterFile()
700+
{
701+
var markup = $$"""
702+
file $$
703+
""";
704+
705+
await VerifyItemIsAbsentAsync(markup, "file");
706+
}
707+
708+
[Fact]
709+
public async Task SuggestFileAfterReadonly()
710+
{
711+
// e.g. 'readonly file struct X { }'
712+
var markup = $$"""
713+
readonly $$
714+
""";
715+
716+
await VerifyItemExistsAsync(markup, "file");
717+
}
718+
719+
[Fact]
720+
public async Task SuggestFileBeforeFileType()
721+
{
722+
var markup = $$"""
723+
$$
724+
725+
file class C { }
726+
""";
727+
728+
// it might seem like we want to prevent 'file file class',
729+
// but it's likely the user is declaring a file type above an existing file type here.
730+
await VerifyItemExistsAsync(markup, "file");
731+
}
732+
733+
[Fact]
734+
public async Task SuggestFileBeforeDelegate()
735+
{
736+
var markup = $$"""
737+
$$ delegate
738+
""";
739+
740+
await VerifyItemExistsAsync(markup, "file");
741+
}
742+
743+
[Fact]
744+
public async Task DoNotSuggestFileOnNestedTypes()
745+
{
746+
var markup = $$"""
747+
class Outer
748+
{
749+
$$ class C { }
750+
}
751+
""";
752+
753+
await VerifyItemIsAbsentAsync(markup, "file");
754+
}
755+
756+
[Fact]
757+
public async Task DoNotSuggestFileOnNonTypeMembers()
758+
{
759+
var markup = $$"""
760+
class C
761+
{
762+
$$
763+
}
764+
""";
765+
766+
await VerifyItemIsAbsentAsync(markup, "file");
767+
}
768+
769+
[Theory]
770+
[InlineData("public")]
771+
[InlineData("internal")]
772+
[InlineData("protected")]
773+
[InlineData("private")]
774+
public async Task DoNotSuggestFileAfterFilteredKeywords(string keyword)
775+
{
776+
var markup = $$"""
777+
{{keyword}} $$
778+
""";
779+
780+
await VerifyItemIsAbsentAsync(markup, "file");
781+
}
782+
783+
[Theory]
784+
[InlineData("public")]
785+
[InlineData("internal")]
786+
[InlineData("protected")]
787+
[InlineData("private")]
788+
public async Task DoNotSuggestFilteredKeywordsAfterFile(string keyword)
789+
{
790+
var markup = $$"""
791+
file $$
792+
""";
793+
794+
await VerifyItemIsAbsentAsync(markup, keyword);
795+
}
796+
797+
[Fact]
798+
public async Task SuggestFileInFileScopedNamespace()
799+
{
800+
var markup = $$"""
801+
namespace NS;
802+
803+
$$
804+
""";
805+
806+
await VerifyItemExistsAsync(markup, "file");
807+
}
808+
809+
[Fact]
810+
public async Task SuggestFileInNamespace()
811+
{
812+
var markup = $$"""
813+
namespace NS
814+
{
815+
$$
816+
}
817+
""";
818+
819+
await VerifyItemExistsAsync(markup, "file");
820+
}
821+
822+
[Fact]
823+
public async Task SuggestFileAfterClass()
824+
{
825+
var markup = $$"""
826+
file class C { }
827+
828+
$$
829+
""";
830+
831+
await VerifyItemExistsAsync(markup, "file");
832+
}
687833
}
688834
}

src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyCompilationsTests.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,98 @@ public void M<Z>(List<Z> list)
239239
ResolveAndVerifySymbolList(members1, members2, comp1);
240240
}
241241

242+
[Fact]
243+
public void FileType1()
244+
{
245+
var src1 = @"using System;
246+
247+
namespace N1.N2
248+
{
249+
file class C { }
250+
}
251+
";
252+
var originalComp = CreateCompilation(src1, assemblyName: "Test");
253+
var newComp = CreateCompilation(src1, assemblyName: "Test");
254+
255+
var originalSymbols = GetSourceSymbols(originalComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray();
256+
var newSymbols = GetSourceSymbols(newComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray();
257+
258+
Assert.Equal(3, originalSymbols.Length);
259+
ResolveAndVerifySymbolList(newSymbols, originalSymbols, originalComp);
260+
}
261+
262+
[Fact]
263+
public void FileType2()
264+
{
265+
var src1 = @"using System;
266+
267+
namespace N1.N2
268+
{
269+
file class C<T> { }
270+
}
271+
";
272+
var originalComp = CreateCompilation(src1, assemblyName: "Test");
273+
var newComp = CreateCompilation(src1, assemblyName: "Test");
274+
275+
var originalSymbols = GetSourceSymbols(originalComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray();
276+
var newSymbols = GetSourceSymbols(newComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray();
277+
278+
Assert.Equal(3, originalSymbols.Length);
279+
ResolveAndVerifySymbolList(newSymbols, originalSymbols, originalComp);
280+
}
281+
282+
[Fact]
283+
public void FileType3()
284+
{
285+
var src1 = @"using System;
286+
287+
namespace N1.N2
288+
{
289+
file class C { }
290+
}
291+
";
292+
// this should result in two entirely separate file symbols.
293+
// note that the IDE can only distinguish file type symbols with the same name when they have distinct file paths.
294+
// We are OK with this as we will require file types with identical names to have distinct file paths later in the preview.
295+
// See https://github.com/dotnet/roslyn/issues/61999
296+
var originalComp = CreateCompilation(new[] { SyntaxFactory.ParseSyntaxTree(src1, path: "file1.cs"), SyntaxFactory.ParseSyntaxTree(src1, path: "file2.cs") }, assemblyName: "Test");
297+
var newComp = CreateCompilation(new[] { SyntaxFactory.ParseSyntaxTree(src1, path: "file1.cs"), SyntaxFactory.ParseSyntaxTree(src1, path: "file2.cs") }, assemblyName: "Test");
298+
299+
var originalSymbols = GetSourceSymbols(originalComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray();
300+
var newSymbols = GetSourceSymbols(newComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray();
301+
302+
Assert.Equal(4, originalSymbols.Length);
303+
ResolveAndVerifySymbolList(newSymbols, originalSymbols, originalComp);
304+
}
305+
306+
[Fact]
307+
public void FileType4()
308+
{
309+
// we should be able to distinguish a file type and non-file type when they have the same source name.
310+
var src1 = SyntaxFactory.ParseSyntaxTree(@"using System;
311+
312+
namespace N1.N2
313+
{
314+
file class C { }
315+
}
316+
", path: "File1.cs");
317+
318+
var src2 = SyntaxFactory.ParseSyntaxTree(@"
319+
namespace N1.N2
320+
{
321+
class C { }
322+
}
323+
", path: "File2.cs");
324+
var originalComp = CreateCompilation(new[] { src1, src2 }, assemblyName: "Test");
325+
var newComp = CreateCompilation(new[] { src1, src2 }, assemblyName: "Test");
326+
327+
var originalSymbols = GetSourceSymbols(originalComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray();
328+
var newSymbols = GetSourceSymbols(newComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray();
329+
330+
Assert.Equal(4, originalSymbols.Length);
331+
ResolveAndVerifySymbolList(newSymbols, originalSymbols, originalComp);
332+
}
333+
242334
#endregion
243335

244336
#region "Change to symbol"

0 commit comments

Comments
 (0)