From 53b193b360c494f7c33db7cd9d7bec7f440cac8b Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 5 Jul 2022 07:15:01 -0700 Subject: [PATCH] File types IDE changes (#62215) Co-authored-by: Cyrus Najmabadi Co-authored-by: Youssef Victor --- .../Symbols/PublicModel/NamedTypeSymbol.cs | 2 + .../Symbols/Source/FileModifierTests.cs | 27 +++- .../Core/Portable/Symbols/INamedTypeSymbol.cs | 5 + .../Portable/Symbols/NamedTypeSymbol.vb | 6 + .../KeywordCompletionProviderTests.cs | 146 ++++++++++++++++++ .../SymbolKey/SymbolKeyCompilationsTests.cs | 92 +++++++++++ .../CSharpTest/SymbolKey/SymbolKeyTests.cs | 105 +++++++++++++ .../CodeGenerationTests.CSharp.cs | 53 +++++++ .../KeywordCompletionProvider.cs | 1 + .../FileKeywordRecommender.cs | 30 ++++ ...aAsSourceService.WrappedNamedTypeSymbol.cs | 2 + .../CodeGeneration/CSharpSyntaxGenerator.cs | 15 +- .../CodeGeneration/NamedTypeGenerator.cs | 16 +- .../CodeGenerationAbstractNamedTypeSymbol.cs | 2 + .../Portable/Editing/DeclarationModifiers.cs | 16 +- .../SymbolKey/SymbolKey.NamedTypeSymbolKey.cs | 24 ++- .../Compiler/Core/SymbolKey/SymbolKey.cs | 2 +- .../CSharp/Utilities/SyntaxKindSet.cs | 1 + 18 files changed, 523 insertions(+), 22 deletions(-) create mode 100644 src/Features/CSharp/Portable/Completion/KeywordRecommenders/FileKeywordRecommender.cs diff --git a/src/Compilers/CSharp/Portable/Symbols/PublicModel/NamedTypeSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/PublicModel/NamedTypeSymbol.cs index f5173fe3d8665..6f09cef0e4714 100644 --- a/src/Compilers/CSharp/Portable/Symbols/PublicModel/NamedTypeSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/PublicModel/NamedTypeSymbol.cs @@ -194,6 +194,8 @@ INamedTypeSymbol INamedTypeSymbol.TupleUnderlyingType bool INamedTypeSymbol.IsSerializable => UnderlyingNamedTypeSymbol.IsSerializable; + bool INamedTypeSymbol.IsFile => UnderlyingNamedTypeSymbol.AssociatedSyntaxTree is not null; + INamedTypeSymbol INamedTypeSymbol.NativeIntegerUnderlyingType => UnderlyingNamedTypeSymbol.NativeIntegerUnderlyingType.GetPublicSymbol(); #region ISymbol Members diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs index dfc93da7eedfc..be9266768eb8a 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/FileModifierTests.cs @@ -3245,9 +3245,22 @@ void M(C c) var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var node = tree.GetRoot().DescendantNodes().OfType().Single(); - var type = model.GetTypeInfo(node.Type!).Type; + var type = (INamedTypeSymbol)model.GetTypeInfo(node.Type!).Type!; Assert.Equal("C@", type.ToTestDisplayString()); - Assert.Equal(tree, type.GetSymbol()!.AssociatedSyntaxTree); + Assert.Equal(tree, type.GetSymbol()!.AssociatedSyntaxTree); + Assert.True(type.IsFile); + + var referencingMetadataComp = CreateCompilation("", new[] { comp.ToMetadataReference() }); + type = ((Compilation)referencingMetadataComp).GetTypeByMetadataName("<>F0__C")!; + Assert.Equal("C@", type.ToTestDisplayString()); + Assert.Equal(tree, type.GetSymbol()!.AssociatedSyntaxTree); + Assert.True(type.IsFile); + + var referencingImageComp = CreateCompilation("", new[] { comp.EmitToImageReference() }); + type = ((Compilation)referencingImageComp).GetTypeByMetadataName("<>F0__C")!; + Assert.Equal("<>F0__C", type.ToTestDisplayString()); + Assert.Null(type.GetSymbol()!.AssociatedSyntaxTree); + Assert.False(type.IsFile); } [Fact] @@ -3267,9 +3280,10 @@ void M(C c) var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var node = tree.GetRoot().DescendantNodes().OfType().Single(); - var type = model.GetTypeInfo(node.Type!).Type; + var type = (INamedTypeSymbol)model.GetTypeInfo(node.Type!).Type!; Assert.Equal("C", type.ToTestDisplayString()); - Assert.Null(type.GetSymbol()!.AssociatedSyntaxTree); + Assert.Null(type.GetSymbol()!.AssociatedSyntaxTree); + Assert.False(type.IsFile); } [Fact] @@ -3289,8 +3303,9 @@ void M(C c) var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var node = tree.GetRoot().DescendantNodes().OfType().Single(); - var type = model.GetTypeInfo(node.Type!).Type; + var type = (INamedTypeSymbol)model.GetTypeInfo(node.Type!).Type!; Assert.Equal("C@", type.ToTestDisplayString()); - Assert.Equal(tree, type.GetSymbol()!.AssociatedSyntaxTree); + Assert.Equal(tree, type.GetSymbol()!.AssociatedSyntaxTree); + Assert.True(type.IsFile); } } diff --git a/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs b/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs index 3ba82acb88738..f8fab6b35decf 100644 --- a/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs +++ b/src/Compilers/Core/Portable/Symbols/INamedTypeSymbol.cs @@ -56,6 +56,11 @@ public interface INamedTypeSymbol : ITypeSymbol /// bool IsComImport { get; } + /// + /// Indicates the type is declared in source and is only visible in the file it is declared in. + /// + bool IsFile { get; } + /// /// Returns collection of names of members declared within this type. /// diff --git a/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb b/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb index c75ff30f818e3..7d30aafb6182c 100644 --- a/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb +++ b/src/Compilers/VisualBasic/Portable/Symbols/NamedTypeSymbol.vb @@ -1216,6 +1216,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols End Get End Property + Private ReadOnly Property INamedTypeSymbol_IsFile As Boolean Implements INamedTypeSymbol.IsFile + Get + Return False + End Get + End Property + Private ReadOnly Property INamedTypeSymbol_NativeIntegerUnderlyingType As INamedTypeSymbol Implements INamedTypeSymbol.NativeIntegerUnderlyingType Get Return Nothing diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs index 5c3d6722ef93b..cfbc1cf8ddd02 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/KeywordCompletionProviderTests.cs @@ -684,5 +684,151 @@ class C await VerifyItemIsAbsentAsync(markup, "required"); } + + [Fact] + public async Task SuggestFileOnTypes() + { + var markup = $$""" + $$ class C { } + """; + + await VerifyItemExistsAsync(markup, "file"); + } + + [Fact] + public async Task DoNotSuggestFileAfterFile() + { + var markup = $$""" + file $$ + """; + + await VerifyItemIsAbsentAsync(markup, "file"); + } + + [Fact] + public async Task SuggestFileAfterReadonly() + { + // e.g. 'readonly file struct X { }' + var markup = $$""" + readonly $$ + """; + + await VerifyItemExistsAsync(markup, "file"); + } + + [Fact] + public async Task SuggestFileBeforeFileType() + { + var markup = $$""" + $$ + + file class C { } + """; + + // it might seem like we want to prevent 'file file class', + // but it's likely the user is declaring a file type above an existing file type here. + await VerifyItemExistsAsync(markup, "file"); + } + + [Fact] + public async Task SuggestFileBeforeDelegate() + { + var markup = $$""" + $$ delegate + """; + + await VerifyItemExistsAsync(markup, "file"); + } + + [Fact] + public async Task DoNotSuggestFileOnNestedTypes() + { + var markup = $$""" + class Outer + { + $$ class C { } + } + """; + + await VerifyItemIsAbsentAsync(markup, "file"); + } + + [Fact] + public async Task DoNotSuggestFileOnNonTypeMembers() + { + var markup = $$""" + class C + { + $$ + } + """; + + await VerifyItemIsAbsentAsync(markup, "file"); + } + + [Theory] + [InlineData("public")] + [InlineData("internal")] + [InlineData("protected")] + [InlineData("private")] + public async Task DoNotSuggestFileAfterFilteredKeywords(string keyword) + { + var markup = $$""" + {{keyword}} $$ + """; + + await VerifyItemIsAbsentAsync(markup, "file"); + } + + [Theory] + [InlineData("public")] + [InlineData("internal")] + [InlineData("protected")] + [InlineData("private")] + public async Task DoNotSuggestFilteredKeywordsAfterFile(string keyword) + { + var markup = $$""" + file $$ + """; + + await VerifyItemIsAbsentAsync(markup, keyword); + } + + [Fact] + public async Task SuggestFileInFileScopedNamespace() + { + var markup = $$""" + namespace NS; + + $$ + """; + + await VerifyItemExistsAsync(markup, "file"); + } + + [Fact] + public async Task SuggestFileInNamespace() + { + var markup = $$""" + namespace NS + { + $$ + } + """; + + await VerifyItemExistsAsync(markup, "file"); + } + + [Fact] + public async Task SuggestFileAfterClass() + { + var markup = $$""" + file class C { } + + $$ + """; + + await VerifyItemExistsAsync(markup, "file"); + } } } diff --git a/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyCompilationsTests.cs b/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyCompilationsTests.cs index 036aecc1b8656..11acb20fafeb1 100644 --- a/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyCompilationsTests.cs +++ b/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyCompilationsTests.cs @@ -239,6 +239,98 @@ public void M(List list) ResolveAndVerifySymbolList(members1, members2, comp1); } + [Fact] + public void FileType1() + { + var src1 = @"using System; + +namespace N1.N2 +{ + file class C { } +} +"; + var originalComp = CreateCompilation(src1, assemblyName: "Test"); + var newComp = CreateCompilation(src1, assemblyName: "Test"); + + var originalSymbols = GetSourceSymbols(originalComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + var newSymbols = GetSourceSymbols(newComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + + Assert.Equal(3, originalSymbols.Length); + ResolveAndVerifySymbolList(newSymbols, originalSymbols, originalComp); + } + + [Fact] + public void FileType2() + { + var src1 = @"using System; + +namespace N1.N2 +{ + file class C { } +} +"; + var originalComp = CreateCompilation(src1, assemblyName: "Test"); + var newComp = CreateCompilation(src1, assemblyName: "Test"); + + var originalSymbols = GetSourceSymbols(originalComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + var newSymbols = GetSourceSymbols(newComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + + Assert.Equal(3, originalSymbols.Length); + ResolveAndVerifySymbolList(newSymbols, originalSymbols, originalComp); + } + + [Fact] + public void FileType3() + { + var src1 = @"using System; + +namespace N1.N2 +{ + file class C { } +} +"; + // this should result in two entirely separate file symbols. + // note that the IDE can only distinguish file type symbols with the same name when they have distinct file paths. + // We are OK with this as we will require file types with identical names to have distinct file paths later in the preview. + // See https://github.com/dotnet/roslyn/issues/61999 + var originalComp = CreateCompilation(new[] { SyntaxFactory.ParseSyntaxTree(src1, path: "file1.cs"), SyntaxFactory.ParseSyntaxTree(src1, path: "file2.cs") }, assemblyName: "Test"); + var newComp = CreateCompilation(new[] { SyntaxFactory.ParseSyntaxTree(src1, path: "file1.cs"), SyntaxFactory.ParseSyntaxTree(src1, path: "file2.cs") }, assemblyName: "Test"); + + var originalSymbols = GetSourceSymbols(originalComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + var newSymbols = GetSourceSymbols(newComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + + Assert.Equal(4, originalSymbols.Length); + ResolveAndVerifySymbolList(newSymbols, originalSymbols, originalComp); + } + + [Fact] + public void FileType4() + { + // we should be able to distinguish a file type and non-file type when they have the same source name. + var src1 = SyntaxFactory.ParseSyntaxTree(@"using System; + +namespace N1.N2 +{ + file class C { } +} +", path: "File1.cs"); + + var src2 = SyntaxFactory.ParseSyntaxTree(@" +namespace N1.N2 +{ + class C { } +} +", path: "File2.cs"); + var originalComp = CreateCompilation(new[] { src1, src2 }, assemblyName: "Test"); + var newComp = CreateCompilation(new[] { src1, src2 }, assemblyName: "Test"); + + var originalSymbols = GetSourceSymbols(originalComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + var newSymbols = GetSourceSymbols(newComp, SymbolCategory.DeclaredType | SymbolCategory.DeclaredNamespace).OrderBy(s => s.Name).ToArray(); + + Assert.Equal(4, originalSymbols.Length); + ResolveAndVerifySymbolList(newSymbols, originalSymbols, originalComp); + } + #endregion #region "Change to symbol" diff --git a/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyTests.cs b/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyTests.cs index c14e7076089f5..0acc20355cab7 100644 --- a/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyTests.cs +++ b/src/EditorFeatures/CSharpTest/SymbolKey/SymbolKeyTests.cs @@ -19,6 +19,111 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.SymbolId [UseExportProvider] public class SymbolKeyTests { + [Fact] + public async Task FileType_01() + { + var typeSource = @" +file class C1 +{ + public static void M() { } +} +"; + + var workspaceXml = @$" + + + + +{typeSource} + + + +"; + using var workspace = TestWorkspace.Create(workspaceXml); + + var solution = workspace.CurrentSolution; + var project = solution.Projects.Single(); + + var compilation = await project.GetCompilationAsync(); + var type = compilation.GetTypeByMetadataName("F0__C1"); + Assert.NotNull(type); + var symbolKey = SymbolKey.Create(type); + var resolved = symbolKey.Resolve(compilation).Symbol; + Assert.Same(type, resolved); + } + + [Fact] + public async Task FileType_02() + { + var workspaceXml = $$""" + + + + +file class C +{ + public static void M() { } +} + + +file class C +{ + public static void M() { } +} + + + +"""; + using var workspace = TestWorkspace.Create(workspaceXml); + + var solution = workspace.CurrentSolution; + var project = solution.Projects.Single(); + + var compilation = await project.GetCompilationAsync(); + + var type = compilation.GetTypeByMetadataName("F1__C"); + Assert.NotNull(type); + var symbolKey = SymbolKey.Create(type); + var resolved = symbolKey.Resolve(compilation).Symbol; + Assert.Same(type, resolved); + + type = compilation.GetTypeByMetadataName("F0__C"); + Assert.NotNull(type); + symbolKey = SymbolKey.Create(type); + resolved = symbolKey.Resolve(compilation).Symbol; + Assert.Same(type, resolved); + } + + [Fact] + public async Task FileType_03() + { + var workspaceXml = $$""" + + + + +file class C +{ + public class Inner { } +} + + + +"""; + using var workspace = TestWorkspace.Create(workspaceXml); + + var solution = workspace.CurrentSolution; + var project = solution.Projects.Single(); + + var compilation = await project.GetCompilationAsync(); + + var type = compilation.GetTypeByMetadataName("F0__C+Inner"); + Assert.NotNull(type); + var symbolKey = SymbolKey.Create(type); + var resolved = symbolKey.Resolve(compilation).Symbol; + Assert.Same(type, resolved); + } + [Fact, WorkItem(45437, "https://github.com/dotnet/roslyn/issues/45437")] public async Task TestGenericsAndNullability() { diff --git a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs index e7706f9195b6b..529e64816b069 100644 --- a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs +++ b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs @@ -220,6 +220,59 @@ public static class C modifiers: new Editing.DeclarationModifiers(isStatic: true)); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeGeneration), WorkItem(544405, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544405")] + public async Task AddStaticAbstractClass() + { + var input = "namespace [|N|] { }"; + var expected = @"namespace N +{ + public static class C + { + } +}"; + // note that 'abstract' is dropped here + await TestAddNamedTypeAsync(input, expected, + modifiers: new Editing.DeclarationModifiers(isStatic: true, isAbstract: true)); + } + + [Theory, Trait(Traits.Feature, Traits.Features.CodeGeneration), WorkItem(544405, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544405")] + [InlineData(Accessibility.NotApplicable)] + [InlineData(Accessibility.Internal)] + [InlineData(Accessibility.Public)] + public async Task AddFileClass(Accessibility accessibility) + { + var input = "namespace [|N|] { }"; + var expected = @"namespace N +{ + file class C + { + } +}"; + // note: when invalid combinations of modifiers+accessibility are present here, + // we actually drop the accessibility. This is similar to what is done if someone declares a 'static abstract class C { }'. + await TestAddNamedTypeAsync(input, expected, + accessibility: accessibility, + modifiers: new Editing.DeclarationModifiers(isFile: true)); + } + + [Theory, Trait(Traits.Feature, Traits.Features.CodeGeneration), WorkItem(544405, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544405")] + [InlineData("struct", TypeKind.Struct)] + [InlineData("interface", TypeKind.Interface)] + [InlineData("enum", TypeKind.Enum)] + public async Task AddFileType(string kindString, TypeKind typeKind) + { + var input = "namespace [|N|] { }"; + var expected = @"namespace N +{ + file " + kindString + @" C + { + } +}"; + await TestAddNamedTypeAsync(input, expected, + typeKind: typeKind, + modifiers: new Editing.DeclarationModifiers(isFile: true)); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeGeneration), WorkItem(544405, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/544405")] public async Task AddSealedClass() { diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs index 2c74a4a4cc164..21c022e42f4eb 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/KeywordCompletionProvider.cs @@ -68,6 +68,7 @@ public KeywordCompletionProvider() new ExternKeywordRecommender(), new FalseKeywordRecommender(), new FieldKeywordRecommender(), + new FileKeywordRecommender(), new FinallyKeywordRecommender(), new FixedKeywordRecommender(), new FloatKeywordRecommender(), diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FileKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FileKeywordRecommender.cs new file mode 100644 index 0000000000000..73510f4b46dd1 --- /dev/null +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FileKeywordRecommender.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders; + +internal class FileKeywordRecommender : AbstractSyntacticSingleKeywordRecommender +{ + private static readonly ISet s_validModifiers = SyntaxKindSet.AllMemberModifiers + .Where(s => s != SyntaxKind.FileKeyword && !SyntaxFacts.IsAccessibilityModifier(s)) + .ToSet(); + + public FileKeywordRecommender() + : base(SyntaxKind.FileKeyword) + { + } + + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) + { + return context.ContainingTypeDeclaration == null + && context.IsTypeDeclarationContext(s_validModifiers, SyntaxKindSet.AllTypeDeclarations, canBePartial: true, cancellationToken); + } +} diff --git a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs index 8439fc7c3bd27..cb8972561e3ef 100644 --- a/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs +++ b/src/Features/Core/Portable/MetadataAsSource/AbstractMetadataAsSourceService.WrappedNamedTypeSymbol.cs @@ -144,6 +144,8 @@ public ImmutableArray ToMinimalDisplayParts(SemanticModel sem public bool IsNativeIntegerType => _symbol.IsNativeIntegerType; + public bool IsFile => _symbol.IsFile; + public INamedTypeSymbol NativeIntegerUnderlyingType => _symbol.NativeIntegerUnderlyingType; NullableAnnotation ITypeSymbol.NullableAnnotation => throw new NotImplementedException(); diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs index 7e32e605a28e3..3ba8fffee3e30 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs @@ -1447,23 +1447,26 @@ public override SyntaxNode WithAccessibility(SyntaxNode declaration, Accessibili DeclarationModifiers.Partial | DeclarationModifiers.Sealed | DeclarationModifiers.Static | - DeclarationModifiers.Unsafe; + DeclarationModifiers.Unsafe | + DeclarationModifiers.File; private static readonly DeclarationModifiers s_recordModifiers = DeclarationModifiers.Abstract | DeclarationModifiers.New | DeclarationModifiers.Partial | DeclarationModifiers.Sealed | - DeclarationModifiers.Unsafe; + DeclarationModifiers.Unsafe | + DeclarationModifiers.File; private static readonly DeclarationModifiers s_structModifiers = DeclarationModifiers.New | DeclarationModifiers.Partial | DeclarationModifiers.ReadOnly | DeclarationModifiers.Ref | - DeclarationModifiers.Unsafe; + DeclarationModifiers.Unsafe | + DeclarationModifiers.File; - private static readonly DeclarationModifiers s_interfaceModifiers = DeclarationModifiers.New | DeclarationModifiers.Partial | DeclarationModifiers.Unsafe; + private static readonly DeclarationModifiers s_interfaceModifiers = DeclarationModifiers.New | DeclarationModifiers.Partial | DeclarationModifiers.Unsafe | DeclarationModifiers.File; private static readonly DeclarationModifiers s_accessorModifiers = DeclarationModifiers.Abstract | DeclarationModifiers.New | DeclarationModifiers.Override | DeclarationModifiers.Virtual; private static readonly DeclarationModifiers s_localFunctionModifiers = @@ -1486,10 +1489,10 @@ private static DeclarationModifiers GetAllowedModifiers(SyntaxKind kind) return s_classModifiers; case SyntaxKind.EnumDeclaration: - return DeclarationModifiers.New; + return DeclarationModifiers.New | DeclarationModifiers.File; case SyntaxKind.DelegateDeclaration: - return DeclarationModifiers.New | DeclarationModifiers.Unsafe; + return DeclarationModifiers.New | DeclarationModifiers.Unsafe | DeclarationModifiers.File; case SyntaxKind.InterfaceDeclaration: return s_interfaceModifiers; diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/NamedTypeGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/NamedTypeGenerator.cs index 41249624471d0..941fef327976a 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/NamedTypeGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/NamedTypeGenerator.cs @@ -267,11 +267,17 @@ private static MemberDeclarationSyntax RemoveAllMembers(MemberDeclarationSyntax { var tokens = ArrayBuilder.GetInstance(); - var defaultAccessibility = destination is CodeGenerationDestination.CompilationUnit or CodeGenerationDestination.Namespace - ? Accessibility.Internal - : Accessibility.Private; - - AddAccessibilityModifiers(namedType.DeclaredAccessibility, tokens, info, defaultAccessibility); + if (!namedType.IsFile) + { + var defaultAccessibility = destination is CodeGenerationDestination.CompilationUnit or CodeGenerationDestination.Namespace + ? Accessibility.Internal + : Accessibility.Private; + AddAccessibilityModifiers(namedType.DeclaredAccessibility, tokens, info, defaultAccessibility); + } + else + { + tokens.Add(SyntaxFactory.Token(SyntaxKind.FileKeyword)); + } if (namedType.IsStatic) { diff --git a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs index ba6e428ebc76f..bc12ff16bfbe4 100644 --- a/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs +++ b/src/Workspaces/Core/Portable/CodeGeneration/Symbols/CodeGenerationAbstractNamedTypeSymbol.cs @@ -121,5 +121,7 @@ public override string MetadataName public INamedTypeSymbol TupleUnderlyingType => null; public bool IsSerializable => false; + + public bool IsFile => Modifiers.IsFile; } } diff --git a/src/Workspaces/Core/Portable/Editing/DeclarationModifiers.cs b/src/Workspaces/Core/Portable/Editing/DeclarationModifiers.cs index df4f81dd1d41e..b53d401c8b683 100644 --- a/src/Workspaces/Core/Portable/Editing/DeclarationModifiers.cs +++ b/src/Workspaces/Core/Portable/Editing/DeclarationModifiers.cs @@ -38,7 +38,8 @@ private DeclarationModifiers(Modifiers modifiers) bool isRef = false, bool isVolatile = false, bool isExtern = false, - bool isRequired = false) + bool isRequired = false, + bool isFile = false) : this( (isStatic ? Modifiers.Static : Modifiers.None) | (isAbstract ? Modifiers.Abstract : Modifiers.None) | @@ -56,7 +57,8 @@ private DeclarationModifiers(Modifiers modifiers) (isRef ? Modifiers.Ref : Modifiers.None) | (isVolatile ? Modifiers.Volatile : Modifiers.None) | (isExtern ? Modifiers.Extern : Modifiers.None) | - (isRequired ? Modifiers.Required : Modifiers.None)) + (isRequired ? Modifiers.Required : Modifiers.None) | + (isFile ? Modifiers.File : Modifiers.None)) { } @@ -84,7 +86,8 @@ IMethodSymbol or isVolatile: field?.IsVolatile == true, isExtern: symbol.IsExtern, isAsync: method?.IsAsync == true, - isRequired: symbol.IsRequired()); + isRequired: symbol.IsRequired(), + isFile: (symbol as INamedTypeSymbol)?.IsFile == true); } // Only named types, members of named types, and local functions have modifiers. @@ -126,6 +129,8 @@ IMethodSymbol or public bool IsRequired => (_modifiers & Modifiers.Required) != 0; + public bool IsFile => (_modifiers & Modifiers.File) != 0; + public DeclarationModifiers WithIsStatic(bool isStatic) => new(SetFlag(_modifiers, Modifiers.Static, isStatic)); @@ -178,6 +183,9 @@ public DeclarationModifiers WithIsExtern(bool isExtern) public DeclarationModifiers WithIsRequired(bool isRequired) => new(SetFlag(_modifiers, Modifiers.Required, isRequired)); + public DeclarationModifiers WithIsFile(bool isFile) + => new(SetFlag(_modifiers, Modifiers.File, isFile)); + private static Modifiers SetFlag(Modifiers existing, Modifiers modifier, bool isSet) => isSet ? (existing | modifier) : (existing & ~modifier); @@ -203,6 +211,7 @@ private enum Modifiers Volatile = 1 << 14, Extern = 1 << 15, Required = 1 << 16, + File = 1 << 17, #pragma warning restore format } @@ -225,6 +234,7 @@ private enum Modifiers public static DeclarationModifiers Volatile => new(Modifiers.Volatile); public static DeclarationModifiers Extern => new(Modifiers.Extern); public static DeclarationModifiers Required => new(Modifiers.Required); + public static DeclarationModifiers File => new(Modifiers.File); public static DeclarationModifiers operator |(DeclarationModifiers left, DeclarationModifiers right) => new(left._modifiers | right._modifiers); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.NamedTypeSymbolKey.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.NamedTypeSymbolKey.cs index f45f66bdfe248..6b0fee736cd1e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.NamedTypeSymbolKey.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.NamedTypeSymbolKey.cs @@ -16,6 +16,9 @@ public static void Create(INamedTypeSymbol symbol, SymbolKeyWriter visitor) visitor.WriteSymbolKey(symbol.ContainingSymbol); visitor.WriteString(symbol.Name); visitor.WriteInteger(symbol.Arity); + visitor.WriteString(symbol.IsFile + ? symbol.DeclaringSyntaxReferences[0].SyntaxTree.FilePath + : null); visitor.WriteBoolean(symbol.IsUnboundGenericType); if (!symbol.Equals(symbol.ConstructedFrom) && !symbol.IsUnboundGenericType) @@ -33,7 +36,9 @@ public static SymbolKeyResolution Resolve(SymbolKeyReader reader, out string? fa var containingSymbolResolution = reader.ReadSymbolKey(out var containingSymbolFailureReason); var name = reader.ReadRequiredString(); var arity = reader.ReadInteger(); + var filePath = reader.ReadString(); var isUnboundGenericType = reader.ReadBoolean(); + using var typeArguments = reader.ReadSymbolKeyArray(out var typeArgumentsFailureReason); if (containingSymbolFailureReason != null) @@ -61,7 +66,7 @@ public static SymbolKeyResolution Resolve(SymbolKeyReader reader, out string? fa foreach (var nsOrType in containingSymbolResolution.OfType()) { Resolve( - result, nsOrType, name, arity, + result, nsOrType, name, arity, filePath, isUnboundGenericType, typeArgumentArray); } @@ -73,11 +78,28 @@ public static SymbolKeyResolution Resolve(SymbolKeyReader reader, out string? fa INamespaceOrTypeSymbol container, string name, int arity, + string? filePath, bool isUnboundGenericType, ITypeSymbol[] typeArguments) { foreach (var type in container.GetTypeMembers(name, arity)) { + // if this is a 'file' type, then only resolve to a file-type from this same file + if (filePath != null) + { + if (!type.IsFile || + // note: if we found 'IsFile' returned true, we can assume DeclaringSyntaxReferences is non-empty. + type.DeclaringSyntaxReferences[0].SyntaxTree.FilePath != filePath) + { + continue; + } + } + else if (type.IsFile) + { + // since this key lacks a file path it can't match against a 'file' type + continue; + } + var currentType = typeArguments.Length > 0 ? type.Construct(typeArguments) : type; currentType = isUnboundGenericType ? currentType.ConstructUnboundGenericType() : currentType; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.cs index e30a325b2c17b..ad6fe979a4fa9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/SymbolKey/SymbolKey.cs @@ -109,7 +109,7 @@ internal partial struct SymbolKey : IEquatable /// out a SymbolKey from a previous version of Roslyn and then attempt to use it in a /// newer version where the encoding has changed. /// - internal const int FormatVersion = 2; + internal const int FormatVersion = 3; [DataMember(Order = 0)] private readonly string _symbolKeyData; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs index 132a1de02a285..17444004886c8 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Utilities/SyntaxKindSet.cs @@ -14,6 +14,7 @@ internal class SyntaxKindSet public static readonly ISet AllTypeModifiers = new HashSet(SyntaxFacts.EqualityComparer) { SyntaxKind.AbstractKeyword, + SyntaxKind.FileKeyword, SyntaxKind.InternalKeyword, SyntaxKind.NewKeyword, SyntaxKind.PublicKeyword,