Skip to content

Commit 13bbdd5

Browse files
authored
UseType analyzers/fixers should handle ref types (#27246)
1 parent 4db93f3 commit 13bbdd5

File tree

9 files changed

+178
-17
lines changed

9 files changed

+178
-17
lines changed

src/EditorFeatures/CSharpTest/Diagnostics/UseImplicitOrExplicitType/UseExplicitTypeTests.cs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,23 @@ void Method()
184184
}", new TestParameters(options: ExplicitTypeEverywhere()));
185185
}
186186

187+
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExplicitType)]
188+
[WorkItem(27221, "https://github.com/dotnet/roslyn/issues/27221")]
189+
public async Task NotIfRefTypeAlreadyExplicitlyTyped()
190+
{
191+
await TestMissingInRegularAndScriptAsync(
192+
@"using System;
193+
194+
struct Program
195+
{
196+
void Method()
197+
{
198+
ref [|Program|] p = Ref();
199+
}
200+
ref Program Ref() => throw null;
201+
}", new TestParameters(options: ExplicitTypeEverywhere()));
202+
}
203+
187204
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExplicitType)]
188205
public async Task NotOnRHS()
189206
{
@@ -297,6 +314,68 @@ void Method(int? x)
297314
await TestInRegularAndScriptAsync(before, after, options: ExplicitTypeExceptWhereApparent());
298315
}
299316

317+
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExplicitType)]
318+
[WorkItem(27221, "https://github.com/dotnet/roslyn/issues/27221")]
319+
public async Task WithRefIntrinsicType()
320+
{
321+
var before = @"
322+
class Program
323+
{
324+
void Method()
325+
{
326+
ref [|var|] y = Ref();
327+
}
328+
ref int Ref() => throw null;
329+
}";
330+
var after = @"
331+
class Program
332+
{
333+
void Method()
334+
{
335+
ref int y = Ref();
336+
}
337+
ref int Ref() => throw null;
338+
}";
339+
// The type is intrinsic and not apparent
340+
await TestInRegularAndScriptAsync(before, after, options: ExplicitTypeEverywhere());
341+
await TestInRegularAndScriptAsync(before, after, options: ExplicitTypeForBuiltInTypesOnly());
342+
await TestInRegularAndScriptAsync(before, after, options: ExplicitTypeExceptWhereApparent());
343+
}
344+
345+
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExplicitType)]
346+
[WorkItem(27221, "https://github.com/dotnet/roslyn/issues/27221")]
347+
public async Task WithRefIntrinsicTypeInForeach()
348+
{
349+
var before = @"
350+
class E
351+
{
352+
public ref int Current => throw null;
353+
public bool MoveNext() => throw null;
354+
public E GetEnumerator() => throw null;
355+
356+
void M()
357+
{
358+
foreach (ref [|var|] x in this) { }
359+
}
360+
}";
361+
var after = @"
362+
class E
363+
{
364+
public ref int Current => throw null;
365+
public bool MoveNext() => throw null;
366+
public E GetEnumerator() => throw null;
367+
368+
void M()
369+
{
370+
foreach (ref int x in this) { }
371+
}
372+
}";
373+
// The type is intrinsic and not apparent
374+
await TestInRegularAndScriptAsync(before, after, options: ExplicitTypeEverywhere());
375+
await TestInRegularAndScriptAsync(before, after, options: ExplicitTypeForBuiltInTypesOnly());
376+
await TestInRegularAndScriptAsync(before, after, options: ExplicitTypeExceptWhereApparent());
377+
}
378+
300379
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExplicitType)]
301380
[WorkItem(23907, "https://github.com/dotnet/roslyn/issues/23907")]
302381
public async Task InArrayOfNullableIntrinsicType()

src/EditorFeatures/CSharpTest/Diagnostics/UseImplicitOrExplicitType/UseImplicitTypeTests.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,21 @@ void Method()
117117
}", new TestParameters(options: ImplicitTypeEverywhere()));
118118
}
119119

120+
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitType)]
121+
[WorkItem(27221, "https://github.com/dotnet/roslyn/issues/27221")]
122+
public async Task NotOnRefVar()
123+
{
124+
await TestMissingInRegularAndScriptAsync(@"
125+
class Program
126+
{
127+
void Method()
128+
{
129+
ref [|var|] x = Method2();
130+
}
131+
ref int Method2() => throw null;
132+
}", new TestParameters(options: ImplicitTypeEverywhere()));
133+
}
134+
120135
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitType)]
121136
public async Task NotOnDynamic()
122137
{
@@ -447,6 +462,64 @@ static void M()
447462
}", options: ImplicitTypeEverywhere());
448463
}
449464

465+
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitType)]
466+
[WorkItem(27221, "https://github.com/dotnet/roslyn/issues/27221")]
467+
public async Task SuggestVarOnRefIntrinsicType()
468+
{
469+
await TestInRegularAndScriptAsync(
470+
@"using System;
471+
472+
class C
473+
{
474+
static void M()
475+
{
476+
ref [|int|] s = Ref();
477+
}
478+
static ref int Ref() => throw null;
479+
}",
480+
@"using System;
481+
482+
class C
483+
{
484+
static void M()
485+
{
486+
ref var s = Ref();
487+
}
488+
static ref int Ref() => throw null;
489+
}", options: ImplicitTypeEverywhere());
490+
}
491+
492+
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitType)]
493+
[WorkItem(27221, "https://github.com/dotnet/roslyn/issues/27221")]
494+
public async Task WithRefIntrinsicTypeInForeach()
495+
{
496+
var before = @"
497+
class E
498+
{
499+
public ref int Current => throw null;
500+
public bool MoveNext() => throw null;
501+
public E GetEnumerator() => throw null;
502+
503+
void M()
504+
{
505+
foreach (ref [|int|] x in this) { }
506+
}
507+
}";
508+
var after = @"
509+
class E
510+
{
511+
public ref int Current => throw null;
512+
public bool MoveNext() => throw null;
513+
public E GetEnumerator() => throw null;
514+
515+
void M()
516+
{
517+
foreach (ref var x in this) { }
518+
}
519+
}";
520+
await TestInRegularAndScriptAsync(before, after, options: ImplicitTypeEverywhere());
521+
}
522+
450523
[WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitType)]
451524
public async Task SuggestVarOnFrameworkType()
452525
{

src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpTypeStyleDiagnosticAnalyzerBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ private void HandleVariableDeclaration(SyntaxNodeAnalysisContext context)
7171

7272
// The severity preference is not Hidden, as indicated by IsStylePreferred.
7373
var descriptor = GetDescriptorWithSeverity(typeStyle.Severity);
74-
context.ReportDiagnostic(CreateDiagnostic(descriptor, declarationStatement, declaredType.Span));
74+
context.ReportDiagnostic(CreateDiagnostic(descriptor, declarationStatement, declaredType.StripRefIfNeeded().Span));
7575
}
7676

7777
private Diagnostic CreateDiagnostic(DiagnosticDescriptor descriptor, SyntaxNode declaration, TextSpan diagnosticSpan)

src/Features/CSharp/Portable/TypeStyle/UseExplicitTypeCodeFixProvider.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ internal static async Task HandleDeclarationAsync(
5555

5656
TypeSyntax typeSyntax = null;
5757
ParenthesizedVariableDesignationSyntax parensDesignation = null;
58+
if (declarationContext is RefTypeSyntax refType)
59+
{
60+
declarationContext = declarationContext.Parent;
61+
}
62+
5863
if (declarationContext is VariableDeclarationSyntax varDecl)
5964
{
6065
typeSyntax = varDecl.Type;
@@ -78,7 +83,7 @@ internal static async Task HandleDeclarationAsync(
7883

7984
if (parensDesignation is null)
8085
{
81-
var typeSymbol = semanticModel.GetTypeInfo(typeSyntax).ConvertedType;
86+
var typeSymbol = semanticModel.GetTypeInfo(typeSyntax.StripRefIfNeeded()).ConvertedType;
8287

8388
// We're going to be passed through the simplifier. Tell it to not just convert
8489
// this back to var (as that would defeat the purpose of this refactoring entirely).

src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ public static NamespaceDeclarationSyntax GetInnermostNamespaceDeclarationWithUsi
200200
}
201201

202202
public static IEnumerable<SyntaxTrivia> GetAllPrecedingTriviaToPreviousToken(
203-
this SyntaxNode node, SourceText sourceText = null,
203+
this SyntaxNode node, SourceText sourceText = null,
204204
bool includePreviousTokenTrailingTriviaOnlyIfOnSameLine = false)
205205
=> node.GetFirstToken().GetAllPrecedingTriviaToPreviousToken(
206206
sourceText, includePreviousTokenTrailingTriviaOnlyIfOnSameLine);
@@ -210,7 +210,7 @@ public static IEnumerable<SyntaxTrivia> GetAllPrecedingTriviaToPreviousToken(
210210
/// the previous token's trailing trivia and this token's leading trivia).
211211
/// </summary>
212212
public static IEnumerable<SyntaxTrivia> GetAllPrecedingTriviaToPreviousToken(
213-
this SyntaxToken token, SourceText sourceText = null,
213+
this SyntaxToken token, SourceText sourceText = null,
214214
bool includePreviousTokenTrailingTriviaOnlyIfOnSameLine = false)
215215
{
216216
var prevToken = token.GetPreviousToken(includeSkipped: true);
@@ -219,7 +219,7 @@ public static IEnumerable<SyntaxTrivia> GetAllPrecedingTriviaToPreviousToken(
219219
return token.LeadingTrivia;
220220
}
221221

222-
if (includePreviousTokenTrailingTriviaOnlyIfOnSameLine &&
222+
if (includePreviousTokenTrailingTriviaOnlyIfOnSameLine &&
223223
!sourceText.AreOnSameLine(prevToken, token))
224224
{
225225
return token.LeadingTrivia;
@@ -735,7 +735,7 @@ node is UsingStatementSyntax ||
735735
public static StatementSyntax GetEmbeddedStatement(this SyntaxNode node)
736736
{
737737
switch (node)
738-
{
738+
{
739739
case DoStatementSyntax n: return n.Statement;
740740
case ElseClauseSyntax n: return n.Statement;
741741
case FixedStatementSyntax n: return n.Statement;

src/Workspaces/CSharp/Portable/Extensions/TypeSyntaxExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,5 +122,8 @@ public static TypeSyntax GenerateTypeSyntax(this IPropertySymbol property)
122122
return property.Type.GenerateTypeSyntax();
123123
}
124124
}
125+
126+
public static TypeSyntax StripRefIfNeeded(this TypeSyntax type)
127+
=> type is RefTypeSyntax refType ? refType.Type : type;
125128
}
126129
}

src/Workspaces/CSharp/Portable/Utilities/CSharpTypeStyleHelper.State.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ private bool IsTypeApparentInDeclaration(VariableDeclarationSyntax variableDecla
8686
}
8787

8888
var initializerExpression = CSharpUseImplicitTypeHelper.GetInitializerExpression(initializer.Value);
89-
var declaredTypeSymbol = semanticModel.GetTypeInfo(variableDeclaration.Type, cancellationToken).Type;
89+
var declaredTypeSymbol = semanticModel.GetTypeInfo(variableDeclaration.Type.StripRefIfNeeded(), cancellationToken).Type;
9090
return TypeStyleHelper.IsTypeApparentInAssignmentExpression(stylePreferences, initializerExpression, semanticModel, cancellationToken, declaredTypeSymbol);
9191
}
9292

@@ -104,7 +104,7 @@ private bool IsPredefinedTypeInDeclaration(SyntaxNode declarationStatement, Sema
104104
var typeSyntax = GetTypeSyntaxFromDeclaration(declarationStatement);
105105

106106
return typeSyntax != null
107-
? IsMadeOfSpecialTypes(semanticModel.GetTypeInfo(typeSyntax).Type)
107+
? IsMadeOfSpecialTypes(semanticModel.GetTypeInfo(typeSyntax.StripRefIfNeeded()).Type)
108108
: false;
109109
}
110110

src/Workspaces/CSharp/Portable/Utilities/CSharpUseExplicitTypeHelper.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ protected override bool IsStylePreferred(
4040

4141
protected override bool ShouldAnalyzeVariableDeclaration(VariableDeclarationSyntax variableDeclaration, SemanticModel semanticModel, CancellationToken cancellationToken)
4242
{
43-
if (!variableDeclaration.Type.IsVar)
43+
if (!variableDeclaration.Type.StripRefIfNeeded().IsVar)
4444
{
4545
// If the type is not 'var', this analyze has no work to do
4646
return false;
@@ -52,7 +52,7 @@ protected override bool ShouldAnalyzeVariableDeclaration(VariableDeclarationSynt
5252

5353
protected override bool ShouldAnalyzeForEachStatement(ForEachStatementSyntax forEachStatement, SemanticModel semanticModel, CancellationToken cancellationToken)
5454
{
55-
if (!forEachStatement.Type.IsVar)
55+
if (!forEachStatement.Type.StripRefIfNeeded().IsVar)
5656
{
5757
// If the type is not 'var', this analyze has no work to do
5858
return false;
@@ -79,7 +79,7 @@ internal override bool TryAnalyzeVariableDeclaration(
7979

8080
// If it is currently not var, explicit typing exists, return.
8181
// this also takes care of cases where var is mapped to a named type via an alias or a class declaration.
82-
if (!typeName.IsTypeInferred(semanticModel))
82+
if (!typeName.StripRefIfNeeded().IsTypeInferred(semanticModel))
8383
{
8484
return false;
8585
}
@@ -141,7 +141,7 @@ protected override bool AssignmentSupportsStylePreference(
141141
// cases :
142142
// var anon = new { Num = 1 };
143143
// var enumerableOfAnons = from prod in products select new { prod.Color, prod.Price };
144-
var declaredType = semanticModel.GetTypeInfo(typeName, cancellationToken).Type;
144+
var declaredType = semanticModel.GetTypeInfo(typeName.StripRefIfNeeded(), cancellationToken).Type;
145145
if (declaredType.ContainsAnonymousType())
146146
{
147147
return false;

src/Workspaces/CSharp/Portable/Utilities/CSharpUseImplicitTypeHelper.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public override TypeStyleResult AnalyzeTypeName(
2626
TypeSyntax typeName, SemanticModel semanticModel,
2727
OptionSet optionSet, CancellationToken cancellationToken)
2828
{
29-
if (typeName.IsVar)
29+
if (typeName.StripRefIfNeeded().IsVar)
3030
{
3131
return default;
3232
}
@@ -47,9 +47,10 @@ public override TypeStyleResult AnalyzeTypeName(
4747

4848
protected override bool ShouldAnalyzeVariableDeclaration(VariableDeclarationSyntax variableDeclaration, SemanticModel semanticModel, CancellationToken cancellationToken)
4949
{
50-
if (variableDeclaration.Type.IsVar)
50+
var type = variableDeclaration.Type.StripRefIfNeeded();
51+
if (type.IsVar)
5152
{
52-
// If the type is already 'var', this analyze has no work to do
53+
// If the type is already 'var' or 'ref var', this analyzer has no work to do
5354
return false;
5455
}
5556

@@ -94,7 +95,7 @@ internal override bool TryAnalyzeVariableDeclaration(
9495
TypeSyntax typeName, SemanticModel semanticModel,
9596
OptionSet optionSet, CancellationToken cancellationToken)
9697
{
97-
Debug.Assert(!typeName.IsVar, "'var' special case should have prevented analysis of this variable.");
98+
Debug.Assert(!typeName.StripRefIfNeeded().IsVar, "'var' special case should have prevented analysis of this variable.");
9899

99100
var candidateReplacementNode = SyntaxFactory.IdentifierName("var");
100101

@@ -252,7 +253,7 @@ protected override bool AssignmentSupportsStylePreference(
252253
}
253254

254255
// cannot use implicit typing on method group or on dynamic
255-
var declaredType = semanticModel.GetTypeInfo(typeName, cancellationToken).Type;
256+
var declaredType = semanticModel.GetTypeInfo(typeName.StripRefIfNeeded(), cancellationToken).Type;
256257
if (declaredType != null && declaredType.TypeKind == TypeKind.Dynamic)
257258
{
258259
return false;

0 commit comments

Comments
 (0)