Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge 'file-local types' to main #62375

Merged
merged 17 commits into from
Jul 6, 2022
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
15 changes: 14 additions & 1 deletion docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# This document lists known breaking changes in Roslyn after .NET 6 all the way to .NET 7.

## Types cannot be named `file`

***Introduced in Visual Studio 2022 version 17.4.*** Starting in C# 11, types cannot be named `file`. The compiler will report an error on all such type names. To work around this, the type name and all usages must be escaped with an `@`:

```csharp
class file {} // Error CS9056
class @file {} // No error
```

This was done as `file` is now a modifier for type declarations.

You can learn more about this change in the associated [csharplang issue](https://github.com/dotnet/csharplang/issues/6011).

## Required spaces in #line span directives

***Introduced in .NET SDK 6.0.400, Visual Studio 2022 version 17.3.***
Expand Down Expand Up @@ -426,4 +439,4 @@ class @required {} // No error

This was done as `required` is now a member modifier for properties and fields.

You can learn more about this change in the associated [csharplang issue](https://github.com/dotnet/csharplang/issues/3630).
You can learn more about this change in the associated [csharplang issue](https://github.com/dotnet/csharplang/issues/3630).
1 change: 1 addition & 0 deletions docs/contributing/Compiler Test Plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ This document provides guidance for thinking about language interactions and tes
# Type and members
- Access modifiers (public, protected, internal, protected internal, private protected, private), static, ref
- type declarations (class, record class/struct with or without positional members, struct, interface, type parameter)
- file-local types
- methods
- fields (required and not)
- properties (including get/set/init accessors, required and not)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,5 +611,71 @@ internal class C : I<C>

await test.RunAsync();
}

[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsAddAccessibilityModifiers)]
[InlineData("class")]
[InlineData("struct")]
[InlineData("record")]
[InlineData("record struct")]
[InlineData("interface")]
[InlineData("enum")]
[WorkItem(62259, "https://github.com/dotnet/roslyn/issues/62259")]
public async Task TestFileDeclaration(string declarationKind)
{
var source = $"file {declarationKind} C {{ }}";

await new VerifyCS.Test
{
TestCode = source,
FixedCode = source,
LanguageVersion = LanguageVersion.Preview,
}.RunAsync();
}

[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddAccessibilityModifiers)]
[WorkItem(62259, "https://github.com/dotnet/roslyn/issues/62259")]
public async Task TestFileDelegate()
{
var source = "file delegate void M();";

await new VerifyCS.Test
{
TestCode = source,
FixedCode = source,
LanguageVersion = LanguageVersion.Preview,
}.RunAsync();
}

[Theory, Trait(Traits.Feature, Traits.Features.CodeActionsAddAccessibilityModifiers)]
[InlineData("class")]
[InlineData("struct")]
[InlineData("record")]
[InlineData("record struct")]
[InlineData("interface")]
[InlineData("enum")]
[WorkItem(62259, "https://github.com/dotnet/roslyn/issues/62259")]
public async Task TestNestedFileDeclaration(string declarationKind)
{
var source = $$"""
file class C1
{
{{declarationKind}} [|C2|] { }
}
""";

var fixedSource = $$"""
file class C1
{
private {{declarationKind}} C2 { }
}
""";

await new VerifyCS.Test
{
TestCode = source,
FixedCode = fixedSource,
LanguageVersion = LanguageVersion.Preview,
}.RunAsync();
}
}
}
3 changes: 3 additions & 0 deletions src/CodeStyle/Core/Analyzers/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsPartial.get -> bo
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsReadOnly.get -> bool
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsRef.get -> bool
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsRequired.get -> bool
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsFile.get -> bool
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsSealed.get -> bool
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsStatic.get -> bool
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.IsUnsafe.get -> bool
Expand All @@ -58,6 +59,7 @@ Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsOverride(bool
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsReadOnly(bool isReadOnly) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsRef(bool isRef) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsRequired(bool isRequired) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsFile(bool isFile) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsSealed(bool isSealed) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsStatic(bool isStatic) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.WithIsUnsafe(bool isUnsafe) -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
Expand Down Expand Up @@ -87,6 +89,7 @@ static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Partial.get
static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.ReadOnly.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Ref.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Required.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.File.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Sealed.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.Static.get -> Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers
static Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers.TryParse(string value, out Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers modifiers) -> bool
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Binder/BinderFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ internal BinderFactory(CSharpCompilation compilation, SyntaxTree syntaxTree, boo
// more than 50 items added before getting collected.
_binderCache = new ConcurrentCache<BinderCacheKey, Binder>(50);

_buckStopsHereBinder = new BuckStopsHereBinder(compilation);
_buckStopsHereBinder = new BuckStopsHereBinder(compilation, syntaxTree);
}

internal SyntaxTree SyntaxTree
Expand Down
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Constraints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,12 @@ private static void CheckConstraintTypeVisibility(
// "Inconsistent accessibility: constraint type '{1}' is less accessible than '{0}'"
diagnostics.Add(ErrorCode.ERR_BadVisBound, location, containingSymbol, constraintType.Type);
}

if (constraintType.Type.IsFileTypeOrUsesFileTypes() && !containingSymbol.ContainingType.IsFileTypeOrUsesFileTypes())
{
diagnostics.Add(ErrorCode.ERR_FileTypeDisallowedInSignature, location, constraintType.Type, containingSymbol);
}

diagnostics.Add(location, useSiteInfo);
}

Expand Down
42 changes: 41 additions & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Lookup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,38 @@ internal static ImmutableArray<Symbol> GetCandidateMembers(NamespaceOrTypeSymbol
}
}

private bool IsInScopeOfAssociatedSyntaxTree(Symbol symbol)
{
while (symbol is not null and not SourceMemberContainerTypeSymbol { IsFile: true })
{
symbol = symbol.ContainingType;
}

if (symbol is null)
{
// the passed-in symbol was not contained in a file type.
return true;
}

var tree = getSyntaxTreeForFileTypes();
return symbol.IsDefinedInSourceTree(tree, definedWithinSpan: null);

SyntaxTree getSyntaxTreeForFileTypes()
{
for (var binder = this; binder != null; binder = binder.Next)
{
if (binder is BuckStopsHereBinder lastBinder)
{
Debug.Assert(lastBinder.AssociatedSyntaxTree is not null);
return lastBinder.AssociatedSyntaxTree;
}
}

Debug.Assert(false);
return null;
}
}

/// <remarks>
/// Distinguish from <see cref="CanAddLookupSymbolInfo"/>, which performs an analogous task for Add*LookupSymbolsInfo*.
/// </remarks>
Expand All @@ -1322,8 +1354,12 @@ internal SingleLookupResult CheckViability(Symbol symbol, int arity, LookupOptio
? ((AliasSymbol)symbol).GetAliasTarget(basesBeingResolved)
: symbol;

if (!IsInScopeOfAssociatedSyntaxTree(unwrappedSymbol))
{
return LookupResult.Empty();
}
// Check for symbols marked with 'Microsoft.CodeAnalysis.Embedded' attribute
if (!this.Compilation.SourceModule.Equals(unwrappedSymbol.ContainingModule) && unwrappedSymbol.IsHiddenByCodeAnalysisEmbeddedAttribute())
else if (!this.Compilation.SourceModule.Equals(unwrappedSymbol.ContainingModule) && unwrappedSymbol.IsHiddenByCodeAnalysisEmbeddedAttribute())
{
return LookupResult.Empty();
}
Expand Down Expand Up @@ -1522,6 +1558,10 @@ internal bool CanAddLookupSymbolInfo(Symbol symbol, LookupOptions options, Looku
{
return false;
}
else if (!IsInScopeOfAssociatedSyntaxTree(symbol))
{
return false;
}
else if ((options & LookupOptions.MustBeInstance) != 0 && !IsInstance(symbol))
{
return false;
Expand Down
27 changes: 26 additions & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Symbols.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1816,6 +1816,16 @@ Symbol resultSymbol(
Debug.Assert(!Symbol.Equals(first, second, TypeCompareKind.ConsiderEverything) || !Symbol.Equals(originalSymbols[best.Index], originalSymbols[secondBest.Index], TypeCompareKind.ConsiderEverything),
"Why does the LookupResult contain the same symbol twice?");

if (best.IsFromFile && !secondBest.IsFromFile)
{
// a lookup of a file type is "better" than a lookup of a non-file type; no need to further diagnose
// https://github.com/dotnet/roslyn/issues/62331
// some "single symbol" diagnostics are missed here for similar reasons
// that make us miss diagnostics when reporting WRN_SameFullNameThisAggAgg.
//
return first;
}

CSDiagnosticInfo info;
bool reportError;

Expand Down Expand Up @@ -2133,6 +2143,7 @@ private static AssemblySymbol GetContainingAssembly(Symbol symbol)
private enum BestSymbolLocation
{
None,
FromFile,
FromSourceModule,
FromAddedModule,
FromReferencedAssembly,
Expand Down Expand Up @@ -2180,6 +2191,14 @@ public bool IsFromCompilation
}
}

public bool IsFromFile
{
get
{
return _location == BestSymbolLocation.FromFile;
}
}

public bool IsNone
{
get
Expand Down Expand Up @@ -2281,6 +2300,11 @@ private BestSymbolInfo GetBestSymbolInfo(ArrayBuilder<Symbol> symbols, out BestS

private static BestSymbolLocation GetLocation(CSharpCompilation compilation, Symbol symbol)
{
if (symbol is SourceMemberContainerTypeSymbol { IsFile: true })
{
return BestSymbolLocation.FromFile;
}

var containingAssembly = symbol.ContainingAssembly;
if (containingAssembly == compilation.SourceAssembly)
{
Expand Down Expand Up @@ -2470,7 +2494,8 @@ protected AssemblySymbol GetForwardedToAssembly(string name, int arity, ref Name

// NOTE: This won't work if the type isn't using CLS-style generic naming (i.e. `arity), but this code is
// only intended to improve diagnostic messages, so false negatives in corner cases aren't a big deal.
var metadataName = MetadataHelpers.ComposeAritySuffixedMetadataName(name, arity);
// File types can't be forwarded, so we won't attempt to determine a file identifier to attach to the metadata name.
var metadataName = MetadataHelpers.ComposeAritySuffixedMetadataName(name, arity, associatedFileIdentifier: null);
var fullMetadataName = MetadataHelpers.BuildQualifiedName(qualifierOpt?.ToDisplayString(SymbolDisplayFormat.QualifiedNameOnlyFormat), metadataName);
var result = GetForwardedToAssembly(fullMetadataName, diagnostics, location);
if ((object)result != null)
Expand Down
12 changes: 11 additions & 1 deletion src/Compilers/CSharp/Portable/Binder/BuckStopsHereBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,21 @@ namespace Microsoft.CodeAnalysis.CSharp
/// </summary>
internal class BuckStopsHereBinder : Binder
{
internal BuckStopsHereBinder(CSharpCompilation compilation)
internal BuckStopsHereBinder(CSharpCompilation compilation, SyntaxTree? associatedSyntaxTree)
: base(compilation)
{
this.AssociatedSyntaxTree = associatedSyntaxTree;
}

/// <summary>
/// In non-speculative scenarios, the syntax tree being bound.
/// In speculative scenarios, the syntax tree from the original compilation used as the speculation context.
/// This is <see langword="null"/> in some scenarios, such as the binder used for <see cref="CSharpCompilation.Conversions" />
/// or the binder used to bind usings in <see cref="CSharpCompilation.UsingsFromOptionsAndDiagnostics"/>.
/// https://github.com/dotnet/roslyn/issues/62332: what about in EE scenarios?
/// </summary>
internal readonly SyntaxTree? AssociatedSyntaxTree;

internal override ImportChain? ImportChain
{
get
Expand Down
21 changes: 21 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -7124,6 +7124,24 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_NewConstraintCannotHaveRequiredMembers" xml:space="preserve">
<value>'{2}' cannot satisfy the 'new()' constraint on parameter '{1}' in the generic type or or method '{0}' because '{2}' has required members.</value>
</data>
<data name="ERR_FileTypeDisallowedInSignature" xml:space="preserve">
<value>File type '{0}' cannot be used in a member signature in non-file type '{1}'.</value>
</data>
<data name="ERR_FileTypeNoExplicitAccessibility" xml:space="preserve">
<value>File type '{0}' cannot use accessibility modifiers.</value>
</data>
<data name="ERR_FileTypeBase" xml:space="preserve">
<value>File type '{0}' cannot be used as a base type of non-file type '{1}'.</value>
</data>
<data name="ERR_FileTypeNested" xml:space="preserve">
<value>File type '{0}' must be defined in a top level type; '{0}' is a nested type.</value>
</data>
<data name="ERR_GlobalUsingStaticFileType" xml:space="preserve">
<value>File type '{0}' cannot be used in a 'global using static' directive.</value>
</data>
<data name="ERR_FileTypeNameDisallowed" xml:space="preserve">
<value>Types and aliases cannot be named 'file'.</value>
</data>
<data name="IDS_FeatureUnsignedRightShift" xml:space="preserve">
<value>unsigned right shift</value>
</data>
Expand Down Expand Up @@ -7157,4 +7175,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="ERR_BadBinaryReadOnlySpanConcatenation" xml:space="preserve">
<value>Operator '{0}' cannot be applied to operands of type '{1}' and '{2}' that are not UTF-8 byte representations</value>
</data>
<data name="IDS_FeatureFileTypes" xml:space="preserve">
<value>file types</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public static UsingsFromOptionsAndDiagnostics FromOptions(CSharpCompilation comp
}

var diagnostics = new DiagnosticBag();
var usingsBinder = new InContainerBinder(compilation.GlobalNamespace, new BuckStopsHereBinder(compilation));
var usingsBinder = new InContainerBinder(compilation.GlobalNamespace, new BuckStopsHereBinder(compilation, associatedSyntaxTree: null));
var boundUsings = ArrayBuilder<NamespaceOrTypeAndUsingDirective>.GetInstance();
var uniqueUsings = PooledHashSet<NamespaceOrTypeSymbol>.GetInstance();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ internal Conversions Conversions
{
if (_conversions == null)
{
Interlocked.CompareExchange(ref _conversions, new BuckStopsHereBinder(this).Conversions, null);
Interlocked.CompareExchange(ref _conversions, new BuckStopsHereBinder(this, associatedSyntaxTree: null).Conversions, null);
}

return _conversions;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ private void BindName(XAttribute attribute, CSharpSyntaxNode originatingSyntax,
"Why are we processing a documentation comment that is not attached to a member declaration?");

var nameDiagnostics = BindingDiagnosticBag.GetInstance(_diagnostics);
Binder binder = MakeNameBinder(isParameter, isTypeParameterRef, _memberSymbol, _compilation);
Binder binder = MakeNameBinder(isParameter, isTypeParameterRef, _memberSymbol, _compilation, originatingSyntax.SyntaxTree);
DocumentationCommentCompiler.BindName(attrSyntax, binder, _memberSymbol, ref _documentedParameters, ref _documentedTypeParameters, nameDiagnostics);
RecordBindingDiagnostics(nameDiagnostics, sourceLocation); // Respects DocumentationMode.
nameDiagnostics.Free();
Expand All @@ -545,9 +545,9 @@ private void BindName(XAttribute attribute, CSharpSyntaxNode originatingSyntax,

// NOTE: We're not sharing code with the BinderFactory visitor, because we already have the
// member symbol in hand, which makes things much easier.
private static Binder MakeNameBinder(bool isParameter, bool isTypeParameterRef, Symbol memberSymbol, CSharpCompilation compilation)
private static Binder MakeNameBinder(bool isParameter, bool isTypeParameterRef, Symbol memberSymbol, CSharpCompilation compilation, SyntaxTree syntaxTree)
{
Binder binder = new BuckStopsHereBinder(compilation);
Binder binder = new BuckStopsHereBinder(compilation, syntaxTree);

// All binders should have a containing symbol.
Symbol containingSymbol = memberSymbol.ContainingSymbol;
Expand Down
Loading