未验证 提交 64bbbc41 编写于 作者: J Julien Couvreur 提交者: GitHub

Use record keyword to display records (#46338)

上级 bccf2a17
......@@ -2,6 +2,7 @@
// 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.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
......@@ -658,6 +659,11 @@ private void AddTypeKind(INamedTypeSymbol symbol)
{
switch (symbol.TypeKind)
{
case TypeKind.Class when FindValidCloneMethod(symbol) is object:
AddKeyword(SyntaxKind.RecordKeyword);
AddSpace();
break;
case TypeKind.Module:
case TypeKind.Class:
AddKeyword(SyntaxKind.ClassKeyword);
......@@ -700,6 +706,61 @@ private void AddTypeKind(INamedTypeSymbol symbol)
}
}
/// <summary>
/// Copy of <see cref="SynthesizedRecordClone.FindValidCloneMethod(TypeSymbol, ref HashSet{DiagnosticInfo}?)"/>
/// </summary>
private static IMethodSymbol FindValidCloneMethod(ITypeSymbol containingType)
{
IMethodSymbol candidate = null;
foreach (var member in containingType.GetMembers(WellKnownMemberNames.CloneMethodName))
{
if (member is IMethodSymbol
{
DeclaredAccessibility: Accessibility.Public,
IsStatic: false,
Parameters: { Length: 0 },
Arity: 0
} method)
{
if (candidate is object)
{
// An ambiguity case, can come from metadata, treat as an error for simplicity.
return null;
}
candidate = method;
}
}
if (candidate is null ||
!(containingType.IsSealed || candidate.IsOverride || candidate.IsVirtual || candidate.IsAbstract) ||
!isEqualToOrDerivedFrom(
containingType,
candidate.ReturnType))
{
return null;
}
return candidate;
static bool isEqualToOrDerivedFrom(ITypeSymbol one, ITypeSymbol other)
{
do
{
if (one.Equals(other, SymbolEqualityComparer.IgnoreAll))
{
return true;
}
one = one.BaseType;
}
while (one != null);
return false;
}
}
private void AddTypeParameterVarianceIfRequired(ITypeParameterSymbol symbol)
{
if (format.GenericsOptions.IncludesOption(SymbolDisplayGenericsOptions.IncludeVariance))
......
......@@ -139,6 +139,7 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState,
throw ExceptionUtilities.Unreachable;
}
// Note: this method was replicated in SymbolDisplayVisitor.FindValidCloneMethod
internal static MethodSymbol? FindValidCloneMethod(TypeSymbol containingType, ref HashSet<DiagnosticInfo>? useSiteDiagnostics)
{
MethodSymbol? candidate = null;
......@@ -155,7 +156,7 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState,
{
if (candidate is object)
{
// An ammbiguity case, can come from metadata, treat as an error for simplicity.
// An ambiguity case, can come from metadata, treat as an error for simplicity.
return null;
}
......
......@@ -1730,6 +1730,9 @@ public void Clone_01()
Assert.True(clone.ContainingType.IsSealed);
Assert.True(clone.ContainingType.IsAbstract);
Assert.Equal("record C1", comp.GlobalNamespace.GetTypeMember("C1")
.ToDisplayString(SymbolDisplayFormat.TestFormat.AddKindOptions(SymbolDisplayKindOptions.IncludeTypeKeyword)));
}
[Fact]
......@@ -1756,6 +1759,9 @@ public void Clone_02()
Assert.True(clone.ContainingType.IsSealed);
Assert.True(clone.ContainingType.IsAbstract);
Assert.Equal("record C1", comp.GlobalNamespace.GetTypeMember("C1")
.ToDisplayString(SymbolDisplayFormat.TestFormat.AddKindOptions(SymbolDisplayKindOptions.IncludeTypeKeyword)));
}
[Fact]
......@@ -1810,10 +1816,13 @@ public void Clone_04()
Assert.True(clone.ContainingType.IsSealed);
Assert.True(clone.ContainingType.IsAbstract);
Assert.Equal("record C1", comp.GlobalNamespace.GetTypeMember("C1")
.ToDisplayString(SymbolDisplayFormat.TestFormat.AddKindOptions(SymbolDisplayKindOptions.IncludeTypeKeyword)));
}
[Fact]
public void Clone_05()
public void Clone_05_IntReturnType_UsedAsBaseType()
{
var ilSource = @"
.class public auto ansi beforefieldinit A
......@@ -1904,10 +1913,13 @@ .maxstack 8
// public record B : A {
Diagnostic(ErrorCode.ERR_BadRecordBase, "A").WithLocation(2, 19)
);
Assert.Equal("class A", comp.GlobalNamespace.GetTypeMember("A")
.ToDisplayString(SymbolDisplayFormat.TestFormat.AddKindOptions(SymbolDisplayKindOptions.IncludeTypeKeyword)));
}
[Fact]
public void Clone_06()
public void Clone_06_IntReturnType_UsedInWith()
{
var ilSource = @"
.class public auto ansi beforefieldinit A
......@@ -2004,10 +2016,13 @@ static void Main()
// A x = new A() with { };
Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "new A()").WithArguments("A").WithLocation(6, 15)
);
Assert.Equal("class A", comp.GlobalNamespace.GetTypeMember("A")
.ToDisplayString(SymbolDisplayFormat.TestFormat.AddKindOptions(SymbolDisplayKindOptions.IncludeTypeKeyword)));
}
[Fact]
public void Clone_07()
public void Clone_07_Ambiguous_UsedAsBaseType()
{
var ilSource = @"
.class public auto ansi beforefieldinit A
......@@ -2108,10 +2123,13 @@ .maxstack 8
// public record B : A {
Diagnostic(ErrorCode.ERR_BadRecordBase, "A").WithLocation(2, 19)
);
Assert.Equal("class A", comp.GlobalNamespace.GetTypeMember("A")
.ToDisplayString(SymbolDisplayFormat.TestFormat.AddKindOptions(SymbolDisplayKindOptions.IncludeTypeKeyword)));
}
[Fact]
public void Clone_08()
public void Clone_08_Ambiguous_UsedInWith()
{
var ilSource = @"
.class public auto ansi beforefieldinit A
......@@ -2218,16 +2236,18 @@ static void Main()
// A x = new A() with { };
Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "new A()").WithArguments("A").WithLocation(6, 15)
);
Assert.Equal("class A", comp.GlobalNamespace.GetTypeMember("A")
.ToDisplayString(SymbolDisplayFormat.TestFormat.AddKindOptions(SymbolDisplayKindOptions.IncludeTypeKeyword)));
}
[Fact]
public void Clone_09()
public void Clone_09_AmbiguousReverseOrder_UsedAsBaseType()
{
var ilSource = @"
.class public auto ansi beforefieldinit A
extends System.Object
{
// Methods
// Methods
.method public hidebysig specialname newslot virtual
instance class A '" + WellKnownMemberNames.CloneMethodName + @"' () cil managed
......@@ -2322,10 +2342,13 @@ .maxstack 8
// public record B : A {
Diagnostic(ErrorCode.ERR_BadRecordBase, "A").WithLocation(2, 19)
);
Assert.Equal("class A", comp.GlobalNamespace.GetTypeMember("A")
.ToDisplayString(SymbolDisplayFormat.TestFormat.AddKindOptions(SymbolDisplayKindOptions.IncludeTypeKeyword)));
}
[Fact]
public void Clone_10()
public void Clone_10_AmbiguousReverseOrder_UsedInWith()
{
var ilSource = @"
.class public auto ansi beforefieldinit A
......@@ -2432,6 +2455,9 @@ static void Main()
// A x = new A() with { };
Diagnostic(ErrorCode.ERR_NoSingleCloneMethod, "new A()").WithArguments("A").WithLocation(6, 15)
);
Assert.Equal("class A", comp.GlobalNamespace.GetTypeMember("A")
.ToDisplayString(SymbolDisplayFormat.TestFormat.AddKindOptions(SymbolDisplayKindOptions.IncludeTypeKeyword)));
}
[Fact]
......@@ -2731,7 +2757,7 @@ public static void Main()
}
[Fact]
public void Clone_17()
public void Clone_17_NonOverridable()
{
var ilSource = @"
.class public auto ansi beforefieldinit A
......@@ -2822,10 +2848,13 @@ .maxstack 8
// public record B : A {
Diagnostic(ErrorCode.ERR_BadRecordBase, "A").WithLocation(2, 19)
);
Assert.Equal("class A", comp.GlobalNamespace.GetTypeMember("A")
.ToDisplayString(SymbolDisplayFormat.TestFormat.AddKindOptions(SymbolDisplayKindOptions.IncludeTypeKeyword)));
}
[Fact]
public void Clone_18()
public void Clone_18_NonOverridable()
{
var ilSource = @"
.class public auto ansi beforefieldinit A
......@@ -2916,6 +2945,9 @@ .maxstack 8
// public record B : A {
Diagnostic(ErrorCode.ERR_BadRecordBase, "A").WithLocation(2, 19)
);
Assert.Equal("class A", comp.GlobalNamespace.GetTypeMember("A")
.ToDisplayString(SymbolDisplayFormat.TestFormat.AddKindOptions(SymbolDisplayKindOptions.IncludeTypeKeyword)));
}
[Fact]
......@@ -19120,6 +19152,19 @@ public static void Main()
);
}
[Fact]
public void RecordLoadedInVisualBasicDisplaysAsRecord()
{
var src = @"
public record A;
";
var compRef = CreateCompilation(src).EmitToImageReference();
var vbComp = CreateVisualBasicCompilation("", referencedAssemblies: new[] { compRef });
var symbol = vbComp.GlobalNamespace.GetTypeMember("A");
Assert.Equal("record A",
SymbolDisplay.ToDisplayString(symbol, SymbolDisplayFormat.TestFormat.AddKindOptions(SymbolDisplayKindOptions.IncludeTypeKeyword)));
}
[Fact]
public void AnalyzerActions_01()
{
......
......@@ -7584,5 +7584,26 @@ class B
method.ToDisplayParts(formatWithoutOptions),
"static void F4(nint[] x, A<nuint> y)");
}
[Fact]
public void RecordDeclaration()
{
var text = @"
record Person(string First, string Last);
";
Func<NamespaceSymbol, Symbol> findSymbol = global => global.GetTypeMembers("Person").Single();
var format = new SymbolDisplayFormat(memberOptions: SymbolDisplayMemberOptions.IncludeType, kindOptions: SymbolDisplayKindOptions.IncludeTypeKeyword);
TestSymbolDescription(
text,
findSymbol,
format,
TestOptions.Regular.WithLanguageVersion(LanguageVersion.CSharp9),
"record Person",
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.ClassName);
}
}
}
......@@ -27,6 +27,7 @@ public sealed class SymbolEqualityComparer : IEqualityComparer<ISymbol?>
// Internal only comparisons:
internal readonly static SymbolEqualityComparer ConsiderEverything = new SymbolEqualityComparer(TypeCompareKind.ConsiderEverything);
internal readonly static SymbolEqualityComparer IgnoreAll = new SymbolEqualityComparer(TypeCompareKind.AllIgnoreOptions);
internal TypeCompareKind CompareKind { get; }
......
......@@ -7036,5 +7036,35 @@ void Goo(object o)
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task QuickInfoRecord()
{
await TestWithOptionsAsync(
Options.Regular.WithLanguageVersion(LanguageVersion.CSharp9),
@"record Person(string First, string Last)
{
void M($$Person p)
{
}
}", MainDescription("record Person"));
}
[Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)]
public async Task QuickInfoDerivedRecord()
{
await TestWithOptionsAsync(
Options.Regular.WithLanguageVersion(LanguageVersion.CSharp9),
@"record Person(string First, string Last)
{
}
record Student(string Id)
{
void M($$Student p)
{
}
}
", MainDescription("record Student"));
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册