Skip to content

Commit 0d12fe5

Browse files
Timur KelmanTimur Kelman
authored andcommitted
Merge branch 'master' into fix/PropertySetterComments/1
# Conflicts: # Tests/CSharp/MemberTests/MemberTests.cs
2 parents 9e5e6c9 + e65b3fd commit 0d12fe5

38 files changed

Lines changed: 2329 additions & 37 deletions

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/DeclarationNodeVisitor.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1504,7 +1504,13 @@ public override async Task<CSharpSyntaxNode> VisitDeclareStatement(VBSyntax.Decl
15041504

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

1507-
var modifiers = CommonConversions.ConvertModifiers(node, node.Modifiers).Add(SyntaxFactory.Token(CSSyntaxKind.StaticKeyword)).Add(SyntaxFactory.Token(CSSyntaxKind.ExternKeyword));
1507+
var tokenContext = GetMemberContext(node);
1508+
var modifiers = CommonConversions.ConvertModifiers(node, node.Modifiers, tokenContext);
1509+
if (!modifiers.Any(m => m.IsKind(CSSyntaxKind.StaticKeyword))) {
1510+
modifiers = modifiers.Add(SyntaxFactory.Token(CSSyntaxKind.StaticKeyword));
1511+
}
1512+
modifiers = modifiers.Add(SyntaxFactory.Token(CSSyntaxKind.ExternKeyword));
1513+
15081514
var returnType = await (node.AsClause?.Type).AcceptAsync<TypeSyntax>(_triviaConvertingExpressionVisitor) ?? SyntaxFactory.PredefinedType(SyntaxFactory.Token(CSSyntaxKind.VoidKeyword));
15091515
var parameterListSyntax = await (node.ParameterList).AcceptAsync<ParameterListSyntax>(_triviaConvertingExpressionVisitor) ??
15101516
SyntaxFactory.ParameterList();

CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 75 additions & 10 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

@@ -1731,17 +1731,22 @@ async Task<ArgumentSyntax> ConvertArg(VBSyntax.ArgumentSyntax arg, int argIndex)
17311731
{
17321732
var argName = arg is VBSyntax.SimpleArgumentSyntax { IsNamed: true } namedArg ? namedArg.NameColonEquals.Name.Identifier.Text : null;
17331733
var parameterSymbol = invocationSymbol?.GetParameters().GetArgument(argName, argIndex);
1734+
var convertedArg = await ConvertArgForParameter(arg, parameterSymbol);
17341735

1735-
if (parameterSymbol != null) {
1736+
if (convertedArg is not null && parameterSymbol is not null) {
17361737
processedParameters.Add(parameterSymbol.Name);
17371738
}
17381739

1740+
return convertedArg;
1741+
}
1742+
1743+
async Task<ArgumentSyntax> ConvertArgForParameter(VBSyntax.ArgumentSyntax arg, IParameterSymbol parameterSymbol)
1744+
{
17391745
if (arg.IsOmitted) {
17401746
if (invocationSymbol != null && !invocationHasOverloads) {
17411747
forceNamedParameters = true;
17421748
return null; //Prefer to skip omitted and use named parameters when the symbol has only one overload
17431749
}
1744-
17451750
return ConvertOmittedArgument(parameterSymbol);
17461751
}
17471752

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

18251876
private RefConversion NeedsVariableForArgument(VBasic.Syntax.ArgumentSyntax node, RefKind refKind)
@@ -1833,9 +1884,14 @@ private RefConversion NeedsVariableForArgument(VBasic.Syntax.ArgumentSyntax node
18331884
RefConversion GetRefConversion(VBSyntax.ExpressionSyntax expression)
18341885
{
18351886
var symbolInfo = GetSymbolInfoInDocument<ISymbol>(expression);
1836-
if (symbolInfo is IPropertySymbol propertySymbol) {
1887+
if (symbolInfo is IPropertySymbol propertySymbol
1888+
// a property in VB.NET code can be ReturnsByRef if it's defined in a C# assembly the VB.NET code references
1889+
&& !propertySymbol.ReturnsByRef && !propertySymbol.ReturnsByRefReadonly) {
18371890
return propertySymbol.IsReadOnly ? RefConversion.PreAssigment : RefConversion.PreAndPostAssignment;
18381891
}
1892+
else if (symbolInfo is IFieldSymbol { IsConst: true } or ILocalSymbol { IsConst: true }) {
1893+
return RefConversion.PreAssigment;
1894+
}
18391895

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

@@ -1858,7 +1914,16 @@ RefConversion GetRefConversion(VBSyntax.ExpressionSyntax expression)
18581914
bool IsRefArrayAcces(VBSyntax.ExpressionSyntax expression)
18591915
{
18601916
if (!(expression is VBSyntax.InvocationExpressionSyntax ies)) return false;
1861-
return _semanticModel.GetOperation(ies).IsArrayElementAccess() && GetRefConversion(ies.Expression) == RefConversion.Inline;
1917+
var op = _semanticModel.GetOperation(ies);
1918+
return (op.IsArrayElementAccess() || IsReturnsByRefPropertyElementAccess(op))
1919+
&& GetRefConversion(ies.Expression) == RefConversion.Inline;
1920+
1921+
static bool IsReturnsByRefPropertyElementAccess(IOperation op)
1922+
{
1923+
return op.IsPropertyElementAccess()
1924+
&& op is IPropertyReferenceOperation { Property: { } prop }
1925+
&& (prop.ReturnsByRef || prop.ReturnsByRefReadonly);
1926+
}
18621927
}
18631928
}
18641929

@@ -1895,7 +1960,7 @@ private ISymbol GetInvocationSymbol(SyntaxNode invocation)
18951960
(VBSyntax.InvocationExpressionSyntax e) => _semanticModel.GetSymbolInfo(e).ExtractBestMatch<ISymbol>(),
18961961
(VBSyntax.ObjectCreationExpressionSyntax e) => _semanticModel.GetSymbolInfo(e).ExtractBestMatch<ISymbol>(),
18971962
(VBSyntax.RaiseEventStatementSyntax e) => _semanticModel.GetSymbolInfo(e.Name).ExtractBestMatch<ISymbol>(),
1898-
(VBSyntax.MidExpressionSyntax _) => _semanticModel.Compilation.GetTypeByMetadataName("Microsoft.VisualBasic.CompilerServices.StringType")?.GetMembers("MidStmtStr").FirstOrDefault(),
1963+
(VBSyntax.MidExpressionSyntax _) => CommonConversions.KnownTypes.VbCompilerStringType?.GetMembers("MidStmtStr").FirstOrDefault(),
18991964
_ => throw new NotSupportedException());
19001965
return symbol;
19011966
}
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/LiteralConversions.cs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,18 +152,16 @@ private static (string textForUser, ExpressionSyntax MaybeFullExpression) Conver
152152
int parsedHexValue = int.Parse(hexValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
153153

154154
// This is a very special case where for 8 digit hex strings, C# interprets them as unsigned ints, but VB interprets them as ints
155-
// 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`
155+
// 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)`
156156
// This way the value looks pretty close to before and remains a compile time constant
157157
if (parsedHexValue < 0) {
158-
int positiveValue = parsedHexValue - int.MinValue;
159-
160-
var intMinValueExpr = SyntaxFactory.MemberAccessExpression(
161-
SyntaxKind.SimpleMemberAccessExpression,
162-
SyntaxFactory.PredefinedType(
163-
SyntaxFactory.Token(SyntaxKind.IntKeyword)),
164-
ValidSyntaxFactory.IdentifierName(nameof(int.MinValue)));
165-
var positiveValueExpr = NumericLiteral(SyntaxFactory.Literal("0x" + positiveValue.ToString("X8", CultureInfo.InvariantCulture), positiveValue));
166-
return (null, SyntaxFactory.BinaryExpression(SyntaxKind.AddExpression, intMinValueExpr, positiveValueExpr));
158+
var hexValueExpr = NumericLiteral(SyntaxFactory.Literal(textForUser, parsedHexValue));
159+
var checkedExpr = SyntaxFactory.CheckedExpression(
160+
CSSyntaxKind.UncheckedExpression,
161+
SyntaxFactory.CastExpression(
162+
SyntaxFactory.PredefinedType(SyntaxFactory.Token(CSSyntaxKind.IntKeyword)),
163+
hexValueExpr));
164+
return (null, checkedExpr);
167165
}
168166
} else if (canBeBinaryOrHex && textForUser.StartsWith("&B", StringComparison.OrdinalIgnoreCase)) {
169167
textForUser = "0b" + textForUser.Substring(2);

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

CodeConverter/CSharp/OperationExtensions.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,10 @@ public static bool IsAssignableExpression(this IOperation operation)
5656
case OperationKind.DynamicMemberReference:
5757
return true;
5858

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

6465
return false;

0 commit comments

Comments
 (0)