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

Update 'use auto property' to support using the field keyword #74629

Merged
Merged
Show file tree
Hide file tree
Changes from 119 commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
9afc7e1
Field and value as contextual keywords
cston Jun 5, 2024
62bdd75
Fix bootstrap build
cston Jun 11, 2024
538db7e
Fix tests
cston Jun 12, 2024
55da141
Additional test
cston Jun 13, 2024
96b23b5
Merge remote-tracking branch 'upstream/main' into field-value-keywords
cston Jun 13, 2024
dee9526
Fix tests
cston Jun 13, 2024
4d227d3
Do not treat field as keyword in property initializer
cston Jun 24, 2024
0ede824
Address feedback
cston Jun 24, 2024
5f40ed7
Merge remote-tracking branch 'upstream/main' into field-value-keywords
cston Jun 25, 2024
afcbacb
Address feedback
cston Jun 25, 2024
4919f39
Remove event support
cston Jun 25, 2024
b300536
Merge remote-tracking branch 'upstream/main' into field-value-keywords
cston Jun 27, 2024
7924bbe
Remove error for contextual keyword as identifier
cston Jun 27, 2024
05c024d
Revert unnecessary changes
cston Jun 27, 2024
0ea409b
Restore test
cston Jun 27, 2024
b957b4c
Fix tests
cston Jul 1, 2024
6f61596
More tests
cston Jul 1, 2024
0461361
Update public API
cston Jul 2, 2024
94d3381
Merge remote-tracking branch 'upstream/main' into field-value-keywords
cston Jul 2, 2024
6626a89
Report diagnostic when value keyword does not bind to parameter
cston Jul 2, 2024
412382a
Fix bootstrap build
cston Jul 2, 2024
2de28ec
Fix test
cston Jul 2, 2024
ef1b6fa
Do not parse 'value' as contextual keyword
cston Jul 16, 2024
fd03d65
Misc.
cston Jul 17, 2024
4a01d5a
initonly
cston Jul 18, 2024
f0d6151
Attributes
cston Jul 19, 2024
117fe3b
Add incremental parsing test
cston Jul 19, 2024
6cfd56a
Fix test
cston Jul 19, 2024
50c45b2
Merge remote-tracking branch 'upstream/main' into field-value-keywords
cston Jul 19, 2024
33543b1
Update tests
cston Jul 19, 2024
1c55beb
More tests
cston Jul 19, 2024
f165c4f
More tests
cston Jul 29, 2024
2b068b2
Simplify FieldExpression case
cston Jul 30, 2024
7ed1c3d
Address feedback
cston Jul 30, 2024
3b372c4
In progress
CyrusNajmabadi Jul 31, 2024
774a128
IN progress
CyrusNajmabadi Jul 31, 2024
0b5c055
IN progress
CyrusNajmabadi Jul 31, 2024
118e271
IN progress
CyrusNajmabadi Jul 31, 2024
35c9449
in progress
CyrusNajmabadi Jul 31, 2024
0227b36
IN progress
CyrusNajmabadi Jul 31, 2024
198e936
In progress
CyrusNajmabadi Jul 31, 2024
5124ead
In progress
CyrusNajmabadi Jul 31, 2024
de3bd01
in progress
CyrusNajmabadi Jul 31, 2024
68379e2
Docs
CyrusNajmabadi Jul 31, 2024
e45c15d
Remove unsupported servicing branches (#74606)
RikkiGibson Jul 30, 2024
39214ab
Move check earlier
CyrusNajmabadi Jul 31, 2024
dfba3ec
Add all
CyrusNajmabadi Jul 31, 2024
51b2ff0
Add properties
CyrusNajmabadi Jul 31, 2024
977afb7
Disallow nameof
CyrusNajmabadi Jul 31, 2024
5217fa8
Add test
CyrusNajmabadi Jul 31, 2024
fced203
Move
CyrusNajmabadi Jul 31, 2024
d89097b
Fix
CyrusNajmabadi Jul 31, 2024
294a6a6
spelling
CyrusNajmabadi Jul 31, 2024
b6ee184
revert
CyrusNajmabadi Jul 31, 2024
49f99ab
Update tests
CyrusNajmabadi Jul 31, 2024
a41fa31
Tweak
CyrusNajmabadi Jul 31, 2024
0a62a2b
In progress
CyrusNajmabadi Jul 31, 2024
1181126
Revert
CyrusNajmabadi Aug 1, 2024
8723be2
in progress
CyrusNajmabadi Aug 1, 2024
39365cd
Merge remote-tracking branch 'cston/field-value-keywords' into semiAu…
CyrusNajmabadi Aug 1, 2024
219d868
Traverse green nodes
cston Aug 1, 2024
b4717dc
Simplify names
cston Aug 1, 2024
74f6f88
Update tests
cston Aug 1, 2024
fe4a559
in progress
CyrusNajmabadi Aug 1, 2024
e44b3df
in progress
CyrusNajmabadi Aug 1, 2024
cad7236
update analyzer logic
CyrusNajmabadi Aug 1, 2024
89f1063
Simplify
CyrusNajmabadi Aug 1, 2024
247c82c
Address feedback
cston Aug 1, 2024
749e60b
Rewriting
CyrusNajmabadi Aug 1, 2024
c8f29d5
Rewriter
CyrusNajmabadi Aug 1, 2024
1858006
Add PROTOTYPE comments
cston Aug 1, 2024
57d8b4d
Formatting
CyrusNajmabadi Aug 1, 2024
1dc3a9b
Formatting
CyrusNajmabadi Aug 1, 2024
64e8faa
Fix test
CyrusNajmabadi Aug 1, 2024
85991db
Merge remote-tracking branch 'cston/field-value-keywords' into semiAu…
CyrusNajmabadi Aug 1, 2024
b56c6c5
Add test
CyrusNajmabadi Aug 1, 2024
51e5848
Merge remote-tracking branch 'upstream/features/field-keyword' into s…
CyrusNajmabadi Aug 1, 2024
81dcce4
Fix
CyrusNajmabadi Aug 1, 2024
3c56e61
Extract type
CyrusNajmabadi Aug 1, 2024
7c894b2
Add tests
CyrusNajmabadi Aug 1, 2024
3d9ff31
update tests
CyrusNajmabadi Aug 1, 2024
5af808c
Move attributes
CyrusNajmabadi Aug 2, 2024
169248a
more test cases
CyrusNajmabadi Aug 2, 2024
495e059
Attributes
CyrusNajmabadi Aug 2, 2024
9f92651
Moving attributes
CyrusNajmabadi Aug 2, 2024
4b7b54d
Extract type
CyrusNajmabadi Aug 2, 2024
117790d
Invert
CyrusNajmabadi Aug 2, 2024
8932350
Extract type
CyrusNajmabadi Aug 2, 2024
c7e3c17
Simplify
CyrusNajmabadi Aug 2, 2024
fdb4c3c
Simplify
CyrusNajmabadi Aug 2, 2024
5ede96b
Simplify
CyrusNajmabadi Aug 2, 2024
2764566
Simplify
CyrusNajmabadi Aug 2, 2024
6f0818a
inline
CyrusNajmabadi Aug 2, 2024
5f96092
inline
CyrusNajmabadi Aug 2, 2024
a1a2064
Extract type
CyrusNajmabadi Aug 2, 2024
8de2fec
Simpler
CyrusNajmabadi Aug 2, 2024
cf5921c
inline
CyrusNajmabadi Aug 2, 2024
961e6a7
Docs
CyrusNajmabadi Aug 2, 2024
868a104
Docs
CyrusNajmabadi Aug 2, 2024
b9fc597
inline
CyrusNajmabadi Aug 2, 2024
84be2ba
Simplify
CyrusNajmabadi Aug 2, 2024
a578fa8
Fix
CyrusNajmabadi Aug 2, 2024
d682631
Add tests
CyrusNajmabadi Aug 2, 2024
8dbe835
check reads/writes
CyrusNajmabadi Aug 2, 2024
f19ef31
Add tests
CyrusNajmabadi Aug 2, 2024
0bab2c9
Fix tests
CyrusNajmabadi Aug 2, 2024
044defd
Add tests
CyrusNajmabadi Aug 2, 2024
d083c2d
Simplify
CyrusNajmabadi Aug 2, 2024
58ada63
Fix tests
CyrusNajmabadi Aug 2, 2024
0465a2e
formatting
CyrusNajmabadi Aug 2, 2024
3af227d
Docs
CyrusNajmabadi Aug 2, 2024
5f5769d
filter out more
CyrusNajmabadi Aug 2, 2024
fe14219
Inline
CyrusNajmabadi Aug 2, 2024
a9587e0
Share more code
CyrusNajmabadi Aug 2, 2024
dc71576
Docs
CyrusNajmabadi Aug 2, 2024
9f72f65
Doc
CyrusNajmabadi Aug 2, 2024
8344b18
Apply suggestions from code review
CyrusNajmabadi Aug 2, 2024
9d56f29
renames
CyrusNajmabadi Aug 2, 2024
c1e5941
Merge branch 'semiAutoProp' of https://github.com/CyrusNajmabadi/rosl…
CyrusNajmabadi Aug 2, 2024
fb9f627
Move
CyrusNajmabadi Aug 2, 2024
03f275b
Renames
CyrusNajmabadi Aug 2, 2024
edd7fad
Docs
CyrusNajmabadi Aug 2, 2024
1e0c11a
add test
CyrusNajmabadi Aug 3, 2024
4ec0be4
add test
CyrusNajmabadi Aug 3, 2024
6f13ed9
add test
CyrusNajmabadi Aug 3, 2024
b00be4e
add test
CyrusNajmabadi Aug 3, 2024
f7a30a0
add test
CyrusNajmabadi Aug 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

view with whitespace off.


using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
Expand All @@ -29,6 +30,12 @@ internal sealed class CSharpUseAutoPropertyAnalyzer : AbstractUseAutoPropertyAna
protected override SyntaxKind PropertyDeclarationKind
=> SyntaxKind.PropertyDeclaration;

protected override bool CanExplicitInterfaceImplementationsBeFixed
=> false;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a move up.


protected override bool SupportsFieldAttributesOnProperties
=> true;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new support. We used to block converting a field/property pair to an auto prop if hte field had attributes. Now we suppor this in c# as you can convert the attribute to [field: Attr...] form and have it still apply to the backing field.


protected override ISemanticFacts SemanticFacts
=> CSharpSemanticFacts.Instance;

Expand All @@ -38,31 +45,49 @@ protected override bool SupportsReadOnlyProperties(Compilation compilation)
protected override bool SupportsPropertyInitializer(Compilation compilation)
=> compilation.LanguageVersion() >= LanguageVersion.CSharp6;

protected override bool CanExplicitInterfaceImplementationsBeFixed()
=> false;
protected override bool SupportsFieldExpression(Compilation compilation)
=> compilation.LanguageVersion() >= LanguageVersion.CSharp13;

protected override ExpressionSyntax? GetFieldInitializer(VariableDeclaratorSyntax variable, CancellationToken cancellationToken)
=> variable.Initializer?.Value;

protected override void RegisterIneligibleFieldsAction(
protected override bool ContainsFieldExpression(PropertyDeclarationSyntax propertyDeclaration, CancellationToken cancellationToken)
{
foreach (var node in propertyDeclaration.DescendantNodes())
{
if (node.IsKind(SyntaxKind.FieldExpression))
return true;
}

return false;
}

protected override void RecordIneligibleFieldLocations(
HashSet<string> fieldNames,
ConcurrentSet<IFieldSymbol> ineligibleFields,
ConcurrentDictionary<IFieldSymbol, ConcurrentSet<SyntaxNode>> ineligibleFields,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we used to just mark a field as being ineligible depending on how it was used in teh file. Now we keep track of the places that previously made it ineligible. If those are outside of the property being converted, we still can't convert (As those external locations can't access the field anymore). But if they are inside of hte proeprty being converted, it is fine as inside the property we can use the new field keyword to reference it.

SemanticModel semanticModel,
SyntaxNode codeBlock,
CancellationToken cancellationToken)
{
foreach (var argument in codeBlock.DescendantNodesAndSelf().OfType<ArgumentSyntax>())
{
// An argument will disqualify a field if that field is used in a ref/out position.
// We can't change such field references to be property references in C#.
// We can't change such field references to be property references in C#, unless we
// are converting to the `field` keyword.
if (argument.RefKindKeyword.Kind() != SyntaxKind.None)
AddIneligibleFieldsForExpression(argument.Expression);

// Use of a field in a nameof(...) expression can't *ever* be converted to use `field`.
// So hard block in this case.
if (argument.Expression.IsNameOfArgumentExpression())
AddIneligibleFieldsForExpression(argument.Expression, alwaysRestricted: true);
}

foreach (var refExpression in codeBlock.DescendantNodesAndSelf().OfType<RefExpressionSyntax>())
AddIneligibleFieldsForExpression(refExpression.Expression);

// Can't take the address of an auto-prop. So disallow for fields that we do `&x` on.
// Can't take the address of an auto-prop. So disallow for fields that we do `&x` on. Unless we are converting
// to the `field` keyword.
foreach (var addressOfExpression in codeBlock.DescendantNodesAndSelf().OfType<PrefixUnaryExpressionSyntax>())
{
if (addressOfExpression.Kind() == SyntaxKind.AddressOfExpression)
Expand All @@ -72,60 +97,74 @@ protected override void RegisterIneligibleFieldsAction(
foreach (var memberAccess in codeBlock.DescendantNodesAndSelf().OfType<MemberAccessExpressionSyntax>())
{
if (CouldReferenceField(memberAccess))
AddIneligibleFieldsIfAccessedOffNotDefinitelyAssignedValue(semanticModel, memberAccess, ineligibleFields, cancellationToken);
AddIneligibleFieldsIfAccessedOffNotDefinitelyAssignedValue(memberAccess);
}

return;

bool CouldReferenceField(ExpressionSyntax expression)
{
// Don't bother binding if the expression isn't even referencing the name of a field we know about.
var rightmostName = expression.GetRightmostName()?.Identifier.ValueText;
return rightmostName != null && fieldNames.Contains(rightmostName);
}

void AddIneligibleFieldsForExpression(ExpressionSyntax expression)
void AddIneligibleFieldsForExpression(ExpressionSyntax expression, bool alwaysRestricted = false)
{
if (!CouldReferenceField(expression))
return;

var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken);
AddIneligibleFields(ineligibleFields, symbolInfo);
AddIneligibleFields(symbolInfo, expression, alwaysRestricted);
}
}

private static void AddIneligibleFieldsIfAccessedOffNotDefinitelyAssignedValue(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved helpers to be inner functions.

SemanticModel semanticModel, MemberAccessExpressionSyntax memberAccess, ConcurrentSet<IFieldSymbol> ineligibleFields, CancellationToken cancellationToken)
{
// `c.x = ...` can't be converted to `c.X = ...` if `c` is a struct and isn't definitely assigned as that point.
void AddIneligibleFieldsIfAccessedOffNotDefinitelyAssignedValue(
MemberAccessExpressionSyntax memberAccess)
{
// `c.x = ...` can't be converted to `c.X = ...` if `c` is a struct and isn't definitely assigned as that point.

// only care about writes. if this was a read, then it must be def assigned and thus is safe to convert to a prop.
if (!memberAccess.IsOnlyWrittenTo())
return;
// only care about writes. if this was a read, then it must be def assigned and thus is safe to convert to a prop.
if (!memberAccess.IsOnlyWrittenTo())
return;

// this only matters for a field access off of a struct. They can be declared unassigned and have their
// fields directly written into.
var symbolInfo = semanticModel.GetSymbolInfo(memberAccess, cancellationToken);
if (symbolInfo.GetAnySymbol() is not IFieldSymbol { ContainingType.TypeKind: TypeKind.Struct })
return;
// this only matters for a field access off of a struct. They can be declared unassigned and have their
// fields directly written into.
var symbolInfo = semanticModel.GetSymbolInfo(memberAccess, cancellationToken);
if (symbolInfo.GetAnySymbol() is not IFieldSymbol { ContainingType.TypeKind: TypeKind.Struct })
return;

var exprSymbol = semanticModel.GetSymbolInfo(memberAccess.Expression, cancellationToken).GetAnySymbol();
if (exprSymbol is not IParameterSymbol and not ILocalSymbol)
return;
var exprSymbol = semanticModel.GetSymbolInfo(memberAccess.Expression, cancellationToken).GetAnySymbol();
if (exprSymbol is not IParameterSymbol and not ILocalSymbol)
return;

var dataFlow = semanticModel.AnalyzeDataFlow(memberAccess.Expression);
if (dataFlow != null && !dataFlow.DefinitelyAssignedOnEntry.Contains(exprSymbol))
AddIneligibleFields(ineligibleFields, symbolInfo);
}
var dataFlow = semanticModel.AnalyzeDataFlow(memberAccess.Expression);
if (dataFlow != null && !dataFlow.DefinitelyAssignedOnEntry.Contains(exprSymbol))
AddIneligibleFields(symbolInfo, memberAccess);
}

private static void AddIneligibleFields(ConcurrentSet<IFieldSymbol> ineligibleFields, SymbolInfo symbolInfo)
{
AddIneligibleField(symbolInfo.Symbol);
foreach (var symbol in symbolInfo.CandidateSymbols)
AddIneligibleField(symbol);
void AddIneligibleFields(
SymbolInfo symbolInfo,
SyntaxNode location,
bool alwaysRestricted = false)
{
AddIneligibleField(symbolInfo.Symbol, location, alwaysRestricted);
foreach (var symbol in symbolInfo.CandidateSymbols)
AddIneligibleField(symbol, location, alwaysRestricted);
}

void AddIneligibleField(ISymbol? symbol)
void AddIneligibleField(
ISymbol? symbol,
SyntaxNode location,
bool alwaysRestricted)
{
// If the field is always restricted, then add the compilation unit itself to the ineligibility locations.
// that way we never think we can convert this field.
if (symbol is IFieldSymbol field)
ineligibleFields.Add(field);
{
AddFieldUsage(ineligibleFields, field, alwaysRestricted
? location.SyntaxTree.GetRoot(cancellationToken)
: location);
}
}
}

Expand Down Expand Up @@ -181,7 +220,7 @@ private static bool CheckExpressionSyntactically(ExpressionSyntax expression)
=> accessorDeclaration is { Body.Statements: [T statement] } ? statement : null;

protected override ExpressionSyntax? GetSetterExpression(
IMethodSymbol setMethod, SemanticModel semanticModel, CancellationToken cancellationToken)
SemanticModel semanticModel, IMethodSymbol setMethod, CancellationToken cancellationToken)
{
// Setter has to be of the form:
//
Expand Down Expand Up @@ -213,4 +252,24 @@ protected override SyntaxNode GetFieldNode(
? fieldDeclaration
: variableDeclarator;
}

protected override void AddAccessedFields(
SemanticModel semanticModel,
IMethodSymbol accessor,
HashSet<string> fieldNames,
HashSet<IFieldSymbol> result,
CancellationToken cancellationToken)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new helper that analyzed the code inside an accessor to see which fields (from teh containing type) that were accessed. These fields will then be checked to see if any could be replaced with field

{
var syntax = accessor.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken);
foreach (var descendant in syntax.DescendantNodesAndSelf())
{
cancellationToken.ThrowIfCancellationRequested();

if (descendant is IdentifierNameSyntax identifierName)
{
result.AddIfNotNull(TryGetDirectlyAccessedFieldSymbol(
semanticModel, identifierName, fieldNames, cancellationToken));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
<Compile Include="$(MSBuildThisFileDirectory)SimplifyInterpolation\SimplifyInterpolationTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UpdateProjectToAllowUnsafe\UpdateProjectToAllowUnsafeTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UpgradeProject\UpgradeProjectTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseAutoProperty\UseAutoPropertyTests_Field.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseAutoProperty\UseAutoPropertyTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseCoalesceExpression\UseCoalesceExpressionForIfNullStatementCheckTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)UseCoalesceExpression\UseCoalesceExpressionForNullableTernaryConditionalCheckTests.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseAutoProperty;

[Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)]
public sealed class UseAutoPropertyTests(ITestOutputHelper logger)
public sealed partial class UseAutoPropertyTests(ITestOutputHelper logger)
: AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor(logger)
{
private readonly ParseOptions CSharp12 = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12);

internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
=> (new CSharpUseAutoPropertyAnalyzer(), GetCSharpUseAutoPropertyCodeFixProvider());

Expand Down Expand Up @@ -667,7 +669,7 @@ class Class
}

[Fact]
public async Task TestGetterWithMutipleStatements()
public async Task TestGetterWithMultipleStatements_CSharp12()
{
await TestMissingInRegularAndScriptAsync(
"""
Expand All @@ -684,11 +686,11 @@ int P
}
}
}
""");
""", new TestParameters(parseOptions: CSharp12));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not supported in c# 12. but will work in c# 13. tests for those are in teh sibling file added.

}

[Fact]
public async Task TestSetterWithMutipleStatements()
public async Task TestSetterWithMultipleStatements_CSharp12()
{
await TestMissingInRegularAndScriptAsync(
"""
Expand Down Expand Up @@ -731,7 +733,7 @@ int P
}
}
}
""");
""", new TestParameters(parseOptions: CSharp12));
}

[Fact]
Expand Down Expand Up @@ -1153,9 +1155,9 @@ partial class Class
}

[Fact]
public async Task TestNotWithFieldWithAttribute()
public async Task TestWithFieldWithAttribute()
{
await TestMissingInRegularAndScriptAsync(
await TestInRegularAndScriptAsync(
"""
class Class
{
Expand All @@ -1170,6 +1172,13 @@ int P
}
}
}
""",
"""
class Class
{
[field: A]
int P { get; }
}
""");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we now support this in any version of hte language.

}

Expand Down
Loading
Loading