Skip to content

Commit 0658a1b

Browse files
Merge pull request #1122 from TymurGubayev/fix/MissingByRefArgument/1
take parameter attributes into account in `CreateOptionalRefArg`
2 parents cf2d4aa + 96bfdb7 commit 0658a1b

5 files changed

Lines changed: 114 additions & 33 deletions

File tree

CodeConverter/CSharp/CommonConversions.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ namespace ICSharpCode.CodeConverter.CSharp;
2020

2121
internal class CommonConversions
2222
{
23-
public ITypeSymbol System_Linq_Expressions_Expression_T { get; }
2423
private static readonly Type ExtensionAttributeType = typeof(ExtensionAttribute);
2524
public Document Document { get; }
2625
public SemanticModel SemanticModel { get; }
@@ -49,9 +48,11 @@ public CommonConversions(Document document, SemanticModel semanticModel,
4948
_typeContext = typeContext;
5049
VisualBasicEqualityComparison = visualBasicEqualityComparison;
5150
WinformsConversions = new WinformsConversions(typeContext);
52-
System_Linq_Expressions_Expression_T = semanticModel.Compilation.GetTypeByMetadataName("System.Linq.Expressions.Expression`1");
51+
KnownTypes = new KnownNamedTypes(semanticModel);
5352
}
5453

54+
public KnownNamedTypes KnownTypes { get; }
55+
5556
public record VariablePair(CSSyntax.VariableDeclaratorSyntax CsVar, VBSyntax.ModifiedIdentifierSyntax VbVar);
5657
public record VariablesDeclaration(CSSyntax.VariableDeclarationSyntax Decl, ITypeSymbol Type, List<VariablePair> Variables);
5758

@@ -756,5 +757,5 @@ public bool IsLinqDelegateExpression(VisualBasicSyntaxNode node)
756757
return false;
757758
}
758759

759-
private bool IsLinqDelegateExpression(ITypeSymbol convertedType) => System_Linq_Expressions_Expression_T?.Equals(convertedType?.OriginalDefinition, SymbolEqualityComparer.Default) == true;
760+
private bool IsLinqDelegateExpression(ITypeSymbol convertedType) =>KnownTypes.System_Linq_Expressions_Expression_T?.Equals(convertedType?.OriginalDefinition, SymbolEqualityComparer.Default) == true;
760761
}

CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ internal class ExpressionNodeVisitor : VBasic.VisualBasicSyntaxVisitor<Task<CSha
3535
private readonly QueryConverter _queryConverter;
3636
private readonly Lazy<IReadOnlyDictionary<ITypeSymbol, string>> _convertMethodsLookupByReturnType;
3737
private readonly LambdaConverter _lambdaConverter;
38-
private readonly INamedTypeSymbol _vbBooleanTypeSymbol;
3938
private readonly VisualBasicNullableExpressionsConverter _visualBasicNullableTypesConverter;
4039
private readonly Dictionary<string, Stack<(SyntaxNode Scope, string TempName)>> _tempNameForAnonymousScope = new();
4140
private readonly HashSet<string> _generatedNames = new(StringComparer.OrdinalIgnoreCase);
@@ -58,7 +57,6 @@ public ExpressionNodeVisitor(SemanticModel semanticModel,
5857
// If this isn't needed, the assembly with Conversions may not be referenced, so this must be done lazily
5958
_convertMethodsLookupByReturnType =
6059
new Lazy<IReadOnlyDictionary<ITypeSymbol, string>>(() => CreateConvertMethodsLookupByReturnType(semanticModel));
61-
_vbBooleanTypeSymbol = _semanticModel.Compilation.GetTypeByMetadataName("System.Boolean");
6260
}
6361

6462
private static IReadOnlyDictionary<ITypeSymbol, string> CreateConvertMethodsLookupByReturnType(
@@ -775,7 +773,7 @@ public override async Task<CSharpSyntaxNode> VisitBinaryConditionalExpression(VB
775773
public override async Task<CSharpSyntaxNode> VisitTernaryConditionalExpression(VBasic.Syntax.TernaryConditionalExpressionSyntax node)
776774
{
777775
var condition = await node.Condition.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor);
778-
condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Condition, condition, forceTargetType: _vbBooleanTypeSymbol);
776+
condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Condition, condition, forceTargetType: CommonConversions.KnownTypes.Boolean);
779777

780778
var whenTrue = await node.WhenTrue.AcceptAsync<ExpressionSyntax>(TriviaConvertingExpressionVisitor);
781779
whenTrue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.WhenTrue, whenTrue);
@@ -900,7 +898,7 @@ private async Task<CSharpSyntaxNode> ConvertBinaryExpressionAsync(VBasic.Syntax.
900898
omitConversion = lhsTypeInfo.Type.SpecialType == SpecialType.System_String ||
901899
rhsTypeInfo.Type.SpecialType == SpecialType.System_String;
902900
if (lhsTypeInfo.ConvertedType.SpecialType != SpecialType.System_String) {
903-
forceLhsTargetType = _semanticModel.Compilation.GetTypeByMetadataName("System.String");
901+
forceLhsTargetType = CommonConversions.KnownTypes.String;
904902
}
905903
}
906904
}
@@ -938,6 +936,8 @@ private async Task<CSharpSyntaxNode> ConvertBinaryExpressionAsync(VBasic.Syntax.
938936
return node.Parent.IsKind(VBasic.SyntaxKind.SimpleArgument) ? exp : exp.AddParens();
939937
}
940938

939+
940+
941941
private async Task<ExpressionSyntax> RewriteBinaryOperatorOrNullAsync(VBSyntax.BinaryExpressionSyntax node) =>
942942
await _operatorConverter.ConvertRewrittenBinaryOperatorOrNullAsync(node, TriviaConvertingExpressionVisitor.IsWithinQuery);
943943

@@ -1818,8 +1818,54 @@ private ArgumentSyntax CreateExtraArgOrNull(IParameterSymbol p, bool requiresCom
18181818
private ArgumentSyntax CreateOptionalRefArg(IParameterSymbol p, RefKind refKind)
18191819
{
18201820
string prefix = $"arg{p.Name}";
1821-
var local = _typeContext.PerScopeState.Hoist(new AdditionalDeclaration(prefix, CommonConversions.Literal(p.ExplicitDefaultValue), CommonConversions.GetTypeSyntax(p.Type)));
1821+
var type = CommonConversions.GetTypeSyntax(p.Type);
1822+
ExpressionSyntax initializer;
1823+
if (p.HasExplicitDefaultValue) {
1824+
initializer = CommonConversions.Literal(p.ExplicitDefaultValue);
1825+
} else if (HasOptionalAttribute(p)) {
1826+
if (TryGetDefaultParameterValueAttributeValue(p, out var defaultValue)){
1827+
initializer = CommonConversions.Literal(defaultValue);
1828+
} else {
1829+
initializer = SyntaxFactory.DefaultExpression(type);
1830+
}
1831+
} else {
1832+
//invalid VB.NET code
1833+
return null;
1834+
}
1835+
var local = _typeContext.PerScopeState.Hoist(new AdditionalDeclaration(prefix, initializer, type));
18221836
return (ArgumentSyntax)CommonConversions.CsSyntaxGenerator.Argument(p.Name, refKind, local.IdentifierName);
1837+
1838+
bool HasOptionalAttribute(IParameterSymbol p)
1839+
{
1840+
var optionalAttribute = CommonConversions.KnownTypes.OptionalAttribute;
1841+
if (optionalAttribute == null) {
1842+
return false;
1843+
}
1844+
1845+
return p.GetAttributes().Any(a => SymbolEqualityComparer.IncludeNullability.Equals(a.AttributeClass, optionalAttribute));
1846+
}
1847+
1848+
bool TryGetDefaultParameterValueAttributeValue(IParameterSymbol p, out object defaultValue)
1849+
{
1850+
defaultValue = null;
1851+
1852+
var defaultParameterValueAttribute = CommonConversions.KnownTypes.DefaultParameterValueAttribute;
1853+
if (defaultParameterValueAttribute == null) {
1854+
return false;
1855+
}
1856+
1857+
var attributeData = p.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.IncludeNullability.Equals(a.AttributeClass, defaultParameterValueAttribute));
1858+
if (attributeData == null) {
1859+
return false;
1860+
}
1861+
1862+
if (attributeData.ConstructorArguments.Length == 0) {
1863+
return false;
1864+
}
1865+
1866+
defaultValue = attributeData.ConstructorArguments.First().Value;
1867+
return true;
1868+
}
18231869
}
18241870

18251871
private RefConversion NeedsVariableForArgument(VBasic.Syntax.ArgumentSyntax node, RefKind refKind)
@@ -1898,7 +1944,7 @@ private ISymbol GetInvocationSymbol(SyntaxNode invocation)
18981944
(VBSyntax.InvocationExpressionSyntax e) => _semanticModel.GetSymbolInfo(e).ExtractBestMatch<ISymbol>(),
18991945
(VBSyntax.ObjectCreationExpressionSyntax e) => _semanticModel.GetSymbolInfo(e).ExtractBestMatch<ISymbol>(),
19001946
(VBSyntax.RaiseEventStatementSyntax e) => _semanticModel.GetSymbolInfo(e.Name).ExtractBestMatch<ISymbol>(),
1901-
(VBSyntax.MidExpressionSyntax _) => _semanticModel.Compilation.GetTypeByMetadataName("Microsoft.VisualBasic.CompilerServices.StringType")?.GetMembers("MidStmtStr").FirstOrDefault(),
1947+
(VBSyntax.MidExpressionSyntax _) => CommonConversions.KnownTypes.VbCompilerStringType?.GetMembers("MidStmtStr").FirstOrDefault(),
19021948
_ => throw new NotSupportedException());
19031949
return symbol;
19041950
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
namespace ICSharpCode.CodeConverter.CSharp;
2+
3+
internal class KnownNamedTypes
4+
{
5+
public KnownNamedTypes(SemanticModel semanticModel)
6+
{
7+
Boolean = semanticModel.Compilation.GetTypeByMetadataName("System.Boolean");
8+
String = semanticModel.Compilation.GetTypeByMetadataName("System.String");
9+
DefaultParameterValueAttribute = semanticModel.Compilation.GetTypeByMetadataName("System.Runtime.InteropServices.DefaultParameterValueAttribute");
10+
OptionalAttribute = semanticModel.Compilation.GetTypeByMetadataName("System.Runtime.InteropServices.OptionalAttribute");
11+
System_Linq_Expressions_Expression_T = semanticModel.Compilation.GetTypeByMetadataName("System.Linq.Expressions.Expression`1");
12+
VbCompilerStringType = semanticModel.Compilation.GetTypeByMetadataName("Microsoft.VisualBasic.CompilerServices.StringType");
13+
}
14+
15+
public INamedTypeSymbol System_Linq_Expressions_Expression_T { get; set; }
16+
17+
public INamedTypeSymbol Boolean { get; }
18+
public INamedTypeSymbol String { get; }
19+
public INamedTypeSymbol DefaultParameterValueAttribute { get; }
20+
public INamedTypeSymbol OptionalAttribute { get; }
21+
public INamedTypeSymbol VbCompilerStringType { get; }
22+
}

CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ internal class MethodBodyExecutableStatementVisitor : VBasic.VisualBasicSyntaxVi
2424
private readonly HashSet<string> _extraUsingDirectives;
2525
private readonly HandledEventsAnalysis _handledEventsAnalysis;
2626
private readonly HashSet<string> _generatedNames = new();
27-
private readonly INamedTypeSymbol _vbBooleanTypeSymbol;
2827
private readonly HashSet<ILocalSymbol> _localsToInlineInLoop;
2928
private readonly PerScopeState _perScopeState;
3029

@@ -65,7 +64,6 @@ private MethodBodyExecutableStatementVisitor(VisualBasicSyntaxNode methodNode, S
6564
_perScopeState = typeContext.PerScopeState;
6665
var byRefParameterVisitor = new PerScopeStateVisitorDecorator(this, _perScopeState, semanticModel, _generatedNames);
6766
CommentConvertingVisitor = new CommentConvertingMethodBodyVisitor(byRefParameterVisitor);
68-
_vbBooleanTypeSymbol = _semanticModel.Compilation.GetTypeByMetadataName("System.Boolean");
6967
_localsToInlineInLoop = localsToInlineInLoop;
7068
}
7169

@@ -520,7 +518,7 @@ await node.Name.AcceptAsync<NameSyntax>(_expressionVisitor),
520518
public override async Task<SyntaxList<StatementSyntax>> VisitSingleLineIfStatement(VBSyntax.SingleLineIfStatementSyntax node)
521519
{
522520
var condition = await node.Condition.AcceptAsync<ExpressionSyntax>(_expressionVisitor);
523-
condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Condition, condition, forceTargetType: _vbBooleanTypeSymbol);
521+
condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Condition, condition, forceTargetType: CommonConversions.KnownTypes.Boolean);
524522
var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements));
525523
ElseClauseSyntax elseClause = null;
526524

@@ -534,7 +532,7 @@ public override async Task<SyntaxList<StatementSyntax>> VisitSingleLineIfStateme
534532
public override async Task<SyntaxList<StatementSyntax>> VisitMultiLineIfBlock(VBSyntax.MultiLineIfBlockSyntax node)
535533
{
536534
var condition = await node.IfStatement.Condition.AcceptAsync<ExpressionSyntax>(_expressionVisitor);
537-
condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.IfStatement.Condition, condition, forceTargetType: _vbBooleanTypeSymbol);
535+
condition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.IfStatement.Condition, condition, forceTargetType: CommonConversions.KnownTypes.Boolean);
538536
var block = SyntaxFactory.Block(await ConvertStatementsAsync(node.Statements));
539537

540538
var elseClause = await ConvertElseClauseAsync(node.ElseBlock);
@@ -553,7 +551,7 @@ public override async Task<SyntaxList<StatementSyntax>> VisitMultiLineIfBlock(VB
553551
{
554552
var elseBlock = SyntaxFactory.Block(await ConvertStatementsAsync(elseIf.Statements));
555553
var elseIfCondition = await elseIf.ElseIfStatement.Condition.AcceptAsync<ExpressionSyntax>(_expressionVisitor);
556-
elseIfCondition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(elseIf.ElseIfStatement.Condition, elseIfCondition, forceTargetType: _vbBooleanTypeSymbol);
554+
elseIfCondition = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(elseIf.ElseIfStatement.Condition, elseIfCondition, forceTargetType: CommonConversions.KnownTypes.Boolean);
557555
return (elseIfCondition, elseBlock);
558556
}
559557

Tests/CSharp/MemberTests/MemberTests.cs

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4051,36 +4051,50 @@ public static int StaticTestProperty
40514051
}
40524052

40534053
[Fact]
4054-
public async Task TestRefConstArgumentAsync()
4054+
public async Task TestMissingByRefArgumentWithNoExplicitDefaultValueAsync()
40554055
{
40564056
await TestConversionVisualBasicToCSharpAsync(
4057-
@"Class RefConstArgument
4058-
Const a As String = ""a""
4057+
@"Imports System.Runtime.InteropServices
4058+
4059+
Class MissingByRefArgumentWithNoExplicitDefaultValue
40594060
Sub S()
4060-
Const b As String = ""b""
4061-
MO(a)
4062-
MS(b)
4061+
ByRefNoDefault()
4062+
OptionalByRefNoDefault()
4063+
OptionalByRefWithDefault()
40634064
End Sub
4064-
Sub MO(ByRef s As Object) : End Sub
4065-
Sub MS(ByRef s As String) : End Sub
4066-
End Class", @"
4067-
internal partial class RefConstArgument
4065+
4066+
Private Sub ByRefNoDefault(ByRef str1 As String) : End Sub
4067+
Private Sub OptionalByRefNoDefault(<[Optional]> ByRef str2 As String) : End Sub
4068+
Private Sub OptionalByRefWithDefault(<[Optional], DefaultParameterValue(""a"")> ByRef str3 As String) : End Sub
4069+
End Class", @"using System.Runtime.InteropServices;
4070+
4071+
internal partial class MissingByRefArgumentWithNoExplicitDefaultValue
40684072
{
4069-
private const string a = ""a"";
40704073
public void S()
40714074
{
4072-
const string b = ""b"";
4073-
object args = a;
4074-
MO(ref args);
4075-
string args1 = b;
4076-
MS(ref args1);
4075+
ByRefNoDefault();
4076+
string argstr2 = default;
4077+
OptionalByRefNoDefault(str2: ref argstr2);
4078+
string argstr3 = ""a"";
4079+
OptionalByRefWithDefault(str3: ref argstr3);
4080+
}
4081+
4082+
private void ByRefNoDefault(ref string str1)
4083+
{
40774084
}
4078-
public void MO(ref object s)
4085+
private void OptionalByRefNoDefault([Optional] ref string str2)
40794086
{
40804087
}
4081-
public void MS(ref string s)
4088+
private void OptionalByRefWithDefault([Optional][DefaultParameterValue(""a"")] ref string str3)
40824089
{
40834090
}
4084-
}");
4091+
}
4092+
3 source compilation errors:
4093+
BC30455: Argument not specified for parameter 'str1' of 'Private Sub ByRefNoDefault(ByRef str1 As String)'.
4094+
BC30455: Argument not specified for parameter 'str2' of 'Private Sub OptionalByRefNoDefault(ByRef str2 As String)'.
4095+
BC30455: Argument not specified for parameter 'str3' of 'Private Sub OptionalByRefWithDefault(ByRef str3 As String)'.
4096+
1 target compilation errors:
4097+
CS7036: There is no argument given that corresponds to the required formal parameter 'str1' of 'MissingByRefArgumentWithNoExplicitDefaultValue.ByRefNoDefault(ref string)'
4098+
");
40854099
}
40864100
}

0 commit comments

Comments
 (0)