From 4310d6efb7d18a89e08d8e765bc78f67d3c0c261 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 23:36:23 +0000 Subject: [PATCH 1/3] Fix GetType on open generic types falling back to typeof(object) VB open generics like `Nullable(Of )` generate `IErrorTypeSymbol` when using Roslyn's semantic model which previously forced `TypeConversionAnalyzer` and equality comparisons to fall back to `object` instead of preserving the syntax tree representing `typeof(X<>)`. This patch correctly yields `OmittedTypeArgumentSyntax` and bypasses the `System.Object` coercion fallback for equality testing. Co-authored-by: GrahamTheCoder <2490482+GrahamTheCoder@users.noreply.github.com> --- .../CSharp/BinaryExpressionConverter.cs | 15 ++++++++++- ...BuiltInVisualBasicOperatorSubstitutions.cs | 26 ++++++++++++++---- CodeConverter/CSharp/ExpressionNodeVisitor.cs | 9 +++++-- .../CSharp/NameExpressionNodeVisitor.cs | 12 ++++++++- .../CSharp/TypeConversionAnalyzer.cs | 9 +++++++ .../CSharp/VisualBasicEqualityComparison.cs | 18 ++++++++++++- .../OmittedTypeArgumentTest.cs | 27 +++++++++++++++++++ 7 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 Tests/CSharp/ExpressionTests/OmittedTypeArgumentTest.cs diff --git a/CodeConverter/CSharp/BinaryExpressionConverter.cs b/CodeConverter/CSharp/BinaryExpressionConverter.cs index f02c085f6..3d4729c13 100644 --- a/CodeConverter/CSharp/BinaryExpressionConverter.cs +++ b/CodeConverter/CSharp/BinaryExpressionConverter.cs @@ -1,4 +1,4 @@ -using ICSharpCode.CodeConverter.Util.FromRoslyn; +using ICSharpCode.CodeConverter.Util.FromRoslyn; namespace ICSharpCode.CodeConverter.CSharp; @@ -101,6 +101,19 @@ private async Task ConvertBinaryExpressionAsync(VBasic.Syntax. lhs = omitConversion ? lhs : CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Left, lhs, forceTargetType: forceLhsTargetType); rhs = omitConversion || omitRightConversion ? rhs : CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, rhs); + if (node.Right is VBSyntax.GetTypeExpressionSyntax getTypeExpr) { + var isUnboundGeneric = getTypeExpr.Type.DescendantNodesAndSelf().OfType().Any(t => t.Arguments.Any(a => a is VBSyntax.IdentifierNameSyntax id && id.Identifier.IsMissing)); + if (isUnboundGeneric) { + rhs = await node.Right.AcceptAsync(TriviaConvertingExpressionVisitor); + } + } + if (node.Left is VBSyntax.GetTypeExpressionSyntax getTypeExprLeft) { + var isUnboundGeneric = getTypeExprLeft.Type.DescendantNodesAndSelf().OfType().Any(t => t.Arguments.Any(a => a is VBSyntax.IdentifierNameSyntax id && id.Identifier.IsMissing)); + if (isUnboundGeneric) { + lhs = await node.Left.AcceptAsync(TriviaConvertingExpressionVisitor); + } + } + var kind = VBasic.VisualBasicExtensions.Kind(node).ConvertToken(); var op = CS.SyntaxFactory.Token(CSharpUtil.GetExpressionOperatorTokenKind(kind)); diff --git a/CodeConverter/CSharp/BuiltInVisualBasicOperatorSubstitutions.cs b/CodeConverter/CSharp/BuiltInVisualBasicOperatorSubstitutions.cs index c4e1765de..73958194a 100644 --- a/CodeConverter/CSharp/BuiltInVisualBasicOperatorSubstitutions.cs +++ b/CodeConverter/CSharp/BuiltInVisualBasicOperatorSubstitutions.cs @@ -1,4 +1,4 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.VisualBasic.CompilerServices; using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using SyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind; @@ -58,7 +58,7 @@ public async Task ConvertReferenceOrNothingComparisonOrNullAsy var equalityCheck = new KnownMethod(nameof(System), nameof(Object), nameof(object.ReferenceEquals)) .Invoke(_visualBasicEqualityComparison.ExtraUsingDirectives, - ConvertTo(node.Left, lhs, SpecialType.System_Object), rhs); + ConvertToIfNecessary(node.Left, lhs, SpecialType.System_Object), ConvertToIfNecessary(node.Right, rhs, SpecialType.System_Object)); return notted ? SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, equalityCheck) : equalityCheck; @@ -80,9 +80,14 @@ private static VBSyntax.ExpressionSyntax ArgComparedToNull(VBSyntax.BinaryExpres return null; } - private async Task ConvertIsOrIsNotExpressionArgAsync(VBSyntax.ExpressionSyntax binaryExpressionArg) => - await ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(binaryExpressionArg) - ?? await binaryExpressionArg.AcceptAsync(_triviaConvertingVisitor); + private async Task ConvertIsOrIsNotExpressionArgAsync(VBSyntax.ExpressionSyntax binaryExpressionArg) + { + if (binaryExpressionArg is VBSyntax.GetTypeExpressionSyntax getTypeExpr && getTypeExpr.Type.DescendantNodesAndSelf().OfType().Any(t => t.Arguments.Any(a => a is VBSyntax.IdentifierNameSyntax id && id.Identifier.IsMissing))) { + return await getTypeExpr.AcceptAsync(_triviaConvertingVisitor); + } + return await ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(binaryExpressionArg) + ?? await binaryExpressionArg.AcceptAsync(_triviaConvertingVisitor); + } private async Task ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(SyntaxNode node) { @@ -122,6 +127,17 @@ private ExpressionSyntax ConvertTo(VBSyntax.ExpressionSyntax vbNode, ExpressionS return _typeConversionAnalyzer.AddExplicitConversion(vbNode, csNode, forceTargetType: _semanticModel.Compilation.GetSpecialType(targetType)); } + private ExpressionSyntax ConvertToIfNecessary(VBSyntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, SpecialType targetType) + { + if (vbNode is VBSyntax.GetTypeExpressionSyntax getTypeExpr && getTypeExpr.Type.DescendantNodesAndSelf().OfType().Any(t => t.Arguments.Any(a => a is VBSyntax.IdentifierNameSyntax id && id.Identifier.IsMissing))) { + return csNode; + } + if (csNode is TypeOfExpressionSyntax typeOfExpr && typeOfExpr.Type is GenericNameSyntax gen && gen.TypeArgumentList.Arguments.Any(a => a is OmittedTypeArgumentSyntax)) { + return csNode; + } + return ConvertTo(vbNode, csNode, targetType); + } + /// No need to implement these since this is only called for things that are already decimal and hence will resolve operator in C# private static async Task ConvertToDecimalBinaryOperatorAsync(VBSyntax.BinaryExpressionSyntax node, KnownMethod member) => default; diff --git a/CodeConverter/CSharp/ExpressionNodeVisitor.cs b/CodeConverter/CSharp/ExpressionNodeVisitor.cs index 159f65c99..e95dc0a66 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/NameExpressionNodeVisitor.cs b/CodeConverter/CSharp/NameExpressionNodeVisitor.cs index 1b39251f5..1e92d4220 100644 --- a/CodeConverter/CSharp/NameExpressionNodeVisitor.cs +++ b/CodeConverter/CSharp/NameExpressionNodeVisitor.cs @@ -162,6 +162,10 @@ public async Task ConvertQualifiedNameAsync(VBasic.Syntax.Qual /// PERF: This is a hot code path, try to avoid using things like GetOperation except where needed. public async Task ConvertIdentifierNameAsync(VBasic.Syntax.IdentifierNameSyntax node) { + if (node.Identifier.IsMissing && node.Parent is VBasic.Syntax.TypeArgumentListSyntax) { + return SyntaxFactory.OmittedTypeArgument(); + } + var identifier = SyntaxFactory.IdentifierName(CommonConversions.ConvertIdentifier(node.Identifier, node.GetAncestor() != null)); bool requiresQualification = !node.Parent.IsKind(VBasic.SyntaxKind.SimpleMemberAccessExpression, VBasic.SyntaxKind.QualifiedName, VBasic.SyntaxKind.NameColonEquals, VBasic.SyntaxKind.ImportsStatement, VBasic.SyntaxKind.NamespaceStatement, VBasic.SyntaxKind.NamedFieldInitializer) || @@ -618,7 +622,13 @@ private ITypeSymbol[] GetOrNullAllTypeArgsIncludingInferred(IMethodSymbol vbMeth private async Task ConvertTypeArgumentListAsync(VBSyntax.GenericNameSyntax node) { - return await node.TypeArgumentList.AcceptAsync(TriviaConvertingExpressionVisitor); + var args = await node.TypeArgumentList.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 CSharpSyntaxNode AddEmptyArgumentListIfImplicit(SyntaxNode node, ExpressionSyntax id) diff --git a/CodeConverter/CSharp/TypeConversionAnalyzer.cs b/CodeConverter/CSharp/TypeConversionAnalyzer.cs index d1c3d3d24..d32731ec7 100644 --- a/CodeConverter/CSharp/TypeConversionAnalyzer.cs +++ b/CodeConverter/CSharp/TypeConversionAnalyzer.cs @@ -49,6 +49,9 @@ public TypeConversionAnalyzer(SemanticModel semanticModel, CSharpCompilation csC public ExpressionSyntax AddExplicitConversion(VBSyntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, bool addParenthesisIfNeeded = true, bool defaultToCast = false, bool isConst = false, ITypeSymbol forceSourceType = null, ITypeSymbol forceTargetType = null) { if (csNode == null) return null; + if (vbNode is VBSyntax.GetTypeExpressionSyntax getTypeExpr && getTypeExpr.Type.DescendantNodesAndSelf().OfType().Any(t => t.Arguments.Any(a => a is VBSyntax.IdentifierNameSyntax id && id.Identifier.IsMissing))) { + return csNode; + } var conversionKind = AnalyzeConversion(vbNode, defaultToCast, isConst, forceSourceType, forceTargetType); csNode = addParenthesisIfNeeded && conversionKind is TypeConversionKind.DestructiveCast or TypeConversionKind.NonDestructiveCast @@ -59,6 +62,9 @@ public ExpressionSyntax AddExplicitConversion(VBSyntax.ExpressionSyntax vbNode, public (ExpressionSyntax Expr, bool IsConst) AddExplicitConversion(VBSyntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, TypeConversionKind conversionKind, bool addParenthesisIfNeeded = false, bool requiresConst = false, ITypeSymbol forceSourceType = null, ITypeSymbol forceTargetType = null) { + if (vbNode is VBSyntax.GetTypeExpressionSyntax getTypeExpr2 && getTypeExpr2.Type.DescendantNodesAndSelf().OfType().Any(t => t.Arguments.Any(a => a is VBSyntax.IdentifierNameSyntax id && id.Identifier.IsMissing))) { + return (csNode, false); + } var (vbType, vbConvertedType) = GetTypeInfo(vbNode, forceSourceType, forceTargetType); bool resultConst = false; @@ -142,6 +148,9 @@ private ExpressionSyntax CreateCast(ExpressionSyntax csNode, ITypeSymbol vbConve public TypeConversionKind AnalyzeConversion(VBSyntax.ExpressionSyntax vbNode, bool alwaysExplicit = false, bool isConst = false, ITypeSymbol forceSourceType = null, ITypeSymbol forceTargetType = null) { + if (vbNode is VBSyntax.GetTypeExpressionSyntax getTypeExpr && getTypeExpr.Type.DescendantNodesAndSelf().OfType().Any(t => t.Arguments.Any(a => a is VBSyntax.IdentifierNameSyntax id && id.Identifier.IsMissing))) { + return TypeConversionKind.NonDestructiveCast; + } var (vbType, vbConvertedType) = GetTypeInfo(vbNode, forceSourceType, forceTargetType); if (vbConvertedType is null) diff --git a/CodeConverter/CSharp/VisualBasicEqualityComparison.cs b/CodeConverter/CSharp/VisualBasicEqualityComparison.cs index 6a70cf5cd..809af014b 100644 --- a/CodeConverter/CSharp/VisualBasicEqualityComparison.cs +++ b/CodeConverter/CSharp/VisualBasicEqualityComparison.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Globalization; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.VisualBasic.CompilerServices; @@ -77,6 +77,22 @@ public RequiredType GetObjectEqualityType(params TypeInfo[] typeInfos) { bool requiresVbEqualityCheck = typeInfos.Any(t => t.Type?.SpecialType == SpecialType.System_Object); + if (typeInfos.Any(t => t.Type?.TypeKind == Microsoft.CodeAnalysis.TypeKind.Error && t.ConvertedType?.TypeKind == Microsoft.CodeAnalysis.TypeKind.Error)) { + return RequiredType.None; + } + if (requiresVbEqualityCheck) { + if (typeInfos.Any(t => t.Type?.TypeKind == TypeKind.Error && t.ConvertedType?.SpecialType == SpecialType.None)) { + return RequiredType.None; + } + } + + if (requiresVbEqualityCheck) { + if (typeInfos.Any(t => t.Type?.TypeKind == TypeKind.Error && t.Type.Name == "Nullable")) { + // VB considers GetType(Nullable(Of)) as IErrorTypeSymbol and it falls back to object, avoid equality check + return RequiredType.None; + } + } + if (typeInfos.All( t => t.Type == null || t.Type.SpecialType == SpecialType.System_String || t.Type.IsArrayOf(SpecialType.System_Char) ) ) { diff --git a/Tests/CSharp/ExpressionTests/OmittedTypeArgumentTest.cs b/Tests/CSharp/ExpressionTests/OmittedTypeArgumentTest.cs new file mode 100644 index 000000000..b28775ceb --- /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 class Test +{ + public bool IsNullable(Type type) + { + return type.IsGenericType && ReferenceEquals(type.GetGenericTypeDefinition(), typeof(Nullable<>)); + } +}"); + } +} From 78a16107513361cb5e65280ceb9768892d8af4de Mon Sep 17 00:00:00 2001 From: Graham Date: Fri, 13 Mar 2026 00:42:16 +0000 Subject: [PATCH 2/3] Fix VbNameExpander corrupting open generic types like GetType(Nullable(Of)) Roslyn's Simplifier.Expand replaces missing type arguments (open generics like Nullable(Of)) with an error type fallback (Object), causing GetType(Nullable(Of)) to incorrectly convert to typeof(object) instead of typeof(Nullable<>). Guard against this in NameCanBeExpanded by detecting GenericNameSyntax nodes with missing type arguments and skipping expansion, preserving the open generic form so the conversion visitors can correctly emit OmittedTypeArgument. Co-Authored-By: Claude Sonnet 4.6 --- CodeConverter/CSharp/VbNameExpander.cs | 3 +++ Tests/CSharp/ExpressionTests/OmittedTypeArgumentTest.cs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CodeConverter/CSharp/VbNameExpander.cs b/CodeConverter/CSharp/VbNameExpander.cs index 7022e65a3..d43e181a9 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 index b28775ceb..0b64ef042 100644 --- a/Tests/CSharp/ExpressionTests/OmittedTypeArgumentTest.cs +++ b/Tests/CSharp/ExpressionTests/OmittedTypeArgumentTest.cs @@ -16,9 +16,9 @@ Return type.IsGenericType AndAlso type.GetGenericTypeDefinition() Is GetType(Nul End Function End Class", @"using System; -public class Test +public partial class Test { - public bool IsNullable(Type type) + public bool IsNullable(Type @type) { return type.IsGenericType && ReferenceEquals(type.GetGenericTypeDefinition(), typeof(Nullable<>)); } From 2bdaa9f2f1218981a1043e716a8260ed5318fa57 Mon Sep 17 00:00:00 2001 From: Graham Date: Fri, 13 Mar 2026 00:52:46 +0000 Subject: [PATCH 3/3] Simplify back down now we have the fix --- .../CSharp/BinaryExpressionConverter.cs | 15 +---------- ...BuiltInVisualBasicOperatorSubstitutions.cs | 26 ++++--------------- .../CSharp/NameExpressionNodeVisitor.cs | 12 +-------- .../CSharp/TypeConversionAnalyzer.cs | 9 ------- .../CSharp/VisualBasicEqualityComparison.cs | 18 +------------ 5 files changed, 8 insertions(+), 72 deletions(-) diff --git a/CodeConverter/CSharp/BinaryExpressionConverter.cs b/CodeConverter/CSharp/BinaryExpressionConverter.cs index 3d4729c13..f02c085f6 100644 --- a/CodeConverter/CSharp/BinaryExpressionConverter.cs +++ b/CodeConverter/CSharp/BinaryExpressionConverter.cs @@ -1,4 +1,4 @@ -using ICSharpCode.CodeConverter.Util.FromRoslyn; +using ICSharpCode.CodeConverter.Util.FromRoslyn; namespace ICSharpCode.CodeConverter.CSharp; @@ -101,19 +101,6 @@ private async Task ConvertBinaryExpressionAsync(VBasic.Syntax. lhs = omitConversion ? lhs : CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Left, lhs, forceTargetType: forceLhsTargetType); rhs = omitConversion || omitRightConversion ? rhs : CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Right, rhs); - if (node.Right is VBSyntax.GetTypeExpressionSyntax getTypeExpr) { - var isUnboundGeneric = getTypeExpr.Type.DescendantNodesAndSelf().OfType().Any(t => t.Arguments.Any(a => a is VBSyntax.IdentifierNameSyntax id && id.Identifier.IsMissing)); - if (isUnboundGeneric) { - rhs = await node.Right.AcceptAsync(TriviaConvertingExpressionVisitor); - } - } - if (node.Left is VBSyntax.GetTypeExpressionSyntax getTypeExprLeft) { - var isUnboundGeneric = getTypeExprLeft.Type.DescendantNodesAndSelf().OfType().Any(t => t.Arguments.Any(a => a is VBSyntax.IdentifierNameSyntax id && id.Identifier.IsMissing)); - if (isUnboundGeneric) { - lhs = await node.Left.AcceptAsync(TriviaConvertingExpressionVisitor); - } - } - var kind = VBasic.VisualBasicExtensions.Kind(node).ConvertToken(); var op = CS.SyntaxFactory.Token(CSharpUtil.GetExpressionOperatorTokenKind(kind)); diff --git a/CodeConverter/CSharp/BuiltInVisualBasicOperatorSubstitutions.cs b/CodeConverter/CSharp/BuiltInVisualBasicOperatorSubstitutions.cs index 73958194a..c4e1765de 100644 --- a/CodeConverter/CSharp/BuiltInVisualBasicOperatorSubstitutions.cs +++ b/CodeConverter/CSharp/BuiltInVisualBasicOperatorSubstitutions.cs @@ -1,4 +1,4 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.VisualBasic.CompilerServices; using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using SyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind; @@ -58,7 +58,7 @@ public async Task ConvertReferenceOrNothingComparisonOrNullAsy var equalityCheck = new KnownMethod(nameof(System), nameof(Object), nameof(object.ReferenceEquals)) .Invoke(_visualBasicEqualityComparison.ExtraUsingDirectives, - ConvertToIfNecessary(node.Left, lhs, SpecialType.System_Object), ConvertToIfNecessary(node.Right, rhs, SpecialType.System_Object)); + ConvertTo(node.Left, lhs, SpecialType.System_Object), rhs); return notted ? SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, equalityCheck) : equalityCheck; @@ -80,14 +80,9 @@ private static VBSyntax.ExpressionSyntax ArgComparedToNull(VBSyntax.BinaryExpres return null; } - private async Task ConvertIsOrIsNotExpressionArgAsync(VBSyntax.ExpressionSyntax binaryExpressionArg) - { - if (binaryExpressionArg is VBSyntax.GetTypeExpressionSyntax getTypeExpr && getTypeExpr.Type.DescendantNodesAndSelf().OfType().Any(t => t.Arguments.Any(a => a is VBSyntax.IdentifierNameSyntax id && id.Identifier.IsMissing))) { - return await getTypeExpr.AcceptAsync(_triviaConvertingVisitor); - } - return await ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(binaryExpressionArg) - ?? await binaryExpressionArg.AcceptAsync(_triviaConvertingVisitor); - } + private async Task ConvertIsOrIsNotExpressionArgAsync(VBSyntax.ExpressionSyntax binaryExpressionArg) => + await ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(binaryExpressionArg) + ?? await binaryExpressionArg.AcceptAsync(_triviaConvertingVisitor); private async Task ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(SyntaxNode node) { @@ -127,17 +122,6 @@ private ExpressionSyntax ConvertTo(VBSyntax.ExpressionSyntax vbNode, ExpressionS return _typeConversionAnalyzer.AddExplicitConversion(vbNode, csNode, forceTargetType: _semanticModel.Compilation.GetSpecialType(targetType)); } - private ExpressionSyntax ConvertToIfNecessary(VBSyntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, SpecialType targetType) - { - if (vbNode is VBSyntax.GetTypeExpressionSyntax getTypeExpr && getTypeExpr.Type.DescendantNodesAndSelf().OfType().Any(t => t.Arguments.Any(a => a is VBSyntax.IdentifierNameSyntax id && id.Identifier.IsMissing))) { - return csNode; - } - if (csNode is TypeOfExpressionSyntax typeOfExpr && typeOfExpr.Type is GenericNameSyntax gen && gen.TypeArgumentList.Arguments.Any(a => a is OmittedTypeArgumentSyntax)) { - return csNode; - } - return ConvertTo(vbNode, csNode, targetType); - } - /// No need to implement these since this is only called for things that are already decimal and hence will resolve operator in C# private static async Task ConvertToDecimalBinaryOperatorAsync(VBSyntax.BinaryExpressionSyntax node, KnownMethod member) => default; diff --git a/CodeConverter/CSharp/NameExpressionNodeVisitor.cs b/CodeConverter/CSharp/NameExpressionNodeVisitor.cs index 1e92d4220..1b39251f5 100644 --- a/CodeConverter/CSharp/NameExpressionNodeVisitor.cs +++ b/CodeConverter/CSharp/NameExpressionNodeVisitor.cs @@ -162,10 +162,6 @@ public async Task ConvertQualifiedNameAsync(VBasic.Syntax.Qual /// PERF: This is a hot code path, try to avoid using things like GetOperation except where needed. public async Task ConvertIdentifierNameAsync(VBasic.Syntax.IdentifierNameSyntax node) { - if (node.Identifier.IsMissing && node.Parent is VBasic.Syntax.TypeArgumentListSyntax) { - return SyntaxFactory.OmittedTypeArgument(); - } - var identifier = SyntaxFactory.IdentifierName(CommonConversions.ConvertIdentifier(node.Identifier, node.GetAncestor() != null)); bool requiresQualification = !node.Parent.IsKind(VBasic.SyntaxKind.SimpleMemberAccessExpression, VBasic.SyntaxKind.QualifiedName, VBasic.SyntaxKind.NameColonEquals, VBasic.SyntaxKind.ImportsStatement, VBasic.SyntaxKind.NamespaceStatement, VBasic.SyntaxKind.NamedFieldInitializer) || @@ -622,13 +618,7 @@ private ITypeSymbol[] GetOrNullAllTypeArgsIncludingInferred(IMethodSymbol vbMeth private async Task ConvertTypeArgumentListAsync(VBSyntax.GenericNameSyntax node) { - var args = await node.TypeArgumentList.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)); + return await node.TypeArgumentList.AcceptAsync(TriviaConvertingExpressionVisitor); } private CSharpSyntaxNode AddEmptyArgumentListIfImplicit(SyntaxNode node, ExpressionSyntax id) diff --git a/CodeConverter/CSharp/TypeConversionAnalyzer.cs b/CodeConverter/CSharp/TypeConversionAnalyzer.cs index d32731ec7..d1c3d3d24 100644 --- a/CodeConverter/CSharp/TypeConversionAnalyzer.cs +++ b/CodeConverter/CSharp/TypeConversionAnalyzer.cs @@ -49,9 +49,6 @@ public TypeConversionAnalyzer(SemanticModel semanticModel, CSharpCompilation csC public ExpressionSyntax AddExplicitConversion(VBSyntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, bool addParenthesisIfNeeded = true, bool defaultToCast = false, bool isConst = false, ITypeSymbol forceSourceType = null, ITypeSymbol forceTargetType = null) { if (csNode == null) return null; - if (vbNode is VBSyntax.GetTypeExpressionSyntax getTypeExpr && getTypeExpr.Type.DescendantNodesAndSelf().OfType().Any(t => t.Arguments.Any(a => a is VBSyntax.IdentifierNameSyntax id && id.Identifier.IsMissing))) { - return csNode; - } var conversionKind = AnalyzeConversion(vbNode, defaultToCast, isConst, forceSourceType, forceTargetType); csNode = addParenthesisIfNeeded && conversionKind is TypeConversionKind.DestructiveCast or TypeConversionKind.NonDestructiveCast @@ -62,9 +59,6 @@ public ExpressionSyntax AddExplicitConversion(VBSyntax.ExpressionSyntax vbNode, public (ExpressionSyntax Expr, bool IsConst) AddExplicitConversion(VBSyntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, TypeConversionKind conversionKind, bool addParenthesisIfNeeded = false, bool requiresConst = false, ITypeSymbol forceSourceType = null, ITypeSymbol forceTargetType = null) { - if (vbNode is VBSyntax.GetTypeExpressionSyntax getTypeExpr2 && getTypeExpr2.Type.DescendantNodesAndSelf().OfType().Any(t => t.Arguments.Any(a => a is VBSyntax.IdentifierNameSyntax id && id.Identifier.IsMissing))) { - return (csNode, false); - } var (vbType, vbConvertedType) = GetTypeInfo(vbNode, forceSourceType, forceTargetType); bool resultConst = false; @@ -148,9 +142,6 @@ private ExpressionSyntax CreateCast(ExpressionSyntax csNode, ITypeSymbol vbConve public TypeConversionKind AnalyzeConversion(VBSyntax.ExpressionSyntax vbNode, bool alwaysExplicit = false, bool isConst = false, ITypeSymbol forceSourceType = null, ITypeSymbol forceTargetType = null) { - if (vbNode is VBSyntax.GetTypeExpressionSyntax getTypeExpr && getTypeExpr.Type.DescendantNodesAndSelf().OfType().Any(t => t.Arguments.Any(a => a is VBSyntax.IdentifierNameSyntax id && id.Identifier.IsMissing))) { - return TypeConversionKind.NonDestructiveCast; - } var (vbType, vbConvertedType) = GetTypeInfo(vbNode, forceSourceType, forceTargetType); if (vbConvertedType is null) diff --git a/CodeConverter/CSharp/VisualBasicEqualityComparison.cs b/CodeConverter/CSharp/VisualBasicEqualityComparison.cs index 809af014b..6a70cf5cd 100644 --- a/CodeConverter/CSharp/VisualBasicEqualityComparison.cs +++ b/CodeConverter/CSharp/VisualBasicEqualityComparison.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System.Globalization; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.VisualBasic.CompilerServices; @@ -77,22 +77,6 @@ public RequiredType GetObjectEqualityType(params TypeInfo[] typeInfos) { bool requiresVbEqualityCheck = typeInfos.Any(t => t.Type?.SpecialType == SpecialType.System_Object); - if (typeInfos.Any(t => t.Type?.TypeKind == Microsoft.CodeAnalysis.TypeKind.Error && t.ConvertedType?.TypeKind == Microsoft.CodeAnalysis.TypeKind.Error)) { - return RequiredType.None; - } - if (requiresVbEqualityCheck) { - if (typeInfos.Any(t => t.Type?.TypeKind == TypeKind.Error && t.ConvertedType?.SpecialType == SpecialType.None)) { - return RequiredType.None; - } - } - - if (requiresVbEqualityCheck) { - if (typeInfos.Any(t => t.Type?.TypeKind == TypeKind.Error && t.Type.Name == "Nullable")) { - // VB considers GetType(Nullable(Of)) as IErrorTypeSymbol and it falls back to object, avoid equality check - return RequiredType.None; - } - } - if (typeInfos.All( t => t.Type == null || t.Type.SpecialType == SpecialType.System_String || t.Type.IsArrayOf(SpecialType.System_Char) ) ) {