未验证 提交 f8ed9e7a 编写于 作者: C CyrusNajmabadi 提交者: GitHub

Merge pull request #44230 from CyrusNajmabadi/nullableMAS

Fix imports not being removed from MAS files.
...@@ -1069,7 +1069,7 @@ class Z<T> where T : class ...@@ -1069,7 +1069,7 @@ class Z<T> where T : class
string b; string b;
T? c; T? c;
public Z(int a, string b, T c{|Navigation:)|} public Z(int a, string b, T? c{|Navigation:)|}
{ {
this.a = a; this.a = a;
this.b = b ?? throw new ArgumentNullException(nameof(b)); this.b = b ?? throw new ArgumentNullException(nameof(b));
......
...@@ -43,14 +43,10 @@ public async Task TestNativeInteger() ...@@ -43,14 +43,10 @@ public async Task TestNativeInteger()
// {CodeAnalysisResources.InMemoryAssembly} // {CodeAnalysisResources.InMemoryAssembly}
#endregion #endregion
using System;
using System.Runtime.CompilerServices;
public class [|C|] public class [|C|]
{{ {{
[NativeIntegerAttribute]
public nint i; public nint i;
[NativeIntegerAttribute]
public nuint i2; public nuint i2;
public C(); public C();
...@@ -88,8 +84,6 @@ public async Task TestValueTuple() ...@@ -88,8 +84,6 @@ public async Task TestValueTuple()
// System.ValueTuple.dll // System.ValueTuple.dll
#endregion #endregion
using System;
using System;
using System.Collections; using System.Collections;
namespace System namespace System
......
...@@ -44,7 +44,6 @@ public async Task BracketedIdentifierSimplificationTest() ...@@ -44,7 +44,6 @@ public async Task BracketedIdentifierSimplificationTest()
' mscorlib.v4_6_1038_0.dll ' mscorlib.v4_6_1038_0.dll
#End Region #End Region
Imports System
Imports System.Runtime.InteropServices Imports System.Runtime.InteropServices
Namespace System Namespace System
...@@ -95,8 +94,6 @@ public async Task TestValueTuple() ...@@ -95,8 +94,6 @@ public async Task TestValueTuple()
' System.ValueTuple.dll ' System.ValueTuple.dll
#End Region #End Region
Imports System
Imports System
Imports System.Collections Imports System.Collections
Namespace System Namespace System
......
...@@ -7,19 +7,23 @@ ...@@ -7,19 +7,23 @@
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.DocumentationComments; using Microsoft.CodeAnalysis.CSharp.DocumentationComments;
using Microsoft.CodeAnalysis.CSharp.Simplification; using Microsoft.CodeAnalysis.CSharp.Simplification;
using Microsoft.CodeAnalysis.CSharp.Utilities; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.DocumentationComments;
using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.MetadataAsSource; using Microsoft.CodeAnalysis.MetadataAsSource;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities; using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.MetadataAsSource namespace Microsoft.CodeAnalysis.CSharp.MetadataAsSource
{ {
internal class CSharpMetadataAsSourceService : AbstractMetadataAsSourceService internal partial class CSharpMetadataAsSourceService : AbstractMetadataAsSourceService
{ {
private static readonly AbstractFormattingRule s_memberSeparationRule = new FormattingRule(); private static readonly AbstractFormattingRule s_memberSeparationRule = new FormattingRule();
public static readonly CSharpMetadataAsSourceService Instance = new CSharpMetadataAsSourceService(); public static readonly CSharpMetadataAsSourceService Instance = new CSharpMetadataAsSourceService();
...@@ -37,16 +41,14 @@ protected override async Task<Document> AddAssemblyInfoRegionAsync(Document docu ...@@ -37,16 +41,14 @@ protected override async Task<Document> AddAssemblyInfoRegionAsync(Document docu
.WithTrailingTrivia(new[] { SyntaxFactory.Space, SyntaxFactory.PreprocessingMessage(assemblyInfo) }); .WithTrailingTrivia(new[] { SyntaxFactory.Space, SyntaxFactory.PreprocessingMessage(assemblyInfo) });
var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var newRoot = oldRoot.WithLeadingTrivia(new[] var newRoot = oldRoot.WithPrependedLeadingTrivia(
{
SyntaxFactory.Trivia(regionTrivia), SyntaxFactory.Trivia(regionTrivia),
SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.CarriageReturnLineFeed,
SyntaxFactory.Comment("// " + assemblyPath), SyntaxFactory.Comment("// " + assemblyPath),
SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.CarriageReturnLineFeed,
SyntaxFactory.Trivia(SyntaxFactory.EndRegionDirectiveTrivia(true)), SyntaxFactory.Trivia(SyntaxFactory.EndRegionDirectiveTrivia(true)),
SyntaxFactory.CarriageReturnLineFeed, SyntaxFactory.CarriageReturnLineFeed,
SyntaxFactory.CarriageReturnLineFeed SyntaxFactory.CarriageReturnLineFeed);
});
return document.WithSyntaxRoot(newRoot); return document.WithSyntaxRoot(newRoot);
} }
...@@ -71,55 +73,164 @@ protected override ImmutableArray<AbstractReducer> GetReducers() ...@@ -71,55 +73,164 @@ protected override ImmutableArray<AbstractReducer> GetReducers()
new CSharpParenthesizedPatternReducer(), new CSharpParenthesizedPatternReducer(),
new CSharpDefaultExpressionReducer()); new CSharpDefaultExpressionReducer());
private class FormattingRule : AbstractMetadataFormattingRule /// <summary>
/// Adds <c>#nullable enable</c> and <c>#nullable disable</c> annotations to the file as necessary. Note that
/// this does not try to be 100% accurate, but rather it handles the most common cases out there. Specifically,
/// if a file contains any nullable annotated/not-annotated types, then we prefix the file with <c>#nullable
/// enable</c>. Then if we hit any members that explicitly have *oblivious* types, but no annotated or
/// non-annotated types, then we switch to <c>#nullable disable</c> for those specific members.
/// <para/>
/// This is technically innacurate for possible, but very uncommon cases. For example, if the user's code
/// explicitly did something like this:
///
/// <code>
/// public void Goo(string goo,
/// #nullable disable
/// string bar
/// #nullable enable
/// string baz);
/// </code>
///
/// Then we would be unable to handle that. However, this is highly unlikely to happen, and so we accept the
/// inaccuracy for the purpose of simplicity and for handling the much more common cases of either the entire
/// file being annotated, or the user individually disabling annotations at the member level.
/// </summary>
protected override async Task<Document> AddNullableRegionsAsync(Document document, CancellationToken cancellationToken)
{ {
protected override AdjustNewLinesOperation GetAdjustNewLinesOperationBetweenMembersAndUsings(SyntaxToken token1, SyntaxToken token2) var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var options = (CSharpParseOptions)tree.Options;
// Only valid for C# 8 and above.
if (options.LanguageVersion < LanguageVersion.CSharp8)
return document;
var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false);
var (_, annotatedOrNotAnnotated) = GetNullableAnnotations(root);
// If there are no annotated or not-annotated types, then no need to add `#nullable enable`.
if (!annotatedOrNotAnnotated)
return document;
var newRoot = AddNullableRegions(root, cancellationToken);
newRoot = newRoot.WithPrependedLeadingTrivia(CreateNullableTrivia(enable: true));
return document.WithSyntaxRoot(newRoot);
}
private (bool oblivious, bool annotatedOrNotAnnotated) GetNullableAnnotations(SyntaxNode node)
{ {
var previousToken = token1; return (HasAnnotation(node, NullableSyntaxAnnotation.Oblivious),
var currentToken = token2; HasAnnotation(node, NullableSyntaxAnnotation.AnnotatedOrNotAnnotated));
}
// We are not between members or usings if the last token wasn't the end of a statement or if the current token private bool HasAnnotation(SyntaxNode node, SyntaxAnnotation annotation)
// is the end of a scope.
if ((previousToken.Kind() != SyntaxKind.SemicolonToken && previousToken.Kind() != SyntaxKind.CloseBraceToken) ||
currentToken.Kind() == SyntaxKind.CloseBraceToken)
{ {
return null; // see if any child nodes have this annotation. Ignore anything in attributes (like `[Obsolete]void Goo()`
// as these are not impacted by `#nullable` regions. Instead, we only care about signature types.
var annotatedChildren = node.GetAnnotatedNodes(annotation);
return annotatedChildren.Any(n => n.GetAncestorOrThis<AttributeSyntax>() == null);
} }
SyntaxNode previousMember = FormattingRangeHelper.GetEnclosingMember(previousToken); private static SyntaxTrivia[] CreateNullableTrivia(bool enable)
SyntaxNode nextMember = FormattingRangeHelper.GetEnclosingMember(currentToken); {
var keyword = enable ? SyntaxKind.EnableKeyword : SyntaxKind.DisableKeyword;
return new[]
{
SyntaxFactory.Trivia(SyntaxFactory.NullableDirectiveTrivia(SyntaxFactory.Token(keyword), isActive: enable)),
SyntaxFactory.ElasticCarriageReturnLineFeed,
SyntaxFactory.ElasticCarriageReturnLineFeed,
};
}
// Is the previous statement an using directive? If so, treat it like a member to add private TSyntax AddNullableRegions<TSyntax>(TSyntax node, CancellationToken cancellationToken)
// the right number of lines. where TSyntax : SyntaxNode
if (previousToken.Kind() == SyntaxKind.SemicolonToken && previousToken.Parent.Kind() == SyntaxKind.UsingDirective)
{ {
previousMember = previousToken.Parent; return node switch
{
CompilationUnitSyntax compilationUnit => (TSyntax)(object)compilationUnit.WithMembers(AddNullableRegions(compilationUnit.Members, cancellationToken)),
NamespaceDeclarationSyntax ns => (TSyntax)(object)ns.WithMembers(AddNullableRegions(ns.Members, cancellationToken)),
TypeDeclarationSyntax type => (TSyntax)(object)AddNullableRegionsAroundTypeMembers(type, cancellationToken),
_ => node,
};
} }
if (previousMember == null || nextMember == null || previousMember == nextMember) private SyntaxList<MemberDeclarationSyntax> AddNullableRegions(
SyntaxList<MemberDeclarationSyntax> members,
CancellationToken cancellationToken)
{ {
return null; using var _ = ArrayBuilder<MemberDeclarationSyntax>.GetInstance(out var builder);
foreach (var member in members)
builder.Add(AddNullableRegions(member, cancellationToken));
return SyntaxFactory.List(builder);
} }
// If we have two members of the same kind, we won't insert a blank line private TypeDeclarationSyntax AddNullableRegionsAroundTypeMembers(
if (previousMember.Kind() == nextMember.Kind()) TypeDeclarationSyntax type, CancellationToken cancellationToken)
{
using var _ = ArrayBuilder<MemberDeclarationSyntax>.GetInstance(out var builder);
var currentlyEnabled = true;
foreach (var member in type.Members)
{ {
return FormattingOperations.CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.ForceLines); cancellationToken.ThrowIfCancellationRequested();
if (member is BaseTypeDeclarationSyntax)
{
// if we hit a type, and we're currently disabled, then switch us back to enabled for that type.
// This ensures whenever we walk into a type-decl, we're always in the enabled-state.
builder.Add(TransitionTo(AddNullableRegions(member, cancellationToken), enabled: true, ref currentlyEnabled));
continue;
} }
// Force a blank line between the two nodes by counting the number of lines of // we hit a member. see what sort of types it contained.
// trivia and adding one to it. var (oblivious, annotatedOrNotAnnotated) = GetNullableAnnotations(member);
var triviaList = token1.TrailingTrivia.Concat(token2.LeadingTrivia);
return FormattingOperations.CreateAdjustNewLinesOperation(GetNumberOfLines(triviaList) + 1, AdjustNewLinesOption.ForceLines); // if we have null annotations, transition us back to the enabled state
if (annotatedOrNotAnnotated)
{
builder.Add(TransitionTo(member, enabled: true, ref currentlyEnabled));
}
else if (oblivious)
{
// if we didn't have null annotations, and we had an explicit oblivious type,
// then definitely transition us to the disabled state
builder.Add(TransitionTo(member, enabled: false, ref currentlyEnabled));
}
else
{
// had no types at all. no need to change state.
builder.Add(member);
}
} }
public override void AddAnchorIndentationOperations(List<AnchorIndentationOperation> list, SyntaxNode node, in NextAnchorIndentationOperationAction nextOperation) var result = type.WithMembers(SyntaxFactory.List(builder));
if (!currentlyEnabled)
{ {
return; // switch us back to enabled as we leave the type.
result = result.WithCloseBraceToken(
result.CloseBraceToken.WithPrependedLeadingTrivia(CreateNullableTrivia(enable: true)));
} }
protected override bool IsNewLine(char c) return result;
=> SyntaxFacts.IsNewLine(c); }
private MemberDeclarationSyntax TransitionTo(MemberDeclarationSyntax member, bool enabled, ref bool currentlyEnabled)
{
if (enabled == currentlyEnabled)
{
// already in the right state. don't start a #nullable region
return member;
}
else
{
// switch to the desired state and add the right trivia to the node.
currentlyEnabled = enabled;
return member.WithPrependedLeadingTrivia(CreateNullableTrivia(currentlyEnabled));
}
} }
} }
} }
// 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 Microsoft.CodeAnalysis.CSharp.Utilities;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.MetadataAsSource
{
internal partial class CSharpMetadataAsSourceService
{
private class FormattingRule : AbstractMetadataFormattingRule
{
protected override AdjustNewLinesOperation GetAdjustNewLinesOperationBetweenMembersAndUsings(SyntaxToken token1, SyntaxToken token2)
{
var previousToken = token1;
var currentToken = token2;
// We are not between members or usings if the last token wasn't the end of a statement or if the current token
// is the end of a scope.
if ((previousToken.Kind() != SyntaxKind.SemicolonToken && previousToken.Kind() != SyntaxKind.CloseBraceToken) ||
currentToken.Kind() == SyntaxKind.CloseBraceToken)
{
return null;
}
SyntaxNode previousMember = FormattingRangeHelper.GetEnclosingMember(previousToken);
SyntaxNode nextMember = FormattingRangeHelper.GetEnclosingMember(currentToken);
// Is the previous statement an using directive? If so, treat it like a member to add
// the right number of lines.
if (previousToken.Kind() == SyntaxKind.SemicolonToken && previousToken.Parent.Kind() == SyntaxKind.UsingDirective)
{
previousMember = previousToken.Parent;
}
if (previousMember == null || nextMember == null || previousMember == nextMember)
{
return null;
}
// If we have two members of the same kind, we won't insert a blank line
if (previousMember.Kind() == nextMember.Kind())
{
return FormattingOperations.CreateAdjustNewLinesOperation(1, AdjustNewLinesOption.ForceLines);
}
// Force a blank line between the two nodes by counting the number of lines of
// trivia and adding one to it.
var triviaList = token1.TrailingTrivia.Concat(token2.LeadingTrivia);
return FormattingOperations.CreateAdjustNewLinesOperation(GetNumberOfLines(triviaList) + 1, AdjustNewLinesOption.ForceLines);
}
public override void AddAnchorIndentationOperations(List<AnchorIndentationOperation> list, SyntaxNode node, in NextAnchorIndentationOperationAction nextOperation)
{
return;
}
protected override bool IsNewLine(char c)
=> SyntaxFacts.IsNewLine(c);
}
}
}
...@@ -5,12 +5,10 @@ ...@@ -5,12 +5,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeGeneration; using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.DocumentationComments;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.LanguageServices;
...@@ -41,7 +39,7 @@ public async Task<Document> AddSourceToAsync(Document document, Compilation symb ...@@ -41,7 +39,7 @@ public async Task<Document> AddSourceToAsync(Document document, Compilation symb
CreateCodeGenerationOptions(newSemanticModel.SyntaxTree.GetLocation(new TextSpan())), CreateCodeGenerationOptions(newSemanticModel.SyntaxTree.GetLocation(new TextSpan())),
cancellationToken).ConfigureAwait(false); cancellationToken).ConfigureAwait(false);
document = await RemoveSimplifierAnnotationsFromImportsAsync(document, cancellationToken).ConfigureAwait(false); document = await AddNullableRegionsAsync(document, cancellationToken).ConfigureAwait(false);
var docCommentFormattingService = document.GetLanguageService<IDocumentationCommentFormattingService>(); var docCommentFormattingService = document.GetLanguageService<IDocumentationCommentFormattingService>();
var docWithDocComments = await ConvertDocCommentsToRegularCommentsAsync(document, docCommentFormattingService, cancellationToken).ConfigureAwait(false); var docWithDocComments = await ConvertDocCommentsToRegularCommentsAsync(document, docCommentFormattingService, cancellationToken).ConfigureAwait(false);
...@@ -55,26 +53,7 @@ public async Task<Document> AddSourceToAsync(Document document, Compilation symb ...@@ -55,26 +53,7 @@ public async Task<Document> AddSourceToAsync(Document document, Compilation symb
return await Simplifier.ReduceAsync(formattedDoc, reducers, null, cancellationToken).ConfigureAwait(false); return await Simplifier.ReduceAsync(formattedDoc, reducers, null, cancellationToken).ConfigureAwait(false);
} }
/// <summary> protected abstract Task<Document> AddNullableRegionsAsync(Document document, CancellationToken cancellationToken);
/// <see cref="ImportAdderService"/> adds <see cref="Simplifier.Annotation"/> to Import Directives it adds,
/// which causes the <see cref="Simplifier"/> to remove import directives when thety are only used by attributes.
/// Presumably this is because MetadataAsSource isn't actually semantically valid code.
///
/// To fix this we remove these annotations.
/// </summary>
private static async Task<Document> RemoveSimplifierAnnotationsFromImportsAsync(Document document, CancellationToken cancellationToken)
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var importDirectives = (await document.GetSyntaxRootAsync().ConfigureAwait(false))
.DescendantNodesAndSelf()
.Where(syntaxFacts.IsUsingOrExternOrImport);
return await document.ReplaceNodesAsync(
importDirectives,
(o, c) => c.WithoutAnnotations(Simplifier.Annotation),
cancellationToken).ConfigureAwait(false);
}
/// <summary> /// <summary>
/// provide formatting rules to be used when formatting MAS file /// provide formatting rules to be used when formatting MAS file
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
Imports System.Collections.Immutable Imports System.Collections.Immutable
Imports System.Threading Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.DocumentationComments Imports Microsoft.CodeAnalysis.DocumentationComments
Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.Formatting.Rules Imports Microsoft.CodeAnalysis.Formatting.Rules
...@@ -49,6 +50,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MetadataAsSource ...@@ -49,6 +50,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MetadataAsSource
Return document.WithSyntaxRoot(newRoot) Return document.WithSyntaxRoot(newRoot)
End Function End Function
Protected Overrides Function AddNullableRegionsAsync(document As Document, cancellationToken As CancellationToken) As Task(Of Document)
' VB has no equivalent to #nullable enable
Return Task.FromResult(document)
End Function
Protected Overrides Async Function ConvertDocCommentsToRegularCommentsAsync(document As Document, docCommentFormattingService As IDocumentationCommentFormattingService, cancellationToken As CancellationToken) As Task(Of Document) Protected Overrides Async Function ConvertDocCommentsToRegularCommentsAsync(document As Document, docCommentFormattingService As IDocumentationCommentFormattingService, cancellationToken As CancellationToken) As Task(Of Document)
Dim syntaxRoot = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) Dim syntaxRoot = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license. // The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
#nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq; using System.Linq;
...@@ -23,7 +25,10 @@ internal static class AttributeGenerator ...@@ -23,7 +25,10 @@ internal static class AttributeGenerator
{ {
if (options.MergeAttributes) if (options.MergeAttributes)
{ {
var attributeNodes = attributes.OrderBy(a => a.AttributeClass.Name).Select(a => GenerateAttribute(a, options)).WhereNotNull().ToList(); var attributeNodes =
attributes.OrderBy(a => a.AttributeClass?.Name)
.Select(a => TryGenerateAttribute(a, options))
.WhereNotNull().ToList();
return attributeNodes.Count == 0 return attributeNodes.Count == 0
? default ? default
: SyntaxFactory.SingletonList(SyntaxFactory.AttributeList( : SyntaxFactory.SingletonList(SyntaxFactory.AttributeList(
...@@ -32,26 +37,34 @@ internal static class AttributeGenerator ...@@ -32,26 +37,34 @@ internal static class AttributeGenerator
} }
else else
{ {
var attributeDeclarations = attributes.OrderBy(a => a.AttributeClass.Name).Select(a => GenerateAttributeDeclaration(a, target, options)).WhereNotNull().ToList(); var attributeDeclarations =
attributes.OrderBy(a => a.AttributeClass?.Name)
.Select(a => TryGenerateAttributeDeclaration(a, target, options))
.WhereNotNull().ToList();
return attributeDeclarations.Count == 0 return attributeDeclarations.Count == 0
? default ? default
: SyntaxFactory.List<AttributeListSyntax>(attributeDeclarations); : SyntaxFactory.List<AttributeListSyntax>(attributeDeclarations);
} }
} }
private static AttributeListSyntax GenerateAttributeDeclaration( private static AttributeListSyntax? TryGenerateAttributeDeclaration(
AttributeData attribute, SyntaxToken? target, CodeGenerationOptions options) AttributeData attribute, SyntaxToken? target, CodeGenerationOptions options)
{ {
var attributeSyntax = GenerateAttribute(attribute, options); var attributeSyntax = TryGenerateAttribute(attribute, options);
return attributeSyntax == null return attributeSyntax == null
? null ? null
: SyntaxFactory.AttributeList( : SyntaxFactory.AttributeList(
target.HasValue ? SyntaxFactory.AttributeTargetSpecifier(target.Value) : null, target.HasValue
? SyntaxFactory.AttributeTargetSpecifier(target.Value)
: null,
SyntaxFactory.SingletonSeparatedList(attributeSyntax)); SyntaxFactory.SingletonSeparatedList(attributeSyntax));
} }
private static AttributeSyntax GenerateAttribute(AttributeData attribute, CodeGenerationOptions options) private static AttributeSyntax? TryGenerateAttribute(AttributeData attribute, CodeGenerationOptions options)
{ {
if (IsCompilerInternalAttribute(attribute))
return null;
if (!options.MergeAttributes) if (!options.MergeAttributes)
{ {
var reusableSyntax = GetReuseableSyntaxNodeForAttribute<AttributeSyntax>(attribute, options); var reusableSyntax = GetReuseableSyntaxNodeForAttribute<AttributeSyntax>(attribute, options);
...@@ -61,17 +74,45 @@ private static AttributeSyntax GenerateAttribute(AttributeData attribute, CodeGe ...@@ -61,17 +74,45 @@ private static AttributeSyntax GenerateAttribute(AttributeData attribute, CodeGe
} }
} }
if (attribute.AttributeClass == null)
return null;
var attributeArguments = GenerateAttributeArgumentList(attribute); var attributeArguments = GenerateAttributeArgumentList(attribute);
return !(attribute.AttributeClass.GenerateTypeSyntax() is NameSyntax nameSyntax) ? null : SyntaxFactory.Attribute(nameSyntax, attributeArguments); return attribute.AttributeClass.GenerateTypeSyntax() is NameSyntax nameSyntax
? SyntaxFactory.Attribute(nameSyntax, attributeArguments)
: null;
} }
private static AttributeArgumentListSyntax GenerateAttributeArgumentList(AttributeData attribute)
private static bool IsCompilerInternalAttribute(AttributeData attribute)
{ {
if (attribute.ConstructorArguments.Length == 0 && attribute.NamedArguments.Length == 0) // from https://github.com/dotnet/roslyn/blob/master/docs/features/nullable-metadata.md
var attrClass = attribute.AttributeClass;
if (attrClass == null)
return false;
var name = attrClass.Name;
if (name != "NullableAttribute" &&
name != "NullableContextAttribute" &&
name != "NativeIntegerAttribute" &&
name != "DynamicAttribute")
{ {
return null; return false;
}
var ns = attrClass.ContainingNamespace;
return ns?.Name == nameof(System.Runtime.CompilerServices) &&
ns.ContainingNamespace?.Name == nameof(System.Runtime) &&
ns.ContainingNamespace.ContainingNamespace?.Name == nameof(System) &&
ns.ContainingNamespace.ContainingNamespace.ContainingNamespace?.IsGlobalNamespace == true;
} }
private static AttributeArgumentListSyntax? GenerateAttributeArgumentList(AttributeData attribute)
{
if (attribute.ConstructorArguments.Length == 0 && attribute.NamedArguments.Length == 0)
return null;
var arguments = new List<AttributeArgumentSyntax>(); var arguments = new List<AttributeArgumentSyntax>();
arguments.AddRange(attribute.ConstructorArguments.Select(c => arguments.AddRange(attribute.ConstructorArguments.Select(c =>
SyntaxFactory.AttributeArgument(ExpressionGenerator.GenerateExpression(c)))); SyntaxFactory.AttributeArgument(ExpressionGenerator.GenerateExpression(c))));
......
...@@ -183,7 +183,6 @@ protected override void GetUnusedNamespaceImports(SemanticModel model, HashSet<S ...@@ -183,7 +183,6 @@ protected override void GetUnusedNamespaceImports(SemanticModel model, HashSet<S
{ {
if (diagnostic.Id == s_CS8019_UnusedUsingDirective) if (diagnostic.Id == s_CS8019_UnusedUsingDirective)
{ {
if (root.FindNode(diagnostic.Location.SourceSpan) is UsingDirectiveSyntax node) if (root.FindNode(diagnostic.Location.SourceSpan) is UsingDirectiveSyntax node)
{ {
namespaceImports.Add(node); namespaceImports.Add(node);
......
// 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 Microsoft.CodeAnalysis.Editing;
namespace Microsoft.CodeAnalysis.CodeGeneration
{
/// <summary>
/// Annotation placed on <see cref="ITypeSymbol"/>s that the <see cref="SyntaxGenerator"/> converts to a node. This
/// information tracks the original nullable state of the symbol and is used by metadata-as-source to determine if
/// it needs to add <c>#nullable</c> directives in the file.
/// </summary>
internal sealed class NullableSyntaxAnnotation
{
/// <summary>
/// For <c>string~</c> types.
/// </summary>
public static readonly SyntaxAnnotation Oblivious = new SyntaxAnnotation($"{nameof(NullableSyntaxAnnotation)}.{Oblivious}");
/// <summary>
/// For <c>string!</c> or <c>string?</c> types.
/// </summary>
public static readonly SyntaxAnnotation AnnotatedOrNotAnnotated = new SyntaxAnnotation($"{nameof(NullableSyntaxAnnotation)}.{AnnotatedOrNotAnnotated}");
}
}
...@@ -213,7 +213,7 @@ public static IEnumerable<TSymbol> FindSimilarSymbols<TSymbol>(TSymbol symbol, C ...@@ -213,7 +213,7 @@ public static IEnumerable<TSymbol> FindSimilarSymbols<TSymbol>(TSymbol symbol, C
// We may be talking about different compilations. So do not try to resolve locations. // We may be talking about different compilations. So do not try to resolve locations.
var result = new HashSet<TSymbol>(); var result = new HashSet<TSymbol>();
var resolution = key.Resolve(compilation, resolveLocations: false, cancellationToken: cancellationToken); var resolution = key.Resolve(compilation, cancellationToken: cancellationToken);
foreach (var current in resolution.OfType<TSymbol>()) foreach (var current in resolution.OfType<TSymbol>())
{ {
result.Add(current); result.Add(current);
......
...@@ -76,7 +76,7 @@ public static SerializableSymbolAndProjectId Create(ISymbol symbol, Project proj ...@@ -76,7 +76,7 @@ public static SerializableSymbolAndProjectId Create(ISymbol symbol, Project proj
// The server and client should both be talking about the same compilation. As such // The server and client should both be talking about the same compilation. As such
// locations in symbols are save to resolve as we rehydrate the SymbolKey. // locations in symbols are save to resolve as we rehydrate the SymbolKey.
var symbol = SymbolKey.ResolveString( var symbol = SymbolKey.ResolveString(
SymbolKeyData, compilation, resolveLocations: true, cancellationToken: cancellationToken).GetAnySymbol(); SymbolKeyData, compilation, cancellationToken: cancellationToken).GetAnySymbol();
if (symbol == null) if (symbol == null)
{ {
......
...@@ -275,7 +275,6 @@ private class SymbolKeyReader : Reader<string> ...@@ -275,7 +275,6 @@ private class SymbolKeyReader : Reader<string>
public SymbolEquivalenceComparer Comparer { get; private set; } public SymbolEquivalenceComparer Comparer { get; private set; }
private readonly List<IMethodSymbol> _methodSymbolStack = new List<IMethodSymbol>(); private readonly List<IMethodSymbol> _methodSymbolStack = new List<IMethodSymbol>();
private bool _resolveLocations;
public SymbolKeyReader() public SymbolKeyReader()
{ {
...@@ -289,7 +288,6 @@ public override void Dispose() ...@@ -289,7 +288,6 @@ public override void Dispose()
_idToResult.Clear(); _idToResult.Clear();
Compilation = null; Compilation = null;
IgnoreAssemblyKey = false; IgnoreAssemblyKey = false;
_resolveLocations = true;
Comparer = null; Comparer = null;
_methodSymbolStack.Clear(); _methodSymbolStack.Clear();
...@@ -299,11 +297,11 @@ public override void Dispose() ...@@ -299,11 +297,11 @@ public override void Dispose()
public static SymbolKeyReader GetReader( public static SymbolKeyReader GetReader(
string data, Compilation compilation, string data, Compilation compilation,
bool ignoreAssemblyKey, bool resolveLocations, bool ignoreAssemblyKey,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var reader = s_readerPool.Allocate(); var reader = s_readerPool.Allocate();
reader.Initialize(data, compilation, ignoreAssemblyKey, resolveLocations, cancellationToken); reader.Initialize(data, compilation, ignoreAssemblyKey, cancellationToken);
return reader; return reader;
} }
...@@ -311,13 +309,11 @@ public override void Dispose() ...@@ -311,13 +309,11 @@ public override void Dispose()
string data, string data,
Compilation compilation, Compilation compilation,
bool ignoreAssemblyKey, bool ignoreAssemblyKey,
bool resolveLocations,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
base.Initialize(data, cancellationToken); base.Initialize(data, cancellationToken);
Compilation = compilation; Compilation = compilation;
IgnoreAssemblyKey = ignoreAssemblyKey; IgnoreAssemblyKey = ignoreAssemblyKey;
_resolveLocations = resolveLocations;
Comparer = ignoreAssemblyKey Comparer = ignoreAssemblyKey
? SymbolEquivalenceComparer.IgnoreAssembliesInstance ? SymbolEquivalenceComparer.IgnoreAssembliesInstance
...@@ -508,8 +504,6 @@ public Location ReadLocation() ...@@ -508,8 +504,6 @@ public Location ReadLocation()
var start = ReadInteger(); var start = ReadInteger();
var length = ReadInteger(); var length = ReadInteger();
if (_resolveLocations)
{
// The syntax tree can be null if we're resolving this location in a compilation // The syntax tree can be null if we're resolving this location in a compilation
// that does not contain this file. In this case, just map this location to None. // that does not contain this file. In this case, just map this location to None.
var syntaxTree = GetSyntaxTree(filePath); var syntaxTree = GetSyntaxTree(filePath);
...@@ -518,14 +512,11 @@ public Location ReadLocation() ...@@ -518,14 +512,11 @@ public Location ReadLocation()
return Location.Create(syntaxTree, new TextSpan(start, length)); return Location.Create(syntaxTree, new TextSpan(start, length));
} }
} }
}
else if (kind == LocationKind.MetadataFile) else if (kind == LocationKind.MetadataFile)
{ {
var assemblyResolution = ReadSymbolKey(); var assemblyResolution = ReadSymbolKey();
var moduleName = ReadString(); var moduleName = ReadString();
if (_resolveLocations)
{
// We may be resolving in a compilation where we don't have a module // We may be resolving in a compilation where we don't have a module
// with this name. In that case, just map this location to none. // with this name. In that case, just map this location to none.
if (assemblyResolution.GetAnySymbol() is IAssemblySymbol assembly) if (assemblyResolution.GetAnySymbol() is IAssemblySymbol assembly)
...@@ -541,7 +532,6 @@ public Location ReadLocation() ...@@ -541,7 +532,6 @@ public Location ReadLocation()
} }
} }
} }
}
return Location.None; return Location.None;
} }
......
...@@ -77,11 +77,10 @@ namespace Microsoft.CodeAnalysis ...@@ -77,11 +77,10 @@ namespace Microsoft.CodeAnalysis
/// The SymbolKey for both 'M' methods will be the same. The SymbolKey will then resolve to both methods. /// The SymbolKey for both 'M' methods will be the same. The SymbolKey will then resolve to both methods.
/// </para> /// </para>
/// <para> /// <para>
/// <see cref="SymbolKey"/>s are not guaranteed to work across different versions of Roslyn. /// <see cref="SymbolKey"/>s are not guaranteed to work across different versions of Roslyn. They can be persisted
/// They can be persisted in their <see cref="ToString()"/> form and used across sessions with /// in their <see cref="ToString()"/> form and used across sessions with the same version of Roslyn. However, future
/// the same version of Roslyn. However, future versions may change the encoded format and may /// versions may change the encoded format and may no longer be able to <see cref="Resolve"/> previous keys. As
/// no longer be able to <see cref="Resolve(Compilation, bool, bool, CancellationToken)"/> previous /// such, only persist if using for a cache that can be regenerated if necessary.
/// keys. As such, only persist if using for a cache that can be regenerated if necessary.
/// </para> /// </para>
/// </summary> /// </summary>
internal partial struct SymbolKey internal partial struct SymbolKey
...@@ -128,11 +127,10 @@ internal static IEqualityComparer<SymbolKey> GetComparer(bool ignoreCase = false ...@@ -128,11 +127,10 @@ internal static IEqualityComparer<SymbolKey> GetComparer(bool ignoreCase = false
internal static SymbolKeyResolution ResolveString( internal static SymbolKeyResolution ResolveString(
string symbolKey, Compilation compilation, string symbolKey, Compilation compilation,
bool ignoreAssemblyKey = false, bool resolveLocations = true, bool ignoreAssemblyKey = false, CancellationToken cancellationToken = default)
CancellationToken cancellationToken = default)
{ {
using var reader = SymbolKeyReader.GetReader( using var reader = SymbolKeyReader.GetReader(
symbolKey, compilation, ignoreAssemblyKey, resolveLocations, cancellationToken); symbolKey, compilation, ignoreAssemblyKey, cancellationToken);
var version = reader.ReadFormatVersion(); var version = reader.ReadFormatVersion();
if (version != FormatVersion) if (version != FormatVersion)
{ {
...@@ -158,15 +156,12 @@ internal static string CreateStringWorker(int version, ISymbol symbol, Cancellat ...@@ -158,15 +156,12 @@ internal static string CreateStringWorker(int version, ISymbol symbol, Cancellat
/// <summary> /// <summary>
/// Tries to resolve this <see cref="SymbolKey"/> in the given /// Tries to resolve this <see cref="SymbolKey"/> in the given
/// <paramref name="compilation"/> to a matching symbol. <paramref name="resolveLocations"/> /// <paramref name="compilation"/> to a matching symbol.
/// should only be given <see langword="true"/> if the symbol was produced from a compilation
/// that has the exact same source as the compilation we're resolving against. Otherwise
/// the locations resolved may not actually be correct in the final compilation.
/// </summary> /// </summary>
public SymbolKeyResolution Resolve( public SymbolKeyResolution Resolve(
Compilation compilation, bool ignoreAssemblyKey = false, bool resolveLocations = true, CancellationToken cancellationToken = default) Compilation compilation, bool ignoreAssemblyKey = false, CancellationToken cancellationToken = default)
{ {
return ResolveString(_symbolKeyData, compilation, ignoreAssemblyKey, resolveLocations, cancellationToken); return ResolveString(_symbolKeyData, compilation, ignoreAssemblyKey, cancellationToken);
} }
/// <summary> /// <summary>
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Threading;
#if DEBUG #if DEBUG
using System.Diagnostics; using System.Diagnostics;
...@@ -12,11 +11,10 @@ ...@@ -12,11 +11,10 @@
namespace Microsoft.CodeAnalysis namespace Microsoft.CodeAnalysis
{ {
/// <summary> /// <summary>
/// The result of <see cref="SymbolKey.Resolve(Compilation, bool, bool, CancellationToken)"/>. /// The result of <see cref="SymbolKey.Resolve"/>. If the <see cref="SymbolKey"/> could be uniquely mapped to a
/// If the <see cref="SymbolKey"/> could be uniquely mapped to a single <see cref="ISymbol"/> /// single <see cref="ISymbol"/> then that will be returned in <see cref="Symbol"/>. Otherwise, if the key resolves
/// then that will be returned in <see cref="Symbol"/>. Otherwise, if /// to multiple symbols (which can happen in error scenarios), then <see cref="CandidateSymbols"/> and <see
/// the key resolves to multiple symbols (which can happen in error scenarios), then /// cref="CandidateReason"/> will be returned.
/// <see cref="CandidateSymbols"/> and <see cref="CandidateReason"/> will be returned.
/// ///
/// If no symbol can be found <see cref="Symbol"/> will be <c>null</c> and <see cref="CandidateSymbols"/> /// If no symbol can be found <see cref="Symbol"/> will be <c>null</c> and <see cref="CandidateSymbols"/>
/// will be empty. /// will be empty.
......
...@@ -223,7 +223,7 @@ public class C ...@@ -223,7 +223,7 @@ public class C
public A GetA<A>(A a) { return a; } public A GetA<A>(A a) { return a; }
public A GetA<A, B>(A a, B b) { return a; } public A GetA<A, B>(A a, B b) { return a; }
public B GetB<A, B>(A a, B b) { return b; } public B GetB<A, B>(A a, B b) { return b; }
publi C GetC() { return default(C); } public C GetC() { return default(C); }
} }
public class C<T> public class C<T>
...@@ -699,7 +699,7 @@ object LocalFunction<T>() ...@@ -699,7 +699,7 @@ object LocalFunction<T>()
Assert.NotNull(found); Assert.NotNull(found);
// note: we don't check that the symbols are equal. That's because the compiler // note: we don't check that the symbols are equal. That's because the compiler
// doesn't guarantee that the TypeParameters will be hte same across successive // doesn't guarantee that the TypeParameters will be the same across successive
// invocations. // invocations.
Assert.Equal(symbol.OriginalDefinition, found.OriginalDefinition); Assert.Equal(symbol.OriginalDefinition, found.OriginalDefinition);
...@@ -736,7 +736,7 @@ void Method((C, int) t) ...@@ -736,7 +736,7 @@ void Method((C, int) t)
// Validate that if the client does ask to resolve locations that we // Validate that if the client does ask to resolve locations that we
// do not crash if those locations cannot be found. // do not crash if those locations cannot be found.
var found = SymbolKey.ResolveString(id, compilation2, resolveLocations: true).GetAnySymbol(); var found = SymbolKey.ResolveString(id, compilation2).GetAnySymbol();
Assert.NotNull(found); Assert.NotNull(found);
Assert.Equal(symbol.Name, found.Name); Assert.Equal(symbol.Name, found.Name);
...@@ -773,7 +773,7 @@ void Method((C a, int b) t) ...@@ -773,7 +773,7 @@ void Method((C a, int b) t)
// Validate that if the client does ask to resolve locations that we // Validate that if the client does ask to resolve locations that we
// do not crash if those locations cannot be found. // do not crash if those locations cannot be found.
var found = SymbolKey.ResolveString(id, compilation2, resolveLocations: true).GetAnySymbol(); var found = SymbolKey.ResolveString(id, compilation2).GetAnySymbol();
Assert.NotNull(found); Assert.NotNull(found);
Assert.Equal(symbol.Name, found.Name); Assert.Equal(symbol.Name, found.Name);
......
...@@ -304,7 +304,13 @@ public override TypeSyntax VisitPointerType(IPointerTypeSymbol symbol) ...@@ -304,7 +304,13 @@ public override TypeSyntax VisitPointerType(IPointerTypeSymbol symbol)
} }
public override TypeSyntax VisitTypeParameter(ITypeParameterSymbol symbol) public override TypeSyntax VisitTypeParameter(ITypeParameterSymbol symbol)
=> AddInformationTo(symbol.Name.ToIdentifierName(), symbol); {
TypeSyntax typeSyntax = AddInformationTo(symbol.Name.ToIdentifierName(), symbol);
if (symbol.NullableAnnotation == NullableAnnotation.Annotated)
typeSyntax = AddInformationTo(SyntaxFactory.NullableType(typeSyntax), symbol);
return typeSyntax;
}
} }
} }
} }
...@@ -16,6 +16,10 @@ ...@@ -16,6 +16,10 @@
using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities; using Roslyn.Utilities;
#if !CODE_STYLE
using Microsoft.CodeAnalysis.CodeGeneration;
#endif
namespace Microsoft.CodeAnalysis.CSharp.Extensions namespace Microsoft.CodeAnalysis.CSharp.Extensions
{ {
internal static partial class ITypeSymbolExtensions internal static partial class ITypeSymbolExtensions
...@@ -41,7 +45,8 @@ internal static partial class ITypeSymbolExtensions ...@@ -41,7 +45,8 @@ internal static partial class ITypeSymbolExtensions
private static TypeSyntax GenerateTypeSyntax( private static TypeSyntax GenerateTypeSyntax(
INamespaceOrTypeSymbol symbol, bool nameSyntax, bool allowVar = true) INamespaceOrTypeSymbol symbol, bool nameSyntax, bool allowVar = true)
{ {
if (symbol is ITypeSymbol type && type.ContainsAnonymousType()) var type = symbol as ITypeSymbol;
if (type != null && type.ContainsAnonymousType())
{ {
// something with an anonymous type can only be represented with 'var', regardless // something with an anonymous type can only be represented with 'var', regardless
// of what the user's preferences might be. // of what the user's preferences might be.
...@@ -56,6 +61,22 @@ internal static partial class ITypeSymbolExtensions ...@@ -56,6 +61,22 @@ internal static partial class ITypeSymbolExtensions
syntax = syntax.WithAdditionalAnnotations(DoNotAllowVarAnnotation.Annotation); syntax = syntax.WithAdditionalAnnotations(DoNotAllowVarAnnotation.Annotation);
} }
#if !CODE_STYLE
if (type != null && type.IsReferenceType)
{
syntax = syntax.WithAdditionalAnnotations(
type.NullableAnnotation switch
{
NullableAnnotation.None => NullableSyntaxAnnotation.Oblivious,
NullableAnnotation.Annotated => NullableSyntaxAnnotation.AnnotatedOrNotAnnotated,
NullableAnnotation.NotAnnotated => NullableSyntaxAnnotation.AnnotatedOrNotAnnotated,
_ => throw ExceptionUtilities.UnexpectedValue(type.NullableAnnotation),
});
}
#endif
return syntax; return syntax;
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册