未验证 提交 f99a821c 编写于 作者: R Rikki Gibson 提交者: GitHub

Readonly members symbol display (#34876)

* Symbol display for readonly members

* Add coverage

* Test malformed properties and events

* Show readonly keyword on non-field members in QuickInfo

* Use IsDeclaredReadOnly for symbol display (WIP)

* Update based on design and fix some test failures

* Add readonly keywords to various IL tests

* Fix more tests, respond to feedback

* Fix compile error

* Don't display readonly keyword for implicit readonly getters from source

* Don't show readonly keyword for members contained within a readonly struct

* Move from extension methods to private statics
上级 b1dbcb0a
......@@ -77,11 +77,60 @@ public override void VisitField(IFieldSymbol symbol)
}
}
private static bool ShouldPropertyDisplayReadOnly(IPropertySymbol property)
{
if (property.ContainingType?.IsReadOnly == true)
{
return false;
}
// If at least one accessor is present and all present accessors are readonly, the property should be marked readonly.
var getMethod = property.GetMethod;
if (getMethod is object && !ShouldMethodDisplayReadOnly(getMethod, property))
{
return false;
}
var setMethod = property.SetMethod;
if (setMethod is object && !ShouldMethodDisplayReadOnly(setMethod, property))
{
return false;
}
return getMethod is object || setMethod is object;
}
private static bool ShouldMethodDisplayReadOnly(IMethodSymbol method, IPropertySymbol propertyOpt = null)
{
if (method.ContainingType?.IsReadOnly == true)
{
return false;
}
if (method is SourcePropertyAccessorSymbol sourceAccessor && propertyOpt is SourcePropertySymbol sourceProperty)
{
// only display if the accessor is explicitly readonly
return sourceAccessor.LocalDeclaredReadOnly || sourceProperty.HasReadOnlyModifier;
}
else if (method is MethodSymbol m)
{
return m.IsDeclaredReadOnly;
}
return false;
}
public override void VisitProperty(IPropertySymbol symbol)
{
AddAccessibilityIfRequired(symbol);
AddMemberModifiersIfRequired(symbol);
if (ShouldPropertyDisplayReadOnly(symbol))
{
AddReadOnlyIfRequired();
}
if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeType))
{
if (symbol.ReturnsByRef)
......@@ -168,6 +217,12 @@ public override void VisitEvent(IEventSymbol symbol)
AddAccessibilityIfRequired(symbol);
AddMemberModifiersIfRequired(symbol);
var accessor = symbol.AddMethod ?? symbol.RemoveMethod;
if (accessor is object && ShouldMethodDisplayReadOnly(accessor))
{
AddReadOnlyIfRequired();
}
if (format.KindOptions.IncludesOption(SymbolDisplayKindOptions.IncludeMemberKeyword))
{
AddKeyword(SyntaxKind.EventKeyword);
......@@ -257,6 +312,11 @@ public override void VisitMethod(IMethodSymbol symbol)
AddAccessibilityIfRequired(symbol);
AddMemberModifiersIfRequired(symbol);
if (ShouldMethodDisplayReadOnly(symbol))
{
AddReadOnlyIfRequired();
}
if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeType))
{
switch (symbol.MethodKind)
......@@ -753,7 +813,7 @@ private void AddParametersIfRequired(bool hasThisParameter, bool isVarargs, Immu
}
}
private void AddAccessor(ISymbol property, IMethodSymbol method, SyntaxKind keyword)
private void AddAccessor(IPropertySymbol property, IMethodSymbol method, SyntaxKind keyword)
{
if (method != null)
{
......@@ -763,6 +823,11 @@ private void AddAccessor(ISymbol property, IMethodSymbol method, SyntaxKind keyw
AddAccessibility(method);
}
if (!ShouldPropertyDisplayReadOnly(property) && ShouldMethodDisplayReadOnly(method, property))
{
AddReadOnlyIfRequired();
}
AddKeyword(keyword);
AddPunctuation(SyntaxKind.SemicolonToken);
}
......@@ -829,6 +894,17 @@ private void AddRefReadonlyIfRequired()
}
}
private void AddReadOnlyIfRequired()
{
// 'readonly' in this context is effectively a 'ref' modifier
// because it affects whether the 'this' parameter is 'ref' or 'in'.
if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeRef))
{
AddKeyword(SyntaxKind.ReadOnlyKeyword);
AddSpace();
}
}
private void AddParameterRefKindIfRequired(RefKind refKind)
{
if (format.ParameterOptions.IncludesOption(SymbolDisplayParameterOptions.IncludeParamsRefOut))
......
......@@ -117,7 +117,8 @@ public sealed override ImmutableArray<MethodSymbol> ExplicitInterfaceImplementat
get { return ImmutableArray<MethodSymbol>.Empty; }
}
internal sealed override bool IsDeclaredReadOnly => true;
// methods on classes are never 'readonly'
internal sealed override bool IsDeclaredReadOnly => false;
public sealed override ImmutableArray<CustomModifier> RefCustomModifiers
{
......
......@@ -261,7 +261,8 @@ public override ImmutableArray<MethodSymbol> ExplicitInterfaceImplementations
}
}
internal override bool IsDeclaredReadOnly => true;
// operators are never 'readonly' because there is no 'this' parameter
internal override bool IsDeclaredReadOnly => false;
public override ImmutableArray<CustomModifier> RefCustomModifiers
{
......
......@@ -367,7 +367,7 @@ .maxstack 2
IL_0003: stfld ""int S.<X>k__BackingField""
IL_0008: ldarg.0
IL_0009: ldarg.0
IL_000a: call ""int S.X.get""
IL_000a: call ""readonly int S.X.get""
IL_000f: stfld ""int S.<Y>k__BackingField""
IL_0014: ret
}
......@@ -408,11 +408,11 @@ .maxstack 2
IL_0003: stfld ""int C.<I>k__BackingField""
IL_0008: ldarg.0
IL_0009: ldarg.0
IL_000a: call ""int C.I.get""
IL_000a: call ""readonly int C.I.get""
IL_000f: call ""void C.J.set""
IL_0014: ldarg.0
IL_0015: ldarg.0
IL_0016: call ""int C.J.get""
IL_0016: call ""readonly int C.J.get""
IL_001b: stfld ""int C.<I>k__BackingField""
IL_0020: ldarg.0
IL_0021: ldarg.1
......
......@@ -1856,7 +1856,7 @@ .maxstack 1
.locals init (S V_0, //copy
S V_1)
IL_0000: ldarg.0
IL_0001: call ""void S.M1()""
IL_0001: call ""readonly void S.M1()""
IL_0006: ldarg.0
IL_0007: ldfld ""int S.i""
IL_000c: call ""void System.Console.Write(int)""
......@@ -1932,7 +1932,7 @@ .maxstack 1
.locals init (S V_0, //copy
S V_1)
IL_0000: ldarg.0
IL_0001: call ""int S.P1.get""
IL_0001: call ""readonly int S.P1.get""
IL_0006: pop
IL_0007: ldarg.0
IL_0008: ldfld ""int S.i""
......@@ -1976,7 +1976,7 @@ public struct S
// Code size 9 (0x9)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call ""int S.M2()""
IL_0001: call ""readonly int S.M2()""
IL_0006: ldc.i4.1
IL_0007: add
IL_0008: ret
......@@ -2002,7 +2002,7 @@ public struct S
// Code size 9 (0x9)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call ""int S.P2.get""
IL_0001: call ""readonly int S.P2.get""
IL_0006: ldc.i4.1
IL_0007: add
IL_0008: ret
......@@ -2027,7 +2027,7 @@ public struct S
// Code size 9 (0x9)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call ""int S.P2.get""
IL_0001: call ""readonly int S.P2.get""
IL_0006: ldc.i4.1
IL_0007: add
IL_0008: ret
......@@ -2053,7 +2053,7 @@ public struct S
// Code size 9 (0x9)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call ""int S.M2()""
IL_0001: call ""readonly int S.M2()""
IL_0006: ldc.i4.1
IL_0007: add
IL_0008: ret
......@@ -2089,7 +2089,7 @@ public struct S2
.maxstack 1
IL_0000: ldarg.0
IL_0001: ldflda ""S1 S2.s1""
IL_0006: call ""void S1.M1()""
IL_0006: call ""readonly void S1.M1()""
IL_000b: ret
}");
......@@ -2465,17 +2465,17 @@ public struct S
// Code size 99 (0x63)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call ""System.Type S.GetType()""
IL_0001: call ""readonly System.Type S.GetType()""
IL_0006: pop
IL_0007: ldarg.0
IL_0008: call ""string S.ToString()""
IL_0008: call ""readonly string S.ToString()""
IL_000d: pop
IL_000e: ldarg.0
IL_000f: call ""int S.GetHashCode()""
IL_000f: call ""readonly int S.GetHashCode()""
IL_0014: pop
IL_0015: ldarg.0
IL_0016: ldnull
IL_0017: call ""bool S.Equals(object)""
IL_0017: call ""readonly bool S.Equals(object)""
IL_001c: pop
IL_001d: ldarg.0
IL_001e: ldobj ""S""
......
......@@ -3704,7 +3704,7 @@ .locals init (Program.C1 V_0)
IL_000e: call ""Program.C1 Program.C1?.GetValueOrDefault()""
IL_0013: stloc.0
IL_0014: ldloca.s V_0
IL_0016: call ""int Program.C1.x.get""
IL_0016: call ""readonly int Program.C1.x.get""
IL_001b: ret
}
").VerifyIL("Program.Test2(Program.C1?)", @"
......@@ -3725,7 +3725,7 @@ .maxstack 1
IL_0016: call ""Program.C1 Program.C1?.GetValueOrDefault()""
IL_001b: stloc.2
IL_001c: ldloca.s V_2
IL_001e: call ""int? Program.C1.y.get""
IL_001e: call ""readonly int? Program.C1.y.get""
IL_0023: stloc.0
IL_0024: ldloca.s V_0
IL_0026: call ""bool int?.HasValue.get""
......@@ -3793,7 +3793,7 @@ .locals init (Program.C1 V_0)
IL_000d: call ""Program.C1 Program.C1?.GetValueOrDefault()""
IL_0012: stloc.0
IL_0013: ldloca.s V_0
IL_0015: call ""int Program.C1.x.get""
IL_0015: call ""readonly int Program.C1.x.get""
IL_001a: ret
}
......@@ -3816,7 +3816,7 @@ .maxstack 2
IL_0015: call ""Program.C1 Program.C1?.GetValueOrDefault()""
IL_001a: stloc.2
IL_001b: ldloca.s V_2
IL_001d: call ""int? Program.C1.y.get""
IL_001d: call ""readonly int? Program.C1.y.get""
IL_0022: stloc.0
IL_0023: ldloca.s V_0
IL_0025: call ""bool int?.HasValue.get""
......
......@@ -14456,8 +14456,8 @@ public override string ToString()
"(System.Int32, System.Int32)..ctor(System.Int32 item1, System.Int32 item2)",
"System.String (System.Int32, System.Int32).ToString()",
"System.Int32 (System.Int32, System.Int32).<P1>k__BackingField",
"System.Int32 (System.Int32, System.Int32).P1 { get; set; }",
"System.Int32 (System.Int32, System.Int32).P1.get",
"System.Int32 (System.Int32, System.Int32).P1 { readonly get; set; }",
"readonly System.Int32 (System.Int32, System.Int32).P1.get",
"void (System.Int32, System.Int32).P1.set",
"System.Int32 (System.Int32, System.Int32).this[System.Int32 a, System.Int32 b] { get; }",
"System.Int32 (System.Int32, System.Int32).this[System.Int32 a, System.Int32 b].get",
......@@ -14470,7 +14470,7 @@ public override string ToString()
m1Tuple.MemberNames.ToArray());
Assert.Equal(m1Tuple.TupleUnderlyingType.GetEarlyAttributeDecodingMembers().Select(m => m.Name).ToArray(),
m1Tuple.GetEarlyAttributeDecodingMembers().Select(m => m.Name).ToArray());
Assert.Equal("System.Int32 (System.Int32, System.Int32).P1 { get; set; }", m1Tuple.GetEarlyAttributeDecodingMembers("P1").Single().ToTestDisplayString());
Assert.Equal("System.Int32 (System.Int32, System.Int32).P1 { readonly get; set; }", m1Tuple.GetEarlyAttributeDecodingMembers("P1").Single().ToTestDisplayString());
var m2Tuple = (NamedTypeSymbol)c.GetMember<MethodSymbol>("M2").ReturnType;
var m3Tuple = (NamedTypeSymbol)c.GetMember<MethodSymbol>("M3").ReturnType;
......@@ -14486,7 +14486,7 @@ public override string ToString()
Assert.Equal(SymbolKind.Property, m1P1.Kind);
Assert.Same(m1P1, m1P1.OriginalDefinition);
Assert.True(m1P1.Equals(m1P1));
Assert.Equal("System.Int32 System.ValueTuple<System.Int32, System.Int32>.P1 { get; set; }", m1P1.TupleUnderlyingProperty.ToTestDisplayString());
Assert.Equal("System.Int32 System.ValueTuple<System.Int32, System.Int32>.P1 { readonly get; set; }", m1P1.TupleUnderlyingProperty.ToTestDisplayString());
Assert.Same(m1Tuple, m1P1.ContainingSymbol);
Assert.Same(m1Tuple.TupleUnderlyingType, m1P1.TupleUnderlyingProperty.ContainingSymbol);
Assert.True(m1P1.TypeWithAnnotations.CustomModifiers.IsEmpty);
......@@ -96,7 +96,7 @@ .maxstack 2
IL_001b: ldfld ""int MemberInitializerTest.x""
IL_0020: call ""void System.Console.WriteLine(int)""
IL_0025: ldloca.s V_0
IL_0027: call ""int MemberInitializerTest.y.get""
IL_0027: call ""readonly int MemberInitializerTest.y.get""
IL_002c: call ""void System.Console.WriteLine(int)""
IL_0031: ret
}");
......
......@@ -7589,7 +7589,7 @@ .maxstack 3
IL_0002: initobj ""S""
IL_0008: ldloca.s V_0
IL_000a: dup
IL_000b: call ""S* S.P.get""
IL_000b: call ""readonly S* S.P.get""
IL_0010: stloc.1
IL_0011: ldloc.1
IL_0012: sizeof ""S""
......@@ -7608,7 +7608,7 @@ .maxstack 3
IL_0036: ldloc.1
IL_0037: call ""void S.this[int].set""
IL_003c: ldloca.s V_0
IL_003e: call ""S* S.P.get""
IL_003e: call ""readonly S* S.P.get""
IL_0043: conv.i4
IL_0044: call ""void System.Console.Write(int)""
IL_0049: ret
......@@ -7764,7 +7764,7 @@ .maxstack 5
IL_0002: initobj ""S""
IL_0008: ldloca.s V_0
IL_000a: dup
IL_000b: call ""S* S.P.get""
IL_000b: call ""readonly S* S.P.get""
IL_0010: ldc.i4.3
IL_0011: conv.i
IL_0012: sizeof ""S""
......@@ -7787,7 +7787,7 @@ .maxstack 5
IL_003a: sub
IL_003b: call ""void S.this[int].set""
IL_0040: ldloca.s V_0
IL_0042: call ""S* S.P.get""
IL_0042: call ""readonly S* S.P.get""
IL_0047: conv.i4
IL_0048: call ""void System.Console.Write(int)""
IL_004d: ret
......
......@@ -68,6 +68,8 @@ public enum SymbolDisplayMemberOptions
/// <summary>
/// Includes the <c>ref</c>, <c>ref readonly</c>, <c>ByRef</c> keywords for ref-returning methods and properties/indexers.
/// Also includes the <c>readonly</c> keyword on methods, properties/indexers, and events due to the keyword
/// changing the <c>this</c> parameter's ref kind from <c>ref</c> to <c>ref readonly</c>.
/// </summary>
IncludeRef = 1 << 7,
}
......
......@@ -537,6 +537,145 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense
AssertEqualAdornments(expected, container)
End Sub
<WpfFact, Trait(Traits.Feature, Traits.Features.QuickInfo)>
<WorkItem(33546, "https://github.com/dotnet/roslyn/issues/33546")>
Public Async Sub QuickInfoForReadOnlyMethodReference()
Dim workspace =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
struct MyStruct {
readonly void MyMethod() {
MyM$$ethod();
}
}
</Document>
</Project>
</Workspace>
Dim codeAnalysisQuickInfoItem = Await GetQuickInfoItemAsync(workspace, LanguageNames.CSharp)
Dim trackingSpan = New Mock(Of ITrackingSpan) With {
.DefaultValue = DefaultValue.Mock
}
Dim intellisenseQuickInfo = Await IntellisenseQuickInfoBuilder.BuildItemAsync(trackingSpan.Object, codeAnalysisQuickInfoItem, Nothing, Nothing, CancellationToken.None)
Assert.NotNull(intellisenseQuickInfo)
Dim container = Assert.IsType(Of ContainerElement)(intellisenseQuickInfo.Item)
Dim expected = New ContainerElement(
ContainerElementStyle.Stacked Or ContainerElementStyle.VerticalPadding,
New ContainerElement(
ContainerElementStyle.Wrapped,
New ImageElement(New ImageId(KnownImageIds.ImageCatalogGuid, KnownImageIds.MethodPrivate)),
New ClassifiedTextElement(
New ClassifiedTextRun(ClassificationTypeNames.Keyword, "readonly"),
New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "),
New ClassifiedTextRun(ClassificationTypeNames.Keyword, "void"),
New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "),
New ClassifiedTextRun(ClassificationTypeNames.StructName, "MyStruct"),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "."),
New ClassifiedTextRun(ClassificationTypeNames.MethodName, "MyMethod"),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "("),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ")"))))
AssertEqualAdornments(expected, container)
End Sub
<WpfFact, Trait(Traits.Feature, Traits.Features.QuickInfo)>
<WorkItem(33546, "https://github.com/dotnet/roslyn/issues/33546")>
Public Async Sub QuickInfoForReadOnlyPropertyReference()
Dim workspace =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
struct MyStruct {
readonly int MyProperty => My$$Property;
}
</Document>
</Project>
</Workspace>
Dim codeAnalysisQuickInfoItem = Await GetQuickInfoItemAsync(workspace, LanguageNames.CSharp)
Dim trackingSpan = New Mock(Of ITrackingSpan) With {
.DefaultValue = DefaultValue.Mock
}
Dim intellisenseQuickInfo = Await IntellisenseQuickInfoBuilder.BuildItemAsync(trackingSpan.Object, codeAnalysisQuickInfoItem, Nothing, Nothing, CancellationToken.None)
Assert.NotNull(intellisenseQuickInfo)
Dim container = Assert.IsType(Of ContainerElement)(intellisenseQuickInfo.Item)
Dim expected = New ContainerElement(
ContainerElementStyle.Stacked Or ContainerElementStyle.VerticalPadding,
New ContainerElement(
ContainerElementStyle.Wrapped,
New ImageElement(New ImageId(KnownImageIds.ImageCatalogGuid, KnownImageIds.PropertyPrivate)),
New ClassifiedTextElement(
New ClassifiedTextRun(ClassificationTypeNames.Keyword, "readonly"),
New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "),
New ClassifiedTextRun(ClassificationTypeNames.Keyword, "int"),
New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "),
New ClassifiedTextRun(ClassificationTypeNames.StructName, "MyStruct"),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "."),
New ClassifiedTextRun(ClassificationTypeNames.PropertyName, "MyProperty"),
New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "{"),
New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "),
New ClassifiedTextRun(ClassificationTypeNames.Keyword, "get"),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ";"),
New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "}"))))
AssertEqualAdornments(expected, container)
End Sub
<WpfFact, Trait(Traits.Feature, Traits.Features.QuickInfo)>
<WorkItem(33546, "https://github.com/dotnet/roslyn/issues/33546")>
Public Async Sub QuickInfoForReadOnlyEventReference()
Dim workspace =
<Workspace>
<Project Language="C#" CommonReferences="true">
<Document>
struct MyStruct {
readonly event System.Action MyEvent { add { My$$Event += value; } remove { } }
}
</Document>
</Project>
</Workspace>
Dim codeAnalysisQuickInfoItem = Await GetQuickInfoItemAsync(workspace, LanguageNames.CSharp)
Dim trackingSpan = New Mock(Of ITrackingSpan) With {
.DefaultValue = DefaultValue.Mock
}
Dim intellisenseQuickInfo = Await IntellisenseQuickInfoBuilder.BuildItemAsync(trackingSpan.Object, codeAnalysisQuickInfoItem, Nothing, Nothing, CancellationToken.None)
Assert.NotNull(intellisenseQuickInfo)
Dim container = Assert.IsType(Of ContainerElement)(intellisenseQuickInfo.Item)
Dim expected = New ContainerElement(
ContainerElementStyle.Stacked Or ContainerElementStyle.VerticalPadding,
New ContainerElement(
ContainerElementStyle.Wrapped,
New ImageElement(New ImageId(KnownImageIds.ImageCatalogGuid, KnownImageIds.EventPrivate)),
New ClassifiedTextElement(
New ClassifiedTextRun(ClassificationTypeNames.Keyword, "readonly"),
New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "),
New ClassifiedTextRun(ClassificationTypeNames.NamespaceName, "System"),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "."),
New ClassifiedTextRun(ClassificationTypeNames.DelegateName, "Action"),
New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "),
New ClassifiedTextRun(ClassificationTypeNames.StructName, "MyStruct"),
New ClassifiedTextRun(ClassificationTypeNames.Punctuation, "."),
New ClassifiedTextRun(ClassificationTypeNames.EventName, "MyEvent"))))
AssertEqualAdornments(expected, container)
End Sub
<WpfFact, Trait(Traits.Feature, Traits.Features.QuickInfo)>
<WorkItem(33546, "https://github.com/dotnet/roslyn/issues/33546")>
Public Async Sub QuickInfoForTypeParameterReference()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册