diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/UseImplicitOrExplicitType/UseExplicitTypeTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/UseImplicitOrExplicitType/UseExplicitTypeTests.cs index 15fb909091564..ee2b14eed957b 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/UseImplicitOrExplicitType/UseExplicitTypeTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/UseImplicitOrExplicitType/UseExplicitTypeTests.cs @@ -184,6 +184,23 @@ void Method() }", new TestParameters(options: ExplicitTypeEverywhere())); } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExplicitType)] + [WorkItem(27221, "https://github.com/dotnet/roslyn/issues/27221")] + public async Task NotIfRefTypeAlreadyExplicitlyTyped() + { + await TestMissingInRegularAndScriptAsync( +@"using System; + +struct Program +{ + void Method() + { + ref [|Program|] p = Ref(); + } + ref Program Ref() => throw null; +}", new TestParameters(options: ExplicitTypeEverywhere())); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExplicitType)] public async Task NotOnRHS() { @@ -297,6 +314,68 @@ void Method(int? x) await TestInRegularAndScriptAsync(before, after, options: ExplicitTypeExceptWhereApparent()); } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExplicitType)] + [WorkItem(27221, "https://github.com/dotnet/roslyn/issues/27221")] + public async Task WithRefIntrinsicType() + { + var before = @" +class Program +{ + void Method() + { + ref [|var|] y = Ref(); + } + ref int Ref() => throw null; +}"; + var after = @" +class Program +{ + void Method() + { + ref int y = Ref(); + } + ref int Ref() => throw null; +}"; + // The type is intrinsic and not apparent + await TestInRegularAndScriptAsync(before, after, options: ExplicitTypeEverywhere()); + await TestInRegularAndScriptAsync(before, after, options: ExplicitTypeForBuiltInTypesOnly()); + await TestInRegularAndScriptAsync(before, after, options: ExplicitTypeExceptWhereApparent()); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExplicitType)] + [WorkItem(27221, "https://github.com/dotnet/roslyn/issues/27221")] + public async Task WithRefIntrinsicTypeInForeach() + { + var before = @" +class E +{ + public ref int Current => throw null; + public bool MoveNext() => throw null; + public E GetEnumerator() => throw null; + + void M() + { + foreach (ref [|var|] x in this) { } + } +}"; + var after = @" +class E +{ + public ref int Current => throw null; + public bool MoveNext() => throw null; + public E GetEnumerator() => throw null; + + void M() + { + foreach (ref int x in this) { } + } +}"; + // The type is intrinsic and not apparent + await TestInRegularAndScriptAsync(before, after, options: ExplicitTypeEverywhere()); + await TestInRegularAndScriptAsync(before, after, options: ExplicitTypeForBuiltInTypesOnly()); + await TestInRegularAndScriptAsync(before, after, options: ExplicitTypeExceptWhereApparent()); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseExplicitType)] [WorkItem(23907, "https://github.com/dotnet/roslyn/issues/23907")] public async Task InArrayOfNullableIntrinsicType() diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/UseImplicitOrExplicitType/UseImplicitTypeTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/UseImplicitOrExplicitType/UseImplicitTypeTests.cs index 9f0ce0273337a..a182dd642e326 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/UseImplicitOrExplicitType/UseImplicitTypeTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/UseImplicitOrExplicitType/UseImplicitTypeTests.cs @@ -117,6 +117,21 @@ void Method() }", new TestParameters(options: ImplicitTypeEverywhere())); } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitType)] + [WorkItem(27221, "https://github.com/dotnet/roslyn/issues/27221")] + public async Task NotOnRefVar() + { + await TestMissingInRegularAndScriptAsync(@" +class Program +{ + void Method() + { + ref [|var|] x = Method2(); + } + ref int Method2() => throw null; +}", new TestParameters(options: ImplicitTypeEverywhere())); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitType)] public async Task NotOnDynamic() { @@ -447,6 +462,64 @@ static void M() }", options: ImplicitTypeEverywhere()); } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitType)] + [WorkItem(27221, "https://github.com/dotnet/roslyn/issues/27221")] + public async Task SuggestVarOnRefIntrinsicType() + { + await TestInRegularAndScriptAsync( +@"using System; + +class C +{ + static void M() + { + ref [|int|] s = Ref(); + } + static ref int Ref() => throw null; +}", +@"using System; + +class C +{ + static void M() + { + ref var s = Ref(); + } + static ref int Ref() => throw null; +}", options: ImplicitTypeEverywhere()); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitType)] + [WorkItem(27221, "https://github.com/dotnet/roslyn/issues/27221")] + public async Task WithRefIntrinsicTypeInForeach() + { + var before = @" +class E +{ + public ref int Current => throw null; + public bool MoveNext() => throw null; + public E GetEnumerator() => throw null; + + void M() + { + foreach (ref [|int|] x in this) { } + } +}"; + var after = @" +class E +{ + public ref int Current => throw null; + public bool MoveNext() => throw null; + public E GetEnumerator() => throw null; + + void M() + { + foreach (ref var x in this) { } + } +}"; + await TestInRegularAndScriptAsync(before, after, options: ImplicitTypeEverywhere()); + } + [WpfFact, Trait(Traits.Feature, Traits.Features.CodeActionsUseImplicitType)] public async Task SuggestVarOnFrameworkType() { diff --git a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpTypeStyleDiagnosticAnalyzerBase.cs b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpTypeStyleDiagnosticAnalyzerBase.cs index 5fe94ce0e4d29..e26dd083eac48 100644 --- a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpTypeStyleDiagnosticAnalyzerBase.cs +++ b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpTypeStyleDiagnosticAnalyzerBase.cs @@ -71,7 +71,7 @@ private void HandleVariableDeclaration(SyntaxNodeAnalysisContext context) // The severity preference is not Hidden, as indicated by IsStylePreferred. var descriptor = GetDescriptorWithSeverity(typeStyle.Severity); - context.ReportDiagnostic(CreateDiagnostic(descriptor, declarationStatement, declaredType.Span)); + context.ReportDiagnostic(CreateDiagnostic(descriptor, declarationStatement, declaredType.StripRefIfNeeded().Span)); } private Diagnostic CreateDiagnostic(DiagnosticDescriptor descriptor, SyntaxNode declaration, TextSpan diagnosticSpan) diff --git a/src/Features/CSharp/Portable/TypeStyle/UseExplicitTypeCodeFixProvider.cs b/src/Features/CSharp/Portable/TypeStyle/UseExplicitTypeCodeFixProvider.cs index 134c7d8e53790..2c9e604a233dc 100644 --- a/src/Features/CSharp/Portable/TypeStyle/UseExplicitTypeCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/TypeStyle/UseExplicitTypeCodeFixProvider.cs @@ -55,6 +55,11 @@ internal static async Task HandleDeclarationAsync( TypeSyntax typeSyntax = null; ParenthesizedVariableDesignationSyntax parensDesignation = null; + if (declarationContext is RefTypeSyntax refType) + { + declarationContext = declarationContext.Parent; + } + if (declarationContext is VariableDeclarationSyntax varDecl) { typeSyntax = varDecl.Type; @@ -78,7 +83,7 @@ internal static async Task HandleDeclarationAsync( if (parensDesignation is null) { - var typeSymbol = semanticModel.GetTypeInfo(typeSyntax).ConvertedType; + var typeSymbol = semanticModel.GetTypeInfo(typeSyntax.StripRefIfNeeded()).ConvertedType; // We're going to be passed through the simplifier. Tell it to not just convert // this back to var (as that would defeat the purpose of this refactoring entirely). diff --git a/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs index 08700568340a2..ebd0d4d660927 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/SyntaxNodeExtensions.cs @@ -200,7 +200,7 @@ public static NamespaceDeclarationSyntax GetInnermostNamespaceDeclarationWithUsi } public static IEnumerable GetAllPrecedingTriviaToPreviousToken( - this SyntaxNode node, SourceText sourceText = null, + this SyntaxNode node, SourceText sourceText = null, bool includePreviousTokenTrailingTriviaOnlyIfOnSameLine = false) => node.GetFirstToken().GetAllPrecedingTriviaToPreviousToken( sourceText, includePreviousTokenTrailingTriviaOnlyIfOnSameLine); @@ -210,7 +210,7 @@ public static IEnumerable GetAllPrecedingTriviaToPreviousToken( /// the previous token's trailing trivia and this token's leading trivia). /// public static IEnumerable GetAllPrecedingTriviaToPreviousToken( - this SyntaxToken token, SourceText sourceText = null, + this SyntaxToken token, SourceText sourceText = null, bool includePreviousTokenTrailingTriviaOnlyIfOnSameLine = false) { var prevToken = token.GetPreviousToken(includeSkipped: true); @@ -219,7 +219,7 @@ public static IEnumerable GetAllPrecedingTriviaToPreviousToken( return token.LeadingTrivia; } - if (includePreviousTokenTrailingTriviaOnlyIfOnSameLine && + if (includePreviousTokenTrailingTriviaOnlyIfOnSameLine && !sourceText.AreOnSameLine(prevToken, token)) { return token.LeadingTrivia; @@ -735,7 +735,7 @@ node is UsingStatementSyntax || public static StatementSyntax GetEmbeddedStatement(this SyntaxNode node) { switch (node) - { + { case DoStatementSyntax n: return n.Statement; case ElseClauseSyntax n: return n.Statement; case FixedStatementSyntax n: return n.Statement; diff --git a/src/Workspaces/CSharp/Portable/Extensions/TypeSyntaxExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/TypeSyntaxExtensions.cs index 877e1db59c7ce..adcc44659d562 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/TypeSyntaxExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/TypeSyntaxExtensions.cs @@ -122,5 +122,8 @@ public static TypeSyntax GenerateTypeSyntax(this IPropertySymbol property) return property.Type.GenerateTypeSyntax(); } } + + public static TypeSyntax StripRefIfNeeded(this TypeSyntax type) + => type is RefTypeSyntax refType ? refType.Type : type; } } diff --git a/src/Workspaces/CSharp/Portable/Utilities/CSharpTypeStyleHelper.State.cs b/src/Workspaces/CSharp/Portable/Utilities/CSharpTypeStyleHelper.State.cs index 853a9e20dc413..5857dc9c8a1c4 100644 --- a/src/Workspaces/CSharp/Portable/Utilities/CSharpTypeStyleHelper.State.cs +++ b/src/Workspaces/CSharp/Portable/Utilities/CSharpTypeStyleHelper.State.cs @@ -86,7 +86,7 @@ private bool IsTypeApparentInDeclaration(VariableDeclarationSyntax variableDecla } var initializerExpression = CSharpUseImplicitTypeHelper.GetInitializerExpression(initializer.Value); - var declaredTypeSymbol = semanticModel.GetTypeInfo(variableDeclaration.Type, cancellationToken).Type; + var declaredTypeSymbol = semanticModel.GetTypeInfo(variableDeclaration.Type.StripRefIfNeeded(), cancellationToken).Type; return TypeStyleHelper.IsTypeApparentInAssignmentExpression(stylePreferences, initializerExpression, semanticModel, cancellationToken, declaredTypeSymbol); } @@ -104,7 +104,7 @@ private bool IsPredefinedTypeInDeclaration(SyntaxNode declarationStatement, Sema var typeSyntax = GetTypeSyntaxFromDeclaration(declarationStatement); return typeSyntax != null - ? IsMadeOfSpecialTypes(semanticModel.GetTypeInfo(typeSyntax).Type) + ? IsMadeOfSpecialTypes(semanticModel.GetTypeInfo(typeSyntax.StripRefIfNeeded()).Type) : false; } diff --git a/src/Workspaces/CSharp/Portable/Utilities/CSharpUseExplicitTypeHelper.cs b/src/Workspaces/CSharp/Portable/Utilities/CSharpUseExplicitTypeHelper.cs index 7524f300cfc11..300c4dc6d8efd 100644 --- a/src/Workspaces/CSharp/Portable/Utilities/CSharpUseExplicitTypeHelper.cs +++ b/src/Workspaces/CSharp/Portable/Utilities/CSharpUseExplicitTypeHelper.cs @@ -40,7 +40,7 @@ protected override bool IsStylePreferred( protected override bool ShouldAnalyzeVariableDeclaration(VariableDeclarationSyntax variableDeclaration, SemanticModel semanticModel, CancellationToken cancellationToken) { - if (!variableDeclaration.Type.IsVar) + if (!variableDeclaration.Type.StripRefIfNeeded().IsVar) { // If the type is not 'var', this analyze has no work to do return false; @@ -52,7 +52,7 @@ protected override bool ShouldAnalyzeVariableDeclaration(VariableDeclarationSynt protected override bool ShouldAnalyzeForEachStatement(ForEachStatementSyntax forEachStatement, SemanticModel semanticModel, CancellationToken cancellationToken) { - if (!forEachStatement.Type.IsVar) + if (!forEachStatement.Type.StripRefIfNeeded().IsVar) { // If the type is not 'var', this analyze has no work to do return false; @@ -79,7 +79,7 @@ internal override bool TryAnalyzeVariableDeclaration( // If it is currently not var, explicit typing exists, return. // this also takes care of cases where var is mapped to a named type via an alias or a class declaration. - if (!typeName.IsTypeInferred(semanticModel)) + if (!typeName.StripRefIfNeeded().IsTypeInferred(semanticModel)) { return false; } @@ -141,7 +141,7 @@ protected override bool AssignmentSupportsStylePreference( // cases : // var anon = new { Num = 1 }; // var enumerableOfAnons = from prod in products select new { prod.Color, prod.Price }; - var declaredType = semanticModel.GetTypeInfo(typeName, cancellationToken).Type; + var declaredType = semanticModel.GetTypeInfo(typeName.StripRefIfNeeded(), cancellationToken).Type; if (declaredType.ContainsAnonymousType()) { return false; diff --git a/src/Workspaces/CSharp/Portable/Utilities/CSharpUseImplicitTypeHelper.cs b/src/Workspaces/CSharp/Portable/Utilities/CSharpUseImplicitTypeHelper.cs index d96f89599f679..55de841116a41 100644 --- a/src/Workspaces/CSharp/Portable/Utilities/CSharpUseImplicitTypeHelper.cs +++ b/src/Workspaces/CSharp/Portable/Utilities/CSharpUseImplicitTypeHelper.cs @@ -26,7 +26,7 @@ public override TypeStyleResult AnalyzeTypeName( TypeSyntax typeName, SemanticModel semanticModel, OptionSet optionSet, CancellationToken cancellationToken) { - if (typeName.IsVar) + if (typeName.StripRefIfNeeded().IsVar) { return default; } @@ -47,9 +47,10 @@ public override TypeStyleResult AnalyzeTypeName( protected override bool ShouldAnalyzeVariableDeclaration(VariableDeclarationSyntax variableDeclaration, SemanticModel semanticModel, CancellationToken cancellationToken) { - if (variableDeclaration.Type.IsVar) + var type = variableDeclaration.Type.StripRefIfNeeded(); + if (type.IsVar) { - // If the type is already 'var', this analyze has no work to do + // If the type is already 'var' or 'ref var', this analyzer has no work to do return false; } @@ -94,7 +95,7 @@ internal override bool TryAnalyzeVariableDeclaration( TypeSyntax typeName, SemanticModel semanticModel, OptionSet optionSet, CancellationToken cancellationToken) { - Debug.Assert(!typeName.IsVar, "'var' special case should have prevented analysis of this variable."); + Debug.Assert(!typeName.StripRefIfNeeded().IsVar, "'var' special case should have prevented analysis of this variable."); var candidateReplacementNode = SyntaxFactory.IdentifierName("var"); @@ -252,7 +253,7 @@ protected override bool AssignmentSupportsStylePreference( } // cannot use implicit typing on method group or on dynamic - var declaredType = semanticModel.GetTypeInfo(typeName, cancellationToken).Type; + var declaredType = semanticModel.GetTypeInfo(typeName.StripRefIfNeeded(), cancellationToken).Type; if (declaredType != null && declaredType.TypeKind == TypeKind.Dynamic) { return false;