From b34af9f3f250b518943e6654c1745d5f5cc53d64 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jan 2025 22:13:21 -0800 Subject: [PATCH 1/2] Update 'use nameof instead of typeof' to support generic types --- ...ConvertTypeOfToNameOfDiagnosticAnalyzer.cs | 9 +- ...arpConvertTypeOfToNameOfCodeFixProvider.cs | 11 +- ...boundGenericTypeInNameOfCodeFixProvider.cs | 26 +++- .../ConvertTypeOfToNameOfTests.cs | 138 ++++++++++++++---- ...ConvertTypeOfToNameOfDiagnosticAnalyzer.cs | 38 +++-- ...actConvertTypeOfToNameOfCodeFixProvider.cs | 12 +- ...ConvertTypeOfToNameOfDiagnosticAnalyzer.vb | 4 + ...icConvertGetTypeToNameOfCodeFixProvider.vb | 4 + 8 files changed, 172 insertions(+), 70 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/ConvertTypeofToNameof/CSharpConvertTypeOfToNameOfDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/ConvertTypeofToNameof/CSharpConvertTypeOfToNameOfDiagnosticAnalyzer.cs index c1a62cac3a43..59a110ab2350 100644 --- a/src/Analyzers/CSharp/Analyzers/ConvertTypeofToNameof/CSharpConvertTypeOfToNameOfDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/ConvertTypeofToNameof/CSharpConvertTypeOfToNameOfDiagnosticAnalyzer.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.ConvertTypeOfToNameOf; using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; @@ -13,13 +14,13 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertTypeOfToNameOf; /// Finds code like typeof(someType).Name and determines whether it can be changed to nameof(someType), if yes then it offers a diagnostic /// [DiagnosticAnalyzer(LanguageNames.CSharp)] -internal sealed class CSharpConvertTypeOfToNameOfDiagnosticAnalyzer : AbstractConvertTypeOfToNameOfDiagnosticAnalyzer +internal sealed class CSharpConvertTypeOfToNameOfDiagnosticAnalyzer() + : AbstractConvertTypeOfToNameOfDiagnosticAnalyzer(s_title) { private static readonly string s_title = CSharpAnalyzersResources.typeof_can_be_converted_to_nameof; - public CSharpConvertTypeOfToNameOfDiagnosticAnalyzer() : base(s_title) - { - } + protected override bool SupportsUnboundGenerics(ParseOptions options) + => options.LanguageVersion().IsCSharp14OrAbove(); protected override bool IsValidTypeofAction(OperationAnalysisContext context) { diff --git a/src/Analyzers/CSharp/CodeFixes/ConvertTypeOfToNameOf/CSharpConvertTypeOfToNameOfCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/ConvertTypeOfToNameOf/CSharpConvertTypeOfToNameOfCodeFixProvider.cs index 7eebb4cffc71..54b91bec84e9 100644 --- a/src/Analyzers/CSharp/CodeFixes/ConvertTypeOfToNameOf/CSharpConvertTypeOfToNameOfCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/ConvertTypeOfToNameOf/CSharpConvertTypeOfToNameOfCodeFixProvider.cs @@ -8,7 +8,9 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.ConvertTypeOfToNameOf; using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.UseUnboundGenericTypeInNameOf; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -17,12 +19,17 @@ namespace Microsoft.CodeAnalysis.CSharp.ConvertTypeOfToNameOf; [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.ConvertTypeOfToNameOf), Shared] [method: ImportingConstructor] [method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] -internal sealed class CSharpConvertTypeOfToNameOfCodeFixProvider() : AbstractConvertTypeOfToNameOfCodeFixProvider< - MemberAccessExpressionSyntax> +internal sealed class CSharpConvertTypeOfToNameOfCodeFixProvider() + : AbstractConvertTypeOfToNameOfCodeFixProvider { protected override string GetCodeFixTitle() => CSharpCodeFixesResources.Convert_typeof_to_nameof; + protected override SyntaxNode ConvertToUnboundGeneric(ParseOptions options, SyntaxNode nameOfSyntax) + => options.LanguageVersion().IsCSharp14OrAbove() + ? CSharpUseUnboundGenericTypeInNameOfCodeFixProvider.ConvertToUnboundGenericNameof(nameOfSyntax) + : nameOfSyntax; + protected override SyntaxNode GetSymbolTypeExpression(SemanticModel model, MemberAccessExpressionSyntax node, CancellationToken cancellationToken) { // The corresponding analyzer (CSharpConvertTypeOfToNameOfDiagnosticAnalyzer) validated the syntax diff --git a/src/Analyzers/CSharp/CodeFixes/UseUnboundGenericTypeInNameOf/CSharpUseUnboundGenericTypeInNameOfCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseUnboundGenericTypeInNameOf/CSharpUseUnboundGenericTypeInNameOfCodeFixProvider.cs index 657f4593e77d..61565a07875f 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseUnboundGenericTypeInNameOf/CSharpUseUnboundGenericTypeInNameOfCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseUnboundGenericTypeInNameOf/CSharpUseUnboundGenericTypeInNameOfCodeFixProvider.cs @@ -53,14 +53,24 @@ private static void FixOne( if (!nameofInvocation.IsNameOfInvocation()) return; - foreach (var typeArgumentList in nameofInvocation.DescendantNodes().OfType().OrderByDescending(t => t.SpanStart)) - { - if (typeArgumentList.Arguments.Any(a => a.Kind() != SyntaxKind.OmittedTypeArgument)) + editor.ReplaceNode(nameofInvocation, ConvertToUnboundGenericNameof(nameofInvocation)); + } + + public static TSyntax ConvertToUnboundGenericNameof(TSyntax syntax) + where TSyntax : SyntaxNode + { + return syntax.ReplaceNodes( + syntax.DescendantNodes().OfType(), + (original, current) => { - var list = NodeOrTokenList(typeArgumentList.Arguments.GetWithSeparators().Select( - t => t.IsToken ? t.AsToken().WithoutTrivia() : s_omittedArgument)); - editor.ReplaceNode(typeArgumentList, typeArgumentList.WithArguments(SeparatedList(list))); - } - } + if (current.Arguments.Any(a => a.Kind() != SyntaxKind.OmittedTypeArgument)) + { + var list = NodeOrTokenList(current.Arguments.GetWithSeparators().Select( + t => t.IsToken ? t.AsToken().WithoutTrivia() : s_omittedArgument)); + return current.WithArguments(SeparatedList(list)); + } + + return current; + }); } } diff --git a/src/Analyzers/CSharp/Tests/ConvertTypeOfToNameOf/ConvertTypeOfToNameOfTests.cs b/src/Analyzers/CSharp/Tests/ConvertTypeOfToNameOf/ConvertTypeOfToNameOfTests.cs index b757e5f7fdb1..9f300d374004 100644 --- a/src/Analyzers/CSharp/Tests/ConvertTypeOfToNameOf/ConvertTypeOfToNameOfTests.cs +++ b/src/Analyzers/CSharp/Tests/ConvertTypeOfToNameOf/ConvertTypeOfToNameOfTests.cs @@ -18,6 +18,8 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertTypeOfToNameOf; [Trait(Traits.Feature, Traits.Features.ConvertTypeOfToNameOf)] public partial class ConvertTypeOfToNameOfTests { + private static readonly LanguageVersion CSharp14 = LanguageVersion.Preview; + [Fact] public async Task BasicType() { @@ -233,21 +235,57 @@ void Method() } [Fact] - public async Task NotInGenericType() + public async Task GenericType_CSharp13() { - var text = """ - class Test - { - class Goo - { - void M() - { - _ = typeof(Goo).Name; + await new VerifyCS.Test + { + TestCode = """ + class Test + { + class Goo + { + void M() + { + _ = typeof(Goo).Name; + } } } - } - """; - await VerifyCS.VerifyCodeFixAsync(text, text); + """, + LanguageVersion = LanguageVersion.CSharp13, + }.RunAsync(); + } + + [Fact] + public async Task GenericType_CSharp14() + { + await new VerifyCS.Test + { + TestCode = """ + class Test + { + class Goo + { + void M() + { + _ = [|typeof(Goo).Name|]; + } + } + } + """, + FixedCode = """ + class Test + { + class Goo + { + void M() + { + _ = nameof(Goo<>); + } + } + } + """, + LanguageVersion = CSharp14, + }.RunAsync(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47129")] @@ -287,33 +325,69 @@ void M() } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47129")] - public async Task NestedInGenericType2() + public async Task NestedInGenericType2_CSharp13() { - var text = """ - using System; - using System.Collections.Generic; + await new VerifyCS.Test + { + TestCode = """ + using System; + using System.Collections.Generic; - class Test - { - public void M() + class Test { - Console.WriteLine([|typeof(List.Enumerator).Name|]); + public void M() + { + Console.WriteLine([|typeof(List.Enumerator).Name|]); + } } - } - """; - var expected = """ - using System; - using System.Collections.Generic; + """, + FixedCode = """ + using System; + using System.Collections.Generic; - class Test - { - public void M() + class Test { - Console.WriteLine(nameof(List.Enumerator)); + public void M() + { + Console.WriteLine(nameof(List.Enumerator)); + } } - } - """; - await VerifyCS.VerifyCodeFixAsync(text, expected); + """, + LanguageVersion = LanguageVersion.CSharp13, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47129")] + public async Task NestedInGenericType2_CSharp14() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + using System.Collections.Generic; + + class Test + { + public void M() + { + Console.WriteLine([|typeof(List.Enumerator).Name|]); + } + } + """, + FixedCode = """ + using System; + using System.Collections.Generic; + + class Test + { + public void M() + { + Console.WriteLine(nameof(List<>.Enumerator)); + } + } + """, + LanguageVersion = CSharp14, + }.RunAsync(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/54233")] diff --git a/src/Analyzers/Core/Analyzers/ConvertTypeofToNameof/AbstractConvertTypeOfToNameOfDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/ConvertTypeofToNameof/AbstractConvertTypeOfToNameOfDiagnosticAnalyzer.cs index 9cd2d87b87ed..eb64cc6165d2 100644 --- a/src/Analyzers/Core/Analyzers/ConvertTypeofToNameof/AbstractConvertTypeOfToNameOfDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/ConvertTypeofToNameof/AbstractConvertTypeOfToNameOfDiagnosticAnalyzer.cs @@ -8,21 +8,20 @@ namespace Microsoft.CodeAnalysis.ConvertTypeOfToNameOf; -internal abstract class AbstractConvertTypeOfToNameOfDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer +internal abstract class AbstractConvertTypeOfToNameOfDiagnosticAnalyzer(LocalizableString title) + : AbstractBuiltInCodeStyleDiagnosticAnalyzer( + IDEDiagnosticIds.ConvertTypeOfToNameOfDiagnosticId, + EnforceOnBuildValues.ConvertTypeOfToNameOf, + option: null, + title: title) { - protected AbstractConvertTypeOfToNameOfDiagnosticAnalyzer(LocalizableString title) - : base(diagnosticId: IDEDiagnosticIds.ConvertTypeOfToNameOfDiagnosticId, - EnforceOnBuildValues.ConvertTypeOfToNameOf, - option: null, - title: title) - { - } - public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; protected abstract bool IsValidTypeofAction(OperationAnalysisContext context); + protected abstract bool SupportsUnboundGenerics(ParseOptions options); + protected override void InitializeWorker(AnalysisContext context) { context.RegisterOperationAction(AnalyzeAction, OperationKind.TypeOf); @@ -34,48 +33,45 @@ protected void AnalyzeAction(OperationAnalysisContext context) return; if (!IsValidTypeofAction(context) || !IsValidOperation(context.Operation)) - { return; - } var node = context.Operation.Syntax; var parent = node.Parent; + // If the parent node is null then it cannot be a member access, so do not report a diagnostic if (parent is null) - { return; - } var location = parent.GetLocation(); context.ReportDiagnostic(Diagnostic.Create(Descriptor, location)); } - private static bool IsValidOperation(IOperation operation) + private bool IsValidOperation(IOperation operation) { // Cast to a typeof operation & check parent is a property reference and member access var typeofOperation = (ITypeOfOperation)operation; if (operation.Parent is not IPropertyReferenceOperation) - { return false; - } // Check Parent is a .Name access var operationParent = (IPropertyReferenceOperation)operation.Parent; var parentProperty = operationParent.Property.Name; if (parentProperty is not nameof(System.Type.Name)) - { return false; - } // If it's a generic type, do not offer the fix because nameof(T) and typeof(T).Name are not // semantically equivalent, the resulting string is formatted differently, where typeof(T).Name // return "T`1" and nameof just returns "T" if (typeofOperation.TypeOperand is IErrorTypeSymbol) - { return false; - } - return typeofOperation.TypeOperand is INamedTypeSymbol namedType && namedType.TypeArguments.Length == 0; + if (typeofOperation.TypeOperand is not INamedTypeSymbol namedType) + return false; + + if (namedType.TypeArguments.Length == 0) + return true; + + return SupportsUnboundGenerics(operation.Syntax.SyntaxTree.Options); } } diff --git a/src/Analyzers/Core/CodeFixes/ConvertTypeOfToNameOf/AbstractConvertTypeOfToNameOfCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/ConvertTypeOfToNameOf/AbstractConvertTypeOfToNameOfCodeFixProvider.cs index 5aa864ea89df..8dd9ef89348a 100644 --- a/src/Analyzers/Core/CodeFixes/ConvertTypeOfToNameOf/AbstractConvertTypeOfToNameOfCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/ConvertTypeOfToNameOf/AbstractConvertTypeOfToNameOfCodeFixProvider.cs @@ -5,7 +5,6 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editing; @@ -19,6 +18,7 @@ internal abstract class AbstractConvertTypeOfToNameOfCodeFixProvider< { protected abstract string GetCodeFixTitle(); + protected abstract SyntaxNode ConvertToUnboundGeneric(ParseOptions options, SyntaxNode nameOfSyntax); protected abstract SyntaxNode GetSymbolTypeExpression(SemanticModel model, TMemberAccessExpressionSyntax node, CancellationToken cancellationToken); public sealed override ImmutableArray FixableDiagnosticIds @@ -48,10 +48,16 @@ protected override async Task FixAllAsync( /// /// Method converts typeof(...).Name to nameof(...) /// - public void ConvertTypeOfToNameOf(SemanticModel semanticModel, SyntaxEditor editor, TMemberAccessExpressionSyntax nodeToReplace, CancellationToken cancellationToken) + private void ConvertTypeOfToNameOf( + SemanticModel semanticModel, + SyntaxEditor editor, + TMemberAccessExpressionSyntax nodeToReplace, + CancellationToken cancellationToken) { var typeExpression = GetSymbolTypeExpression(semanticModel, nodeToReplace, cancellationToken); var nameOfSyntax = editor.Generator.NameOfExpression(typeExpression); - editor.ReplaceNode(nodeToReplace, nameOfSyntax); + editor.ReplaceNode( + nodeToReplace, + ConvertToUnboundGeneric(semanticModel.SyntaxTree.Options, nameOfSyntax)); } } diff --git a/src/Analyzers/VisualBasic/Analyzers/ConvertTypeofToNameof/VisualBasicConvertTypeOfToNameOfDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/ConvertTypeofToNameof/VisualBasicConvertTypeOfToNameOfDiagnosticAnalyzer.vb index 03d6de10849a..3f2a09238102 100644 --- a/src/Analyzers/VisualBasic/Analyzers/ConvertTypeofToNameof/VisualBasicConvertTypeOfToNameOfDiagnosticAnalyzer.vb +++ b/src/Analyzers/VisualBasic/Analyzers/ConvertTypeofToNameof/VisualBasicConvertTypeOfToNameOfDiagnosticAnalyzer.vb @@ -18,6 +18,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ConvertTypeOfToNameOf MyBase.New(s_title) End Sub + Protected Overrides Function SupportsUnboundGenerics(options As ParseOptions) As Boolean + Return False + End Function + Protected Overrides Function IsValidTypeofAction(context As OperationAnalysisContext) As Boolean Dim node = context.Operation.Syntax Dim compilation = context.Compilation diff --git a/src/Analyzers/VisualBasic/CodeFixes/ConvertTypeOfToNameOf/VisualBasicConvertGetTypeToNameOfCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/ConvertTypeOfToNameOf/VisualBasicConvertGetTypeToNameOfCodeFixProvider.vb index b9b6649de78d..4795664df344 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/ConvertTypeOfToNameOf/VisualBasicConvertGetTypeToNameOfCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/ConvertTypeOfToNameOf/VisualBasicConvertGetTypeToNameOfCodeFixProvider.vb @@ -26,6 +26,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ConvertTypeOfToNameOf Return VisualBasicCodeFixesResources.Convert_GetType_to_NameOf End Function + Protected Overrides Function ConvertToUnboundGeneric(options As ParseOptions, nameOfSyntax As SyntaxNode) As SyntaxNode + Return nameOfSyntax + End Function + Protected Overrides Function GetSymbolTypeExpression(semanticModel As SemanticModel, node As MemberAccessExpressionSyntax, cancellationToken As CancellationToken) As SyntaxNode Dim expression = node.Expression Dim type = DirectCast(expression, GetTypeExpressionSyntax).Type From 661a517dc6ceb51863e172f34ea0a1fe94a4e3fd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jan 2025 22:18:00 -0800 Subject: [PATCH 2/2] Add more tests --- .../ConvertTypeOfToNameOfTests.cs | 108 ++++++++++++++++++ ...ConvertTypeOfToNameOfDiagnosticAnalyzer.cs | 2 + 2 files changed, 110 insertions(+) diff --git a/src/Analyzers/CSharp/Tests/ConvertTypeOfToNameOf/ConvertTypeOfToNameOfTests.cs b/src/Analyzers/CSharp/Tests/ConvertTypeOfToNameOf/ConvertTypeOfToNameOfTests.cs index 9f300d374004..0c357404fdc9 100644 --- a/src/Analyzers/CSharp/Tests/ConvertTypeOfToNameOf/ConvertTypeOfToNameOfTests.cs +++ b/src/Analyzers/CSharp/Tests/ConvertTypeOfToNameOf/ConvertTypeOfToNameOfTests.cs @@ -288,6 +288,60 @@ void M() }.RunAsync(); } + [Fact] + public async Task UnboundGenericType_CSharp13() + { + await new VerifyCS.Test + { + TestCode = """ + class Test + { + class Goo + { + void M() + { + _ = typeof(Goo<>).Name; + } + } + } + """, + LanguageVersion = LanguageVersion.CSharp13, + }.RunAsync(); + } + + [Fact] + public async Task UnboundGenericType_CSharp14() + { + await new VerifyCS.Test + { + TestCode = """ + class Test + { + class Goo + { + void M() + { + _ = [|typeof(Goo<>).Name|]; + } + } + } + """, + FixedCode = """ + class Test + { + class Goo + { + void M() + { + _ = nameof(Goo<>); + } + } + } + """, + LanguageVersion = CSharp14, + }.RunAsync(); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47129")] public async Task NestedInGenericType() { @@ -390,6 +444,60 @@ public void M() }.RunAsync(); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47129")] + public async Task NestedInGenericType_UnboundTypeof_CSharp13() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + using System.Collections.Generic; + + class Test + { + public void M() + { + Console.WriteLine([|typeof(List<>.Enumerator).Name|]); + } + } + """, + LanguageVersion = LanguageVersion.CSharp13, + }.RunAsync(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/47129")] + public async Task NestedInGenericType_UnboundTypeof_CSharp14() + { + await new VerifyCS.Test + { + TestCode = """ + using System; + using System.Collections.Generic; + + class Test + { + public void M() + { + Console.WriteLine([|typeof(List<>.Enumerator).Name|]); + } + } + """, + FixedCode = """ + using System; + using System.Collections.Generic; + + class Test + { + public void M() + { + Console.WriteLine(nameof(List<>.Enumerator)); + } + } + """, + LanguageVersion = CSharp14, + }.RunAsync(); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/54233")] public async Task NotOnVoid() { diff --git a/src/Analyzers/Core/Analyzers/ConvertTypeofToNameof/AbstractConvertTypeOfToNameOfDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/ConvertTypeofToNameof/AbstractConvertTypeOfToNameOfDiagnosticAnalyzer.cs index eb64cc6165d2..3e7664ae6bbd 100644 --- a/src/Analyzers/Core/Analyzers/ConvertTypeofToNameof/AbstractConvertTypeOfToNameOfDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/ConvertTypeofToNameof/AbstractConvertTypeOfToNameOfDiagnosticAnalyzer.cs @@ -69,9 +69,11 @@ private bool IsValidOperation(IOperation operation) if (typeofOperation.TypeOperand is not INamedTypeSymbol namedType) return false; + // Non-generic types are always convertible. typeof(X).Name can always be converted to nameof(X) if (namedType.TypeArguments.Length == 0) return true; + // Generic types are convertible if the lang supports it. e.g. typeof(X).Name can be converted to nameof(X<>). return SupportsUnboundGenerics(operation.Syntax.SyntaxTree.Options); } }