diff --git a/CodeConverter/CSharp/ExpressionNodeVisitor.cs b/CodeConverter/CSharp/ExpressionNodeVisitor.cs index 159f65c9..e95dc0a6 100644 --- a/CodeConverter/CSharp/ExpressionNodeVisitor.cs +++ b/CodeConverter/CSharp/ExpressionNodeVisitor.cs @@ -578,8 +578,13 @@ public override async Task VisitArrayRankSpecifier(VBasic.Synt public override async Task VisitTypeArgumentList(VBasic.Syntax.TypeArgumentListSyntax node) { - var args = await node.Arguments.SelectAsync(async a => await a.AcceptAsync(TriviaConvertingExpressionVisitor)); - return CS.SyntaxFactory.TypeArgumentList(CS.SyntaxFactory.SeparatedList(args)); + var args = await node.Arguments.SelectAsync(async a => { + if (a is VBasic.Syntax.IdentifierNameSyntax id && id.Identifier.IsMissing) { + return CS.SyntaxFactory.OmittedTypeArgument(); + } + return await a.AcceptAsync(TriviaConvertingExpressionVisitor); + }); + return CS.SyntaxFactory.TypeArgumentList(CS.SyntaxFactory.SeparatedList(args)); } private async Task ConvertCastExpressionAsync(VBSyntax.CastExpressionSyntax node, diff --git a/CodeConverter/CSharp/VbNameExpander.cs b/CodeConverter/CSharp/VbNameExpander.cs index 7022e65a..d43e181a 100644 --- a/CodeConverter/CSharp/VbNameExpander.cs +++ b/CodeConverter/CSharp/VbNameExpander.cs @@ -110,6 +110,9 @@ private static bool NameCanBeExpanded(SyntaxNode node) if (node.Parent is NameColonEqualsSyntax || node.Parent is NamedFieldInitializerSyntax) return false; // Workaround roslyn bug where it duplicates the inferred name if (node.Parent is InferredFieldInitializerSyntax) return false; + // Roslyn's Simplifier.Expand corrupts open generic type arguments (e.g. Nullable(Of) in GetType(Nullable(Of))) + // by replacing them with the error type fallback (Object). Prevent expansion so the missing type arg is preserved. + if (node is GenericNameSyntax gns && gns.TypeArgumentList.Arguments.Any(a => a is IdentifierNameSyntax id && id.Identifier.IsMissing)) return false; return true; } diff --git a/Tests/CSharp/ExpressionTests/OmittedTypeArgumentTest.cs b/Tests/CSharp/ExpressionTests/OmittedTypeArgumentTest.cs new file mode 100644 index 00000000..0b64ef04 --- /dev/null +++ b/Tests/CSharp/ExpressionTests/OmittedTypeArgumentTest.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; +using ICSharpCode.CodeConverter.Tests.TestRunners; +using Xunit; + +namespace ICSharpCode.CodeConverter.Tests.CSharp.ExpressionTests; + +public class OmittedTypeArgumentTest : ConverterTestBase +{ + [Fact] + public async Task TestGetTypeOmittedArgument() + { + await TestConversionVisualBasicToCSharpAsync(@"Public Class Test + Public Function IsNullable(ByVal type As Type) As Boolean + Return type.IsGenericType AndAlso type.GetGenericTypeDefinition() Is GetType(Nullable(Of)) + End Function +End Class", @"using System; + +public partial class Test +{ + public bool IsNullable(Type @type) + { + return type.IsGenericType && ReferenceEquals(type.GetGenericTypeDefinition(), typeof(Nullable<>)); + } +}"); + } +}