Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

take parameter attributes into account in CreateOptionalRefArg #1122

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions CodeConverter/CSharp/CommonConversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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<VariablePair> Variables);

Expand Down Expand Up @@ -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;
}
58 changes: 52 additions & 6 deletions CodeConverter/CSharp/ExpressionNodeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ internal class ExpressionNodeVisitor : VBasic.VisualBasicSyntaxVisitor<Task<CSha
private readonly QueryConverter _queryConverter;
private readonly Lazy<IReadOnlyDictionary<ITypeSymbol, string>> _convertMethodsLookupByReturnType;
private readonly LambdaConverter _lambdaConverter;
private readonly INamedTypeSymbol _vbBooleanTypeSymbol;
private readonly VisualBasicNullableExpressionsConverter _visualBasicNullableTypesConverter;
private readonly Dictionary<string, Stack<(SyntaxNode Scope, string TempName)>> _tempNameForAnonymousScope = new();
private readonly HashSet<string> _generatedNames = new(StringComparer.OrdinalIgnoreCase);
Expand All @@ -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<IReadOnlyDictionary<ITypeSymbol, string>>(() => CreateConvertMethodsLookupByReturnType(semanticModel));
_vbBooleanTypeSymbol = _semanticModel.Compilation.GetTypeByMetadataName("System.Boolean");
}

private static IReadOnlyDictionary<ITypeSymbol, string> CreateConvertMethodsLookupByReturnType(
Expand Down Expand Up @@ -775,7 +773,7 @@ public override async Task<CSharpSyntaxNode> VisitBinaryConditionalExpression(VB
public override async Task<CSharpSyntaxNode> VisitTernaryConditionalExpression(VBasic.Syntax.TernaryConditionalExpressionSyntax node)
{
var condition = await node.Condition.AcceptAsync<ExpressionSyntax>(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<ExpressionSyntax>(TriviaConvertingExpressionVisitor);
whenTrue = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.WhenTrue, whenTrue);
Expand Down Expand Up @@ -900,7 +898,7 @@ private async Task<CSharpSyntaxNode> 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;
}
}
}
Expand Down Expand Up @@ -938,6 +936,8 @@ private async Task<CSharpSyntaxNode> ConvertBinaryExpressionAsync(VBasic.Syntax.
return node.Parent.IsKind(VBasic.SyntaxKind.SimpleArgument) ? exp : exp.AddParens();
}



private async Task<ExpressionSyntax> RewriteBinaryOperatorOrNullAsync(VBSyntax.BinaryExpressionSyntax node) =>
await _operatorConverter.ConvertRewrittenBinaryOperatorOrNullAsync(node, TriviaConvertingExpressionVisitor.IsWithinQuery);

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1898,7 +1944,7 @@ private ISymbol GetInvocationSymbol(SyntaxNode invocation)
(VBSyntax.InvocationExpressionSyntax e) => _semanticModel.GetSymbolInfo(e).ExtractBestMatch<ISymbol>(),
(VBSyntax.ObjectCreationExpressionSyntax e) => _semanticModel.GetSymbolInfo(e).ExtractBestMatch<ISymbol>(),
(VBSyntax.RaiseEventStatementSyntax e) => _semanticModel.GetSymbolInfo(e.Name).ExtractBestMatch<ISymbol>(),
(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;
}
Expand Down
22 changes: 22 additions & 0 deletions CodeConverter/CSharp/KnownNamedTypes.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
8 changes: 3 additions & 5 deletions CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ internal class MethodBodyExecutableStatementVisitor : VBasic.VisualBasicSyntaxVi
private readonly HashSet<string> _extraUsingDirectives;
private readonly HandledEventsAnalysis _handledEventsAnalysis;
private readonly HashSet<string> _generatedNames = new();
private readonly INamedTypeSymbol _vbBooleanTypeSymbol;
private readonly HashSet<ILocalSymbol> _localsToInlineInLoop;
private readonly PerScopeState _perScopeState;

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -520,7 +518,7 @@ await node.Name.AcceptAsync<NameSyntax>(_expressionVisitor),
public override async Task<SyntaxList<StatementSyntax>> VisitSingleLineIfStatement(VBSyntax.SingleLineIfStatementSyntax node)
{
var condition = await node.Condition.AcceptAsync<ExpressionSyntax>(_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;

Expand All @@ -534,7 +532,7 @@ public override async Task<SyntaxList<StatementSyntax>> VisitSingleLineIfStateme
public override async Task<SyntaxList<StatementSyntax>> VisitMultiLineIfBlock(VBSyntax.MultiLineIfBlockSyntax node)
{
var condition = await node.IfStatement.Condition.AcceptAsync<ExpressionSyntax>(_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);
Expand All @@ -553,7 +551,7 @@ public override async Task<SyntaxList<StatementSyntax>> VisitMultiLineIfBlock(VB
{
var elseBlock = SyntaxFactory.Block(await ConvertStatementsAsync(elseIf.Statements));
var elseIfCondition = await elseIf.ElseIfStatement.Condition.AcceptAsync<ExpressionSyntax>(_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);
}

Expand Down
52 changes: 33 additions & 19 deletions Tests/CSharp/MemberTests/MemberTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)'
");
}
}
Loading