Skip to content

Commit

Permalink
Merge branch 'master' into fix/PropertySetterComments/1
Browse files Browse the repository at this point in the history
# Conflicts:
#	Tests/CSharp/MemberTests/MemberTests.cs
  • Loading branch information
Timur Kelman authored and Timur Kelman committed Jul 23, 2024
2 parents 9e5e6c9 + e65b3fd commit 0d12fe5
Show file tree
Hide file tree
Showing 38 changed files with 2,329 additions and 37 deletions.
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;
}
8 changes: 7 additions & 1 deletion CodeConverter/CSharp/DeclarationNodeVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1504,7 +1504,13 @@ public override async Task<CSharpSyntaxNode> VisitDeclareStatement(VBSyntax.Decl

var attributeLists = (await CommonConversions.ConvertAttributesAsync(node.AttributeLists)).Add(dllImportAttributeList);

var modifiers = CommonConversions.ConvertModifiers(node, node.Modifiers).Add(SyntaxFactory.Token(CSSyntaxKind.StaticKeyword)).Add(SyntaxFactory.Token(CSSyntaxKind.ExternKeyword));
var tokenContext = GetMemberContext(node);
var modifiers = CommonConversions.ConvertModifiers(node, node.Modifiers, tokenContext);
if (!modifiers.Any(m => m.IsKind(CSSyntaxKind.StaticKeyword))) {
modifiers = modifiers.Add(SyntaxFactory.Token(CSSyntaxKind.StaticKeyword));
}
modifiers = modifiers.Add(SyntaxFactory.Token(CSSyntaxKind.ExternKeyword));

var returnType = await (node.AsClause?.Type).AcceptAsync<TypeSyntax>(_triviaConvertingExpressionVisitor) ?? SyntaxFactory.PredefinedType(SyntaxFactory.Token(CSSyntaxKind.VoidKeyword));
var parameterListSyntax = await (node.ParameterList).AcceptAsync<ParameterListSyntax>(_triviaConvertingExpressionVisitor) ??
SyntaxFactory.ParameterList();
Expand Down
85 changes: 75 additions & 10 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 @@ -1731,17 +1731,22 @@ async Task<ArgumentSyntax> ConvertArg(VBSyntax.ArgumentSyntax arg, int argIndex)
{
var argName = arg is VBSyntax.SimpleArgumentSyntax { IsNamed: true } namedArg ? namedArg.NameColonEquals.Name.Identifier.Text : null;
var parameterSymbol = invocationSymbol?.GetParameters().GetArgument(argName, argIndex);
var convertedArg = await ConvertArgForParameter(arg, parameterSymbol);

if (parameterSymbol != null) {
if (convertedArg is not null && parameterSymbol is not null) {
processedParameters.Add(parameterSymbol.Name);
}

return convertedArg;
}

async Task<ArgumentSyntax> ConvertArgForParameter(VBSyntax.ArgumentSyntax arg, IParameterSymbol parameterSymbol)
{
if (arg.IsOmitted) {
if (invocationSymbol != null && !invocationHasOverloads) {
forceNamedParameters = true;
return null; //Prefer to skip omitted and use named parameters when the symbol has only one overload
}

return ConvertOmittedArgument(parameterSymbol);
}

Expand Down Expand Up @@ -1818,8 +1823,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 All @@ -1833,9 +1884,14 @@ private RefConversion NeedsVariableForArgument(VBasic.Syntax.ArgumentSyntax node
RefConversion GetRefConversion(VBSyntax.ExpressionSyntax expression)
{
var symbolInfo = GetSymbolInfoInDocument<ISymbol>(expression);
if (symbolInfo is IPropertySymbol propertySymbol) {
if (symbolInfo is IPropertySymbol propertySymbol
// a property in VB.NET code can be ReturnsByRef if it's defined in a C# assembly the VB.NET code references
&& !propertySymbol.ReturnsByRef && !propertySymbol.ReturnsByRefReadonly) {
return propertySymbol.IsReadOnly ? RefConversion.PreAssigment : RefConversion.PreAndPostAssignment;
}
else if (symbolInfo is IFieldSymbol { IsConst: true } or ILocalSymbol { IsConst: true }) {
return RefConversion.PreAssigment;
}

if (DeclaredInUsing(symbolInfo)) return RefConversion.PreAssigment;

Expand All @@ -1858,7 +1914,16 @@ RefConversion GetRefConversion(VBSyntax.ExpressionSyntax expression)
bool IsRefArrayAcces(VBSyntax.ExpressionSyntax expression)
{
if (!(expression is VBSyntax.InvocationExpressionSyntax ies)) return false;
return _semanticModel.GetOperation(ies).IsArrayElementAccess() && GetRefConversion(ies.Expression) == RefConversion.Inline;
var op = _semanticModel.GetOperation(ies);
return (op.IsArrayElementAccess() || IsReturnsByRefPropertyElementAccess(op))
&& GetRefConversion(ies.Expression) == RefConversion.Inline;

static bool IsReturnsByRefPropertyElementAccess(IOperation op)
{
return op.IsPropertyElementAccess()
&& op is IPropertyReferenceOperation { Property: { } prop }
&& (prop.ReturnsByRef || prop.ReturnsByRefReadonly);
}
}
}

Expand Down Expand Up @@ -1895,7 +1960,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; }
}
18 changes: 8 additions & 10 deletions CodeConverter/CSharp/LiteralConversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,16 @@ private static (string textForUser, ExpressionSyntax MaybeFullExpression) Conver
int parsedHexValue = int.Parse(hexValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture);

// This is a very special case where for 8 digit hex strings, C# interprets them as unsigned ints, but VB interprets them as ints
// This can lead to a compile error if assigned to an int in VB. So in a case like 0x91234567, we generate `int.MinValue + 0x11234567`
// This can lead to a compile error if assigned to an int in VB. So in a case like 0x91234567, we generate `unchecked((int)0x91234567)`
// This way the value looks pretty close to before and remains a compile time constant
if (parsedHexValue < 0) {
int positiveValue = parsedHexValue - int.MinValue;

var intMinValueExpr = SyntaxFactory.MemberAccessExpression(
SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.PredefinedType(
SyntaxFactory.Token(SyntaxKind.IntKeyword)),
ValidSyntaxFactory.IdentifierName(nameof(int.MinValue)));
var positiveValueExpr = NumericLiteral(SyntaxFactory.Literal("0x" + positiveValue.ToString("X8", CultureInfo.InvariantCulture), positiveValue));
return (null, SyntaxFactory.BinaryExpression(SyntaxKind.AddExpression, intMinValueExpr, positiveValueExpr));
var hexValueExpr = NumericLiteral(SyntaxFactory.Literal(textForUser, parsedHexValue));
var checkedExpr = SyntaxFactory.CheckedExpression(
CSSyntaxKind.UncheckedExpression,
SyntaxFactory.CastExpression(
SyntaxFactory.PredefinedType(SyntaxFactory.Token(CSSyntaxKind.IntKeyword)),
hexValueExpr));
return (null, checkedExpr);
}
} else if (canBeBinaryOrHex && textForUser.StartsWith("&B", StringComparison.OrdinalIgnoreCase)) {
textForUser = "0b" + textForUser.Substring(2);
Expand Down
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
5 changes: 3 additions & 2 deletions CodeConverter/CSharp/OperationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ public static bool IsAssignableExpression(this IOperation operation)
case OperationKind.DynamicMemberReference:
return true;

//Just documenting since it's the only one mentioning reference that can't necessarily be assigned to AFAIK
case OperationKind.PropertyReference:
return false;
//a property might be RefReturn, if it's defined in a referenced C# assembly
var prop = ((IPropertyReferenceOperation)operation).Property;
return prop.ReturnsByRef || prop.ReturnsByRefReadonly;
}

return false;
Expand Down
Loading

0 comments on commit 0d12fe5

Please sign in to comment.