diff --git a/CodeConverter/CSharp/CommonConversions.cs b/CodeConverter/CSharp/CommonConversions.cs index 4402d5a0..383c5260 100644 --- a/CodeConverter/CSharp/CommonConversions.cs +++ b/CodeConverter/CSharp/CommonConversions.cs @@ -20,7 +20,6 @@ namespace ICSharpCode.CodeConverter.CSharp; internal class CommonConversions { - public ITypeSymbol System_Linq_Expressions_Expression_T { get; } private static readonly Type ExtensionAttributeType = typeof(ExtensionAttribute); public Document Document { get; } public SemanticModel SemanticModel { get; } @@ -49,9 +48,11 @@ public CommonConversions(Document document, SemanticModel semanticModel, _typeContext = typeContext; VisualBasicEqualityComparison = visualBasicEqualityComparison; WinformsConversions = new WinformsConversions(typeContext); - System_Linq_Expressions_Expression_T = semanticModel.Compilation.GetTypeByMetadataName("System.Linq.Expressions.Expression`1"); + KnownTypes = new KnownNamedTypes(semanticModel); } + public KnownNamedTypes KnownTypes { get; } + public record VariablePair(CSSyntax.VariableDeclaratorSyntax CsVar, VBSyntax.ModifiedIdentifierSyntax VbVar); public record VariablesDeclaration(CSSyntax.VariableDeclarationSyntax Decl, ITypeSymbol Type, List Variables); @@ -756,5 +757,5 @@ public bool IsLinqDelegateExpression(VisualBasicSyntaxNode node) return false; } - private bool IsLinqDelegateExpression(ITypeSymbol convertedType) => System_Linq_Expressions_Expression_T?.Equals(convertedType?.OriginalDefinition, SymbolEqualityComparer.Default) == true; + private bool IsLinqDelegateExpression(ITypeSymbol convertedType) =>KnownTypes.System_Linq_Expressions_Expression_T?.Equals(convertedType?.OriginalDefinition, SymbolEqualityComparer.Default) == true; } \ No newline at end of file diff --git a/CodeConverter/CSharp/ExpressionNodeVisitor.cs b/CodeConverter/CSharp/ExpressionNodeVisitor.cs index 9af8d45f..fe4bc084 100644 --- a/CodeConverter/CSharp/ExpressionNodeVisitor.cs +++ b/CodeConverter/CSharp/ExpressionNodeVisitor.cs @@ -35,7 +35,6 @@ internal class ExpressionNodeVisitor : VBasic.VisualBasicSyntaxVisitor> _convertMethodsLookupByReturnType; private readonly LambdaConverter _lambdaConverter; - private readonly INamedTypeSymbol _vbBooleanTypeSymbol; private readonly VisualBasicNullableExpressionsConverter _visualBasicNullableTypesConverter; private readonly Dictionary> _tempNameForAnonymousScope = new(); private readonly HashSet _generatedNames = new(StringComparer.OrdinalIgnoreCase); @@ -58,7 +57,6 @@ public ExpressionNodeVisitor(SemanticModel semanticModel, // If this isn't needed, the assembly with Conversions may not be referenced, so this must be done lazily _convertMethodsLookupByReturnType = new Lazy>(() => CreateConvertMethodsLookupByReturnType(semanticModel)); - _vbBooleanTypeSymbol = _semanticModel.Compilation.GetTypeByMetadataName("System.Boolean"); } private static IReadOnlyDictionary CreateConvertMethodsLookupByReturnType( @@ -775,7 +773,7 @@ public override async Task VisitBinaryConditionalExpression(VB public override async Task VisitTernaryConditionalExpression(VBasic.Syntax.TernaryConditionalExpressionSyntax node) { var condition = await node.Condition.AcceptAsync(TriviaConvertingExpressionVisitor); - condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Condition, condition, forceTargetType: _vbBooleanTypeSymbol); + condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Condition, condition, forceTargetType: CommonConversions.KnownTypes.Boolean); var whenTrue = await node.WhenTrue.AcceptAsync(TriviaConvertingExpressionVisitor); whenTrue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.WhenTrue, whenTrue); @@ -900,7 +898,7 @@ private async Task ConvertBinaryExpressionAsync(VBasic.Syntax. omitConversion = lhsTypeInfo.Type.SpecialType == SpecialType.System_String || rhsTypeInfo.Type.SpecialType == SpecialType.System_String; if (lhsTypeInfo.ConvertedType.SpecialType != SpecialType.System_String) { - forceLhsTargetType = _semanticModel.Compilation.GetTypeByMetadataName("System.String"); + forceLhsTargetType = CommonConversions.KnownTypes.String; } } } @@ -938,6 +936,8 @@ private async Task ConvertBinaryExpressionAsync(VBasic.Syntax. return node.Parent.IsKind(VBasic.SyntaxKind.SimpleArgument) ? exp : exp.AddParens(); } + + private async Task RewriteBinaryOperatorOrNullAsync(VBSyntax.BinaryExpressionSyntax node) => await _operatorConverter.ConvertRewrittenBinaryOperatorOrNullAsync(node, TriviaConvertingExpressionVisitor.IsWithinQuery); @@ -1818,8 +1818,54 @@ private ArgumentSyntax CreateExtraArgOrNull(IParameterSymbol p, bool requiresCom private ArgumentSyntax CreateOptionalRefArg(IParameterSymbol p, RefKind refKind) { string prefix = $"arg{p.Name}"; - var local = _typeContext.PerScopeState.Hoist(new AdditionalDeclaration(prefix, CommonConversions.Literal(p.ExplicitDefaultValue), CommonConversions.GetTypeSyntax(p.Type))); + var type = CommonConversions.GetTypeSyntax(p.Type); + ExpressionSyntax initializer; + if (p.HasExplicitDefaultValue) { + initializer = CommonConversions.Literal(p.ExplicitDefaultValue); + } else if (HasOptionalAttribute(p)) { + if (TryGetDefaultParameterValueAttributeValue(p, out var defaultValue)){ + initializer = CommonConversions.Literal(defaultValue); + } else { + initializer = SyntaxFactory.DefaultExpression(type); + } + } else { + //invalid VB.NET code + return null; + } + var local = _typeContext.PerScopeState.Hoist(new AdditionalDeclaration(prefix, initializer, type)); return (ArgumentSyntax)CommonConversions.CsSyntaxGenerator.Argument(p.Name, refKind, local.IdentifierName); + + bool HasOptionalAttribute(IParameterSymbol p) + { + var optionalAttribute = CommonConversions.KnownTypes.OptionalAttribute; + if (optionalAttribute == null) { + return false; + } + + return p.GetAttributes().Any(a => SymbolEqualityComparer.IncludeNullability.Equals(a.AttributeClass, optionalAttribute)); + } + + bool TryGetDefaultParameterValueAttributeValue(IParameterSymbol p, out object defaultValue) + { + defaultValue = null; + + var defaultParameterValueAttribute = CommonConversions.KnownTypes.DefaultParameterValueAttribute; + if (defaultParameterValueAttribute == null) { + return false; + } + + var attributeData = p.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.IncludeNullability.Equals(a.AttributeClass, defaultParameterValueAttribute)); + if (attributeData == null) { + return false; + } + + if (attributeData.ConstructorArguments.Length == 0) { + return false; + } + + defaultValue = attributeData.ConstructorArguments.First().Value; + return true; + } } private RefConversion NeedsVariableForArgument(VBasic.Syntax.ArgumentSyntax node, RefKind refKind) @@ -1898,7 +1944,7 @@ private ISymbol GetInvocationSymbol(SyntaxNode invocation) (VBSyntax.InvocationExpressionSyntax e) => _semanticModel.GetSymbolInfo(e).ExtractBestMatch(), (VBSyntax.ObjectCreationExpressionSyntax e) => _semanticModel.GetSymbolInfo(e).ExtractBestMatch(), (VBSyntax.RaiseEventStatementSyntax e) => _semanticModel.GetSymbolInfo(e.Name).ExtractBestMatch(), - (VBSyntax.MidExpressionSyntax _) => _semanticModel.Compilation.GetTypeByMetadataName("Microsoft.VisualBasic.CompilerServices.StringType")?.GetMembers("MidStmtStr").FirstOrDefault(), + (VBSyntax.MidExpressionSyntax _) => CommonConversions.KnownTypes.VbCompilerStringType?.GetMembers("MidStmtStr").FirstOrDefault(), _ => throw new NotSupportedException()); return symbol; } diff --git a/CodeConverter/CSharp/KnownNamedTypes.cs b/CodeConverter/CSharp/KnownNamedTypes.cs new file mode 100644 index 00000000..3da05918 --- /dev/null +++ b/CodeConverter/CSharp/KnownNamedTypes.cs @@ -0,0 +1,22 @@ +namespace ICSharpCode.CodeConverter.CSharp; + +internal class KnownNamedTypes +{ + public KnownNamedTypes(SemanticModel semanticModel) + { + Boolean = semanticModel.Compilation.GetTypeByMetadataName("System.Boolean"); + String = semanticModel.Compilation.GetTypeByMetadataName("System.String"); + DefaultParameterValueAttribute = semanticModel.Compilation.GetTypeByMetadataName("System.Runtime.InteropServices.DefaultParameterValueAttribute"); + OptionalAttribute = semanticModel.Compilation.GetTypeByMetadataName("System.Runtime.InteropServices.OptionalAttribute"); + System_Linq_Expressions_Expression_T = semanticModel.Compilation.GetTypeByMetadataName("System.Linq.Expressions.Expression`1"); + VbCompilerStringType = semanticModel.Compilation.GetTypeByMetadataName("Microsoft.VisualBasic.CompilerServices.StringType"); + } + + public INamedTypeSymbol System_Linq_Expressions_Expression_T { get; set; } + + public INamedTypeSymbol Boolean { get; } + public INamedTypeSymbol String { get; } + public INamedTypeSymbol DefaultParameterValueAttribute { get; } + public INamedTypeSymbol OptionalAttribute { get; } + public INamedTypeSymbol VbCompilerStringType { get; } +} \ No newline at end of file diff --git a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs index a88510c7..9c23f6c6 100644 --- a/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs +++ b/CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs @@ -24,7 +24,6 @@ internal class MethodBodyExecutableStatementVisitor : VBasic.VisualBasicSyntaxVi private readonly HashSet _extraUsingDirectives; private readonly HandledEventsAnalysis _handledEventsAnalysis; private readonly HashSet _generatedNames = new(); - private readonly INamedTypeSymbol _vbBooleanTypeSymbol; private readonly HashSet _localsToInlineInLoop; private readonly PerScopeState _perScopeState; @@ -65,7 +64,6 @@ private MethodBodyExecutableStatementVisitor(VisualBasicSyntaxNode methodNode, S _perScopeState = typeContext.PerScopeState; var byRefParameterVisitor = new PerScopeStateVisitorDecorator(this, _perScopeState, semanticModel, _generatedNames); CommentConvertingVisitor = new CommentConvertingMethodBodyVisitor(byRefParameterVisitor); - _vbBooleanTypeSymbol = _semanticModel.Compilation.GetTypeByMetadataName("System.Boolean"); _localsToInlineInLoop = localsToInlineInLoop; } @@ -520,7 +518,7 @@ await node.Name.AcceptAsync(_expressionVisitor), public override async Task> VisitSingleLineIfStatement(VBSyntax.SingleLineIfStatementSyntax node) { var condition = await node.Condition.AcceptAsync(_expressionVisitor); - condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Condition, condition, forceTargetType: _vbBooleanTypeSymbol); + condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Condition, condition, forceTargetType: CommonConversions.KnownTypes.Boolean); var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); ElseClauseSyntax elseClause = null; @@ -534,7 +532,7 @@ public override async Task> VisitSingleLineIfStateme public override async Task> VisitMultiLineIfBlock(VBSyntax.MultiLineIfBlockSyntax node) { var condition = await node.IfStatement.Condition.AcceptAsync(_expressionVisitor); - condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.IfStatement.Condition, condition, forceTargetType: _vbBooleanTypeSymbol); + condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.IfStatement.Condition, condition, forceTargetType: CommonConversions.KnownTypes.Boolean); var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements)); var elseClause = await ConvertElseClauseAsync(node.ElseBlock); @@ -553,7 +551,7 @@ public override async Task> VisitMultiLineIfBlock(VB { var elseBlock = SyntaxFactory.Block(await ConvertStatementsAsync(elseIf.Statements)); var elseIfCondition = await elseIf.ElseIfStatement.Condition.AcceptAsync(_expressionVisitor); - elseIfCondition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(elseIf.ElseIfStatement.Condition, elseIfCondition, forceTargetType: _vbBooleanTypeSymbol); + elseIfCondition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(elseIf.ElseIfStatement.Condition, elseIfCondition, forceTargetType: CommonConversions.KnownTypes.Boolean); return (elseIfCondition, elseBlock); } diff --git a/Tests/CSharp/MemberTests/MemberTests.cs b/Tests/CSharp/MemberTests/MemberTests.cs index bbb4389c..3fae09a2 100644 --- a/Tests/CSharp/MemberTests/MemberTests.cs +++ b/Tests/CSharp/MemberTests/MemberTests.cs @@ -4051,36 +4051,50 @@ public static int StaticTestProperty } [Fact] - public async Task TestRefConstArgumentAsync() + public async Task TestMissingByRefArgumentWithNoExplicitDefaultValueAsync() { await TestConversionVisualBasicToCSharpAsync( - @"Class RefConstArgument - Const a As String = ""a"" + @"Imports System.Runtime.InteropServices + +Class MissingByRefArgumentWithNoExplicitDefaultValue Sub S() - Const b As String = ""b"" - MO(a) - MS(b) + ByRefNoDefault() + OptionalByRefNoDefault() + OptionalByRefWithDefault() End Sub - Sub MO(ByRef s As Object) : End Sub - Sub MS(ByRef s As String) : End Sub -End Class", @" -internal partial class RefConstArgument + + Private Sub ByRefNoDefault(ByRef str1 As String) : End Sub + Private Sub OptionalByRefNoDefault(<[Optional]> ByRef str2 As String) : End Sub + Private Sub OptionalByRefWithDefault(<[Optional], DefaultParameterValue(""a"")> ByRef str3 As String) : End Sub +End Class", @"using System.Runtime.InteropServices; + +internal partial class MissingByRefArgumentWithNoExplicitDefaultValue { - private const string a = ""a""; public void S() { - const string b = ""b""; - object args = a; - MO(ref args); - string args1 = b; - MS(ref args1); + ByRefNoDefault(); + string argstr2 = default; + OptionalByRefNoDefault(str2: ref argstr2); + string argstr3 = ""a""; + OptionalByRefWithDefault(str3: ref argstr3); + } + + private void ByRefNoDefault(ref string str1) + { } - public void MO(ref object s) + private void OptionalByRefNoDefault([Optional] ref string str2) { } - public void MS(ref string s) + private void OptionalByRefWithDefault([Optional][DefaultParameterValue(""a"")] ref string str3) { } -}"); +} +3 source compilation errors: +BC30455: Argument not specified for parameter 'str1' of 'Private Sub ByRefNoDefault(ByRef str1 As String)'. +BC30455: Argument not specified for parameter 'str2' of 'Private Sub OptionalByRefNoDefault(ByRef str2 As String)'. +BC30455: Argument not specified for parameter 'str3' of 'Private Sub OptionalByRefWithDefault(ByRef str3 As String)'. +1 target compilation errors: +CS7036: There is no argument given that corresponds to the required formal parameter 'str1' of 'MissingByRefArgumentWithNoExplicitDefaultValue.ByRefNoDefault(ref string)' +"); } } \ No newline at end of file