Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

IDE Support for Required Members #61440

Merged
merged 12 commits into from
May 27, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ private void VisitFieldType(IFieldSymbol symbol)

public override void VisitField(IFieldSymbol symbol)
{
AddAccessibilityIfRequired(symbol);
AddMemberModifiersIfRequired(symbol);
AddAccessibilityIfNeeded(symbol);
AddMemberModifiersIfNeeded(symbol);
AddFieldModifiersIfRequired(symbol);
333fred marked this conversation as resolved.
Show resolved Hide resolved

if (format.MemberOptions.IncludesOption(SymbolDisplayMemberOptions.IncludeType) &&
Expand Down Expand Up @@ -122,8 +122,9 @@ private static bool ShouldMethodDisplayReadOnly(IMethodSymbol method, IPropertyS

public override void VisitProperty(IPropertySymbol symbol)
{
AddAccessibilityIfRequired(symbol);
AddMemberModifiersIfRequired(symbol);
AddAccessibilityIfNeeded(symbol);
AddMemberModifiersIfNeeded(symbol);

333fred marked this conversation as resolved.
Show resolved Hide resolved

if (ShouldPropertyDisplayReadOnly(symbol))
{
Expand Down Expand Up @@ -210,8 +211,8 @@ private void AddPropertyNameAndParameters(IPropertySymbol symbol)

public override void VisitEvent(IEventSymbol symbol)
{
AddAccessibilityIfRequired(symbol);
AddMemberModifiersIfRequired(symbol);
AddAccessibilityIfNeeded(symbol);
AddMemberModifiersIfNeeded(symbol);

var accessor = symbol.AddMethod ?? symbol.RemoveMethod;
if (accessor is object && ShouldMethodDisplayReadOnly(accessor))
Expand Down Expand Up @@ -300,8 +301,8 @@ public override void VisitMethod(IMethodSymbol symbol)

if ((object)symbol.ContainingType != null || (symbol.ContainingSymbol is ITypeSymbol))
{
AddAccessibilityIfRequired(symbol);
AddMemberModifiersIfRequired(symbol);
AddAccessibilityIfNeeded(symbol);
AddMemberModifiersIfNeeded(symbol);

if (ShouldMethodDisplayReadOnly(symbol))
{
Expand Down Expand Up @@ -838,7 +839,7 @@ private void AddFieldModifiersIfRequired(IFieldSymbol symbol)
}
}

private void AddMemberModifiersIfRequired(ISymbol symbol)
private void AddMemberModifiersIfNeeded(ISymbol symbol)
333fred marked this conversation as resolved.
Show resolved Hide resolved
{
INamedTypeSymbol containingType = symbol.ContainingType;

Expand All @@ -850,6 +851,7 @@ private void AddMemberModifiersIfRequired(ISymbol symbol)
(containingType.TypeKind != TypeKind.Interface && !IsEnumMember(symbol) && !IsLocalFunction(symbol))))
{
var isConst = symbol is IFieldSymbol && ((IFieldSymbol)symbol).IsConst;
var isRequired = symbol is IFieldSymbol { IsRequired: true } or IPropertySymbol { IsRequired: true };
if (symbol.IsStatic && !isConst)
{
AddKeyword(SyntaxKind.StaticKeyword);
Expand Down Expand Up @@ -885,6 +887,12 @@ private void AddMemberModifiersIfRequired(ISymbol symbol)
AddKeyword(SyntaxKind.VirtualKeyword);
AddSpace();
}

if (isRequired)
{
AddKeyword(SyntaxKind.RequiredKeyword);
AddSpace();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ private void AddKeyword(SyntaxKind keywordKind)
builder.Add(CreatePart(SymbolDisplayPartKind.Keyword, null, SyntaxFacts.GetText(keywordKind)));
}

private void AddAccessibilityIfRequired(ISymbol symbol)
private void AddAccessibilityIfNeeded(ISymbol symbol)
{
INamedTypeSymbol containingType = symbol.ContainingType;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ bool IFieldSymbol.IsExplicitlyNamedTupleElement

bool IFieldSymbol.IsVolatile => _underlying.IsVolatile;

bool IFieldSymbol.IsRequired => _underlying.IsRequired;

bool IFieldSymbol.IsFixedSizeBuffer => _underlying.IsFixedSizeBuffer;

int IFieldSymbol.FixedSize => _underlying.FixedSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ bool IPropertySymbol.IsWithEvents
get { return false; }
}

bool IPropertySymbol.IsRequired => _underlying.IsRequired;

ImmutableArray<CustomModifier> IPropertySymbol.TypeCustomModifiers
{
get { return _underlying.TypeWithAnnotations.CustomModifiers; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2744,6 +2744,7 @@ public void TestMembersInScriptGlobals()
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeTypeConstraints,
memberOptions:
SymbolDisplayMemberOptions.IncludeModifiers |
SymbolDisplayMemberOptions.IncludeRef |
SymbolDisplayMemberOptions.IncludeType |
SymbolDisplayMemberOptions.IncludeParameters |
Expand Down Expand Up @@ -7991,5 +7992,39 @@ void M(string s!!)
SymbolDisplayPartKind.ParameterName,
SymbolDisplayPartKind.Punctuation);
}

[Fact]
public void TestRequiredProperty()
{
var source = @"
class C
{
required int Prop { get; set; }
}
";

var comp = CreateCompilation(source);
var propertySymbol = comp.GetMember<PropertySymbol>("C.Prop").GetPublicSymbol();

Verify(propertySymbol.ToDisplayParts(s_memberSignatureDisplayFormat), "required int C.Prop { get; set; }",
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.ClassName,
SymbolDisplayPartKind.Punctuation,
SymbolDisplayPartKind.PropertyName,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.Punctuation,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Punctuation,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Punctuation,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.Punctuation);

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1306,5 +1306,13 @@ public void TestNormalizeXmlArgumentsInDocComment7()
const string Text = @"/// Prefix <b b=""y""a=""x"" >S_OK</b> suffix";
TestNormalizeDeclaration(Text, Expected);
}

[Fact]
public void TestRequiredKeywordNormalization()
{
const string Expected = @"public required partial int Field;";
const string Text = @"public required partial int Field;";
TestNormalizeDeclaration(Text, Expected);
}
}
}
3 changes: 2 additions & 1 deletion src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
*REMOVED*override abstract Microsoft.CodeAnalysis.Diagnostic.Equals(object? obj) -> bool
abstract Microsoft.CodeAnalysis.SymbolVisitor<TArgument, TResult>.DefaultResult.get -> TResult
Microsoft.CodeAnalysis.Compilation.GetTypesByMetadataName(string! fullyQualifiedMetadataName) -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.INamedTypeSymbol!>
Microsoft.CodeAnalysis.IFieldSymbol.IsRequired.get -> bool
Microsoft.CodeAnalysis.IImportScope
Microsoft.CodeAnalysis.IImportScope.Aliases.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.IAliasSymbol!>
Microsoft.CodeAnalysis.IImportScope.ExternAliases.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.IAliasSymbol!>
Expand Down Expand Up @@ -48,6 +49,7 @@ Microsoft.CodeAnalysis.IOperation.OperationList.Reversed.Reversed() -> void
Microsoft.CodeAnalysis.IOperation.OperationList.Reversed.Count.get -> int
Microsoft.CodeAnalysis.IOperation.OperationList.Reversed.GetEnumerator() -> Microsoft.CodeAnalysis.IOperation.OperationList.Reversed.Enumerator
Microsoft.CodeAnalysis.IOperation.OperationList.Reversed.ToImmutableArray() -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.IOperation!>
Microsoft.CodeAnalysis.IPropertySymbol.IsRequired.get -> bool
Microsoft.CodeAnalysis.SemanticModel.GetImportScopes(int position, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.IImportScope!>
Microsoft.CodeAnalysis.ISymbol.Accept<TArgument, TResult>(Microsoft.CodeAnalysis.SymbolVisitor<TArgument, TResult>! visitor, TArgument argument) -> TResult
Microsoft.CodeAnalysis.SymbolVisitor<TArgument, TResult>
Expand Down Expand Up @@ -90,5 +92,4 @@ virtual Microsoft.CodeAnalysis.SymbolVisitor<TArgument, TResult>.VisitPointerTyp
virtual Microsoft.CodeAnalysis.SymbolVisitor<TArgument, TResult>.VisitProperty(Microsoft.CodeAnalysis.IPropertySymbol! symbol, TArgument argument) -> TResult
virtual Microsoft.CodeAnalysis.SymbolVisitor<TArgument, TResult>.VisitRangeVariable(Microsoft.CodeAnalysis.IRangeVariableSymbol! symbol, TArgument argument) -> TResult
virtual Microsoft.CodeAnalysis.SymbolVisitor<TArgument, TResult>.VisitTypeParameter(Microsoft.CodeAnalysis.ITypeParameterSymbol! symbol, TArgument argument) -> TResult

Microsoft.CodeAnalysis.Operations.BinaryOperatorKind.UnsignedRightShift = 25 -> Microsoft.CodeAnalysis.Operations.BinaryOperatorKind
5 changes: 5 additions & 0 deletions src/Compilers/Core/Portable/Symbols/IFieldSymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ public interface IFieldSymbol : ISymbol
/// </summary>
bool IsVolatile { get; }

/// <summary>
/// True if this property is required to be set in an object initializer during construction.
333fred marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
bool IsRequired { get; }

/// <summary>
/// Returns true if this field was declared as "fixed".
/// Note that for a fixed-size buffer declaration, this.Type will be a pointer type, of which
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/Core/Portable/Symbols/IPropertySymbol.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public interface IPropertySymbol : ISymbol
/// </summary>
bool IsWriteOnly { get; }

/// <summary>
/// True if this property is required to be set in an object initializer during construction.
/// </summary>
bool IsRequired { get; }
333fred marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Returns true if this property is an auto-created WithEvents property that takes place of
/// a field member when the field is marked as WithEvents.
Expand Down
6 changes: 6 additions & 0 deletions src/Compilers/VisualBasic/Portable/Symbols/FieldSymbol.vb
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols
End Get
End Property

Private ReadOnly Property IFieldSymbol_IsRequired As Boolean Implements IFieldSymbol.IsRequired
Get
Return False
End Get
End Property

Private ReadOnly Property IFieldSymbol_IsFixedSizeBuffer As Boolean Implements IFieldSymbol.IsFixedSizeBuffer
Get
Return False
Expand Down
6 changes: 6 additions & 0 deletions src/Compilers/VisualBasic/Portable/Symbols/PropertySymbol.vb
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols
End Get
End Property

Private ReadOnly Property IPropertySymbol_IsRequired As Boolean Implements IPropertySymbol.IsRequired
Get
Return False
End Get
End Property

Private ReadOnly Property IPropertySymbol_ReturnsByRef As Boolean Implements IPropertySymbol.ReturnsByRef
Get
Return Me.ReturnsByRef
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -616,5 +616,67 @@ public async Task TestInCastExpressionThatMightBeParenthesizedExpression2(bool h
await VerifyItemExistsAsync(markup, "nameof");
}
}

[Theory]
[InlineData("class")]
[InlineData("struct")]
public async Task SuggestRequiredInClassOrStruct(string type)
{
var markup = $$"""
{{type}} C
{
$$
""";

await VerifyItemExistsAsync(markup, "required");
}

[Fact]
public async Task DoNotSuggestRequiredInInterface()
{
var markup = $$"""
interface I
{
public $$
""";

await VerifyItemIsAbsentAsync(markup, "required");
}

[Fact]
public async Task DoNotSuggestRequiredOnStaticMembers()
{
var markup = $$"""
class C
{
static $$
""";

await VerifyItemIsAbsentAsync(markup, "required");
}

[Fact]
public async Task DoNotSuggestStaticOnRequiredMembers()
{
var markup = $$"""
class C
{
required $$
""";

await VerifyItemIsAbsentAsync(markup, "static");
}

[Fact]
public async Task DoNotSuggestRequiredOnRequiredMembers()
{
var markup = $$"""
class C
{
required $$
""";

await VerifyItemIsAbsentAsync(markup, "required");
}
}
}
Loading