未验证 提交 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
string b;
T? c;
public Z(int a, string b, T c{|Navigation:)|}
public Z(int a, string b, T? c{|Navigation:)|}
{
this.a = a;
this.b = b ?? throw new ArgumentNullException(nameof(b));
......
......@@ -43,14 +43,10 @@ public async Task TestNativeInteger()
// {CodeAnalysisResources.InMemoryAssembly}
#endregion
using System;
using System.Runtime.CompilerServices;
public class [|C|]
{{
[NativeIntegerAttribute]
public nint i;
[NativeIntegerAttribute]
public nuint i2;
public C();
......@@ -88,8 +84,6 @@ public async Task TestValueTuple()
// System.ValueTuple.dll
#endregion
using System;
using System;
using System.Collections;
namespace System
......
......@@ -44,7 +44,6 @@ public async Task BracketedIdentifierSimplificationTest()
' mscorlib.v4_6_1038_0.dll
#End Region
Imports System
Imports System.Runtime.InteropServices
Namespace System
......@@ -95,8 +94,6 @@ public async Task TestValueTuple()
' System.ValueTuple.dll
#End Region
Imports System
Imports System
Imports System.Collections
Namespace System
......
......@@ -7,19 +7,23 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.DocumentationComments;
using Microsoft.CodeAnalysis.CSharp.Simplification;
using Microsoft.CodeAnalysis.CSharp.Utilities;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.DocumentationComments;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.MetadataAsSource;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.MetadataAsSource
{
internal class CSharpMetadataAsSourceService : AbstractMetadataAsSourceService
internal partial class CSharpMetadataAsSourceService : AbstractMetadataAsSourceService
{
private static readonly AbstractFormattingRule s_memberSeparationRule = new FormattingRule();
public static readonly CSharpMetadataAsSourceService Instance = new CSharpMetadataAsSourceService();
......@@ -37,16 +41,14 @@ protected override async Task<Document> AddAssemblyInfoRegionAsync(Document docu
.WithTrailingTrivia(new[] { SyntaxFactory.Space, SyntaxFactory.PreprocessingMessage(assemblyInfo) });
var oldRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var newRoot = oldRoot.WithLeadingTrivia(new[]
{
var newRoot = oldRoot.WithPrependedLeadingTrivia(
SyntaxFactory.Trivia(regionTrivia),
SyntaxFactory.CarriageReturnLineFeed,
SyntaxFactory.Comment("// " + assemblyPath),
SyntaxFactory.CarriageReturnLineFeed,
SyntaxFactory.Trivia(SyntaxFactory.EndRegionDirectiveTrivia(true)),
SyntaxFactory.CarriageReturnLineFeed,
SyntaxFactory.CarriageReturnLineFeed
});
SyntaxFactory.CarriageReturnLineFeed);
return document.WithSyntaxRoot(newRoot);
}
......@@ -71,55 +73,164 @@ protected override ImmutableArray<AbstractReducer> GetReducers()
new CSharpParenthesizedPatternReducer(),
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;
var currentToken = token2;
return (HasAnnotation(node, NullableSyntaxAnnotation.Oblivious),
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
// is the end of a scope.
if ((previousToken.Kind() != SyntaxKind.SemicolonToken && previousToken.Kind() != SyntaxKind.CloseBraceToken) ||
currentToken.Kind() == SyntaxKind.CloseBraceToken)
private bool HasAnnotation(SyntaxNode node, SyntaxAnnotation annotation)
{
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);
SyntaxNode nextMember = FormattingRangeHelper.GetEnclosingMember(currentToken);
private static SyntaxTrivia[] CreateNullableTrivia(bool enable)
{
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
// the right number of lines.
if (previousToken.Kind() == SyntaxKind.SemicolonToken && previousToken.Parent.Kind() == SyntaxKind.UsingDirective)
private TSyntax AddNullableRegions<TSyntax>(TSyntax node, CancellationToken cancellationToken)
where TSyntax : SyntaxNode
{
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
if (previousMember.Kind() == nextMember.Kind())
private TypeDeclarationSyntax AddNullableRegionsAroundTypeMembers(
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
// trivia and adding one to it.
var triviaList = token1.TrailingTrivia.Concat(token2.LeadingTrivia);
return FormattingOperations.CreateAdjustNewLinesOperation(GetNumberOfLines(triviaList) + 1, AdjustNewLinesOption.ForceLines);
// we hit a member. see what sort of types it contained.
var (oblivious, annotatedOrNotAnnotated) = GetNullableAnnotations(member);
// 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)
=> SyntaxFacts.IsNewLine(c);
return result;
}
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 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.DocumentationComments;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.LanguageServices;
......@@ -41,7 +39,7 @@ public async Task<Document> AddSourceToAsync(Document document, Compilation symb
CreateCodeGenerationOptions(newSemanticModel.SyntaxTree.GetLocation(new TextSpan())),
cancellationToken).ConfigureAwait(false);
document = await RemoveSimplifierAnnotationsFromImportsAsync(document, cancellationToken).ConfigureAwait(false);
document = await AddNullableRegionsAsync(document, cancellationToken).ConfigureAwait(false);
var docCommentFormattingService = document.GetLanguageService<IDocumentationCommentFormattingService>();
var docWithDocComments = await ConvertDocCommentsToRegularCommentsAsync(document, docCommentFormattingService, cancellationToken).ConfigureAwait(false);
......@@ -55,26 +53,7 @@ public async Task<Document> AddSourceToAsync(Document document, Compilation symb
return await Simplifier.ReduceAsync(formattedDoc, reducers, null, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// <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);
}
protected abstract Task<Document> AddNullableRegionsAsync(Document document, CancellationToken cancellationToken);
/// <summary>
/// provide formatting rules to be used when formatting MAS file
......
......@@ -4,6 +4,7 @@
Imports System.Collections.Immutable
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.DocumentationComments
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.Formatting.Rules
......@@ -49,6 +50,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MetadataAsSource
Return document.WithSyntaxRoot(newRoot)
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)
Dim syntaxRoot = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
......
......@@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
......@@ -23,7 +25,10 @@ internal static class AttributeGenerator
{
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
? default
: SyntaxFactory.SingletonList(SyntaxFactory.AttributeList(
......@@ -32,26 +37,34 @@ internal static class AttributeGenerator
}
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
? default
: SyntaxFactory.List<AttributeListSyntax>(attributeDeclarations);
}
}
private static AttributeListSyntax GenerateAttributeDeclaration(
private static AttributeListSyntax? TryGenerateAttributeDeclaration(
AttributeData attribute, SyntaxToken? target, CodeGenerationOptions options)
{
var attributeSyntax = GenerateAttribute(attribute, options);
var attributeSyntax = TryGenerateAttribute(attribute, options);
return attributeSyntax == null
? null
: SyntaxFactory.AttributeList(
target.HasValue ? SyntaxFactory.AttributeTargetSpecifier(target.Value) : null,
target.HasValue
? SyntaxFactory.AttributeTargetSpecifier(target.Value)
: null,
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)
{
var reusableSyntax = GetReuseableSyntaxNodeForAttribute<AttributeSyntax>(attribute, options);
......@@ -61,17 +74,45 @@ private static AttributeSyntax GenerateAttribute(AttributeData attribute, CodeGe
}
}
if (attribute.AttributeClass == null)
return null;
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>();
arguments.AddRange(attribute.ConstructorArguments.Select(c =>
SyntaxFactory.AttributeArgument(ExpressionGenerator.GenerateExpression(c))));
......
......@@ -183,7 +183,6 @@ protected override void GetUnusedNamespaceImports(SemanticModel model, HashSet<S
{
if (diagnostic.Id == s_CS8019_UnusedUsingDirective)
{
if (root.FindNode(diagnostic.Location.SourceSpan) is UsingDirectiveSyntax 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
// We may be talking about different compilations. So do not try to resolve locations.
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>())
{
result.Add(current);
......
......@@ -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
// locations in symbols are save to resolve as we rehydrate the SymbolKey.
var symbol = SymbolKey.ResolveString(
SymbolKeyData, compilation, resolveLocations: true, cancellationToken: cancellationToken).GetAnySymbol();
SymbolKeyData, compilation, cancellationToken: cancellationToken).GetAnySymbol();
if (symbol == null)
{
......
......@@ -275,7 +275,6 @@ private class SymbolKeyReader : Reader<string>
public SymbolEquivalenceComparer Comparer { get; private set; }
private readonly List<IMethodSymbol> _methodSymbolStack = new List<IMethodSymbol>();
private bool _resolveLocations;
public SymbolKeyReader()
{
......@@ -289,7 +288,6 @@ public override void Dispose()
_idToResult.Clear();
Compilation = null;
IgnoreAssemblyKey = false;
_resolveLocations = true;
Comparer = null;
_methodSymbolStack.Clear();
......@@ -299,11 +297,11 @@ public override void Dispose()
public static SymbolKeyReader GetReader(
string data, Compilation compilation,
bool ignoreAssemblyKey, bool resolveLocations,
bool ignoreAssemblyKey,
CancellationToken cancellationToken)
{
var reader = s_readerPool.Allocate();
reader.Initialize(data, compilation, ignoreAssemblyKey, resolveLocations, cancellationToken);
reader.Initialize(data, compilation, ignoreAssemblyKey, cancellationToken);
return reader;
}
......@@ -311,13 +309,11 @@ public override void Dispose()
string data,
Compilation compilation,
bool ignoreAssemblyKey,
bool resolveLocations,
CancellationToken cancellationToken)
{
base.Initialize(data, cancellationToken);
Compilation = compilation;
IgnoreAssemblyKey = ignoreAssemblyKey;
_resolveLocations = resolveLocations;
Comparer = ignoreAssemblyKey
? SymbolEquivalenceComparer.IgnoreAssembliesInstance
......@@ -508,8 +504,6 @@ public Location ReadLocation()
var start = ReadInteger();
var length = ReadInteger();
if (_resolveLocations)
{
// 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.
var syntaxTree = GetSyntaxTree(filePath);
......@@ -518,14 +512,11 @@ public Location ReadLocation()
return Location.Create(syntaxTree, new TextSpan(start, length));
}
}
}
else if (kind == LocationKind.MetadataFile)
{
var assemblyResolution = ReadSymbolKey();
var moduleName = ReadString();
if (_resolveLocations)
{
// 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.
if (assemblyResolution.GetAnySymbol() is IAssemblySymbol assembly)
......@@ -541,7 +532,6 @@ public Location ReadLocation()
}
}
}
}
return Location.None;
}
......
......@@ -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.
/// </para>
/// <para>
/// <see cref="SymbolKey"/>s are not guaranteed to work across different versions of Roslyn.
/// They can be persisted in their <see cref="ToString()"/> form and used across sessions with
/// the same version of Roslyn. However, future versions may change the encoded format and may
/// no longer be able to <see cref="Resolve(Compilation, bool, bool, CancellationToken)"/> previous
/// keys. As such, only persist if using for a cache that can be regenerated if necessary.
/// <see cref="SymbolKey"/>s are not guaranteed to work across different versions of Roslyn. They can be persisted
/// in their <see cref="ToString()"/> form and used across sessions with the same version of Roslyn. However, future
/// versions may change the encoded format and may no longer be able to <see cref="Resolve"/> previous keys. As
/// such, only persist if using for a cache that can be regenerated if necessary.
/// </para>
/// </summary>
internal partial struct SymbolKey
......@@ -128,11 +127,10 @@ internal static IEqualityComparer<SymbolKey> GetComparer(bool ignoreCase = false
internal static SymbolKeyResolution ResolveString(
string symbolKey, Compilation compilation,
bool ignoreAssemblyKey = false, bool resolveLocations = true,
CancellationToken cancellationToken = default)
bool ignoreAssemblyKey = false, CancellationToken cancellationToken = default)
{
using var reader = SymbolKeyReader.GetReader(
symbolKey, compilation, ignoreAssemblyKey, resolveLocations, cancellationToken);
symbolKey, compilation, ignoreAssemblyKey, cancellationToken);
var version = reader.ReadFormatVersion();
if (version != FormatVersion)
{
......@@ -158,15 +156,12 @@ internal static string CreateStringWorker(int version, ISymbol symbol, Cancellat
/// <summary>
/// Tries to resolve this <see cref="SymbolKey"/> in the given
/// <paramref name="compilation"/> to a matching symbol. <paramref name="resolveLocations"/>
/// 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.
/// <paramref name="compilation"/> to a matching symbol.
/// </summary>
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>
......
......@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Immutable;
using System.Threading;
#if DEBUG
using System.Diagnostics;
......@@ -12,11 +11,10 @@
namespace Microsoft.CodeAnalysis
{
/// <summary>
/// The result of <see cref="SymbolKey.Resolve(Compilation, bool, bool, CancellationToken)"/>.
/// If the <see cref="SymbolKey"/> could be uniquely mapped to a single <see cref="ISymbol"/>
/// then that will be returned in <see cref="Symbol"/>. Otherwise, if
/// the key resolves to multiple symbols (which can happen in error scenarios), then
/// <see cref="CandidateSymbols"/> and <see cref="CandidateReason"/> will be returned.
/// The result of <see cref="SymbolKey.Resolve"/>. If the <see cref="SymbolKey"/> could be uniquely mapped to a
/// single <see cref="ISymbol"/> then that will be returned in <see cref="Symbol"/>. Otherwise, if the key resolves
/// to multiple symbols (which can happen in error scenarios), then <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"/>
/// will be empty.
......
......@@ -223,7 +223,7 @@ public class C
public A GetA<A>(A a) { return a; }
public A GetA<A, B>(A a, B b) { return a; }
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>
......@@ -699,7 +699,7 @@ object LocalFunction<T>()
Assert.NotNull(found);
// 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.
Assert.Equal(symbol.OriginalDefinition, found.OriginalDefinition);
......@@ -736,7 +736,7 @@ void Method((C, int) t)
// Validate that if the client does ask to resolve locations that we
// 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.Equal(symbol.Name, found.Name);
......@@ -773,7 +773,7 @@ void Method((C a, int b) t)
// Validate that if the client does ask to resolve locations that we
// 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.Equal(symbol.Name, found.Name);
......
......@@ -304,7 +304,13 @@ public override TypeSyntax VisitPointerType(IPointerTypeSymbol 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 @@
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
#if !CODE_STYLE
using Microsoft.CodeAnalysis.CodeGeneration;
#endif
namespace Microsoft.CodeAnalysis.CSharp.Extensions
{
internal static partial class ITypeSymbolExtensions
......@@ -41,7 +45,8 @@ internal static partial class ITypeSymbolExtensions
private static TypeSyntax GenerateTypeSyntax(
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
// of what the user's preferences might be.
......@@ -56,6 +61,22 @@ internal static partial class ITypeSymbolExtensions
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;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册