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

Avoid VB assembly string comparison call where possible #317

Merged
merged 6 commits into from
Jun 2, 2019
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
47 changes: 15 additions & 32 deletions ICSharpCode.CodeConverter/CSharp/NodesVisitor.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
Expand All @@ -9,6 +10,7 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.VisualBasic.CompilerServices;
using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using SyntaxNodeExtensions = ICSharpCode.CodeConverter.Util.SyntaxNodeExtensions;
using VBSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax;
Expand Down Expand Up @@ -38,6 +40,7 @@ class NodesVisitor : VBasic.VisualBasicSyntaxVisitor<CSharpSyntaxNode>
private readonly TypeConversionAnalyzer _typeConversionAnalyzer;
public CommentConvertingNodesVisitor TriviaConvertingVisitor { get; }
private bool _optionCompareText = false;
private VisualBasicEqualityComparison _visualBasicEqualityComparison;

private CommonConversions CommonConversions { get; }

Expand Down Expand Up @@ -109,6 +112,7 @@ public override CSharpSyntaxNode VisitCompilationUnit(VBSyntax.CompilationUnitSy

_optionCompareText = node.Options.Any(x => x.NameKeyword.ValueText.Equals("Compare", StringComparison.OrdinalIgnoreCase) &&
x.ValueKeyword.ValueText.Equals("Text", StringComparison.OrdinalIgnoreCase));
_visualBasicEqualityComparison = new VisualBasicEqualityComparison(_semanticModel, _extraUsingDirectives, _optionCompareText);

var attributes = SyntaxFactory.List(node.Attributes.SelectMany(a => a.AttributeLists).SelectMany(ConvertAttribute));
var sourceAndConverted = node.Members.Select(m => (Source: m, Converted: ConvertMember(m))).ToReadOnlyCollection();
Expand Down Expand Up @@ -1692,38 +1696,18 @@ public override CSharpSyntaxNode VisitBinaryExpression(VBSyntax.BinaryExpression
}
}

if (node.IsKind(VBasic.SyntaxKind.EqualsExpression, VBasic.SyntaxKind.NotEqualsExpression)) {
if (lhsTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String &&
rhsTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String) {
bool lhsEmpty = lhs is LiteralExpressionSyntax les &&
(les.IsKind(SyntaxKind.NullLiteralExpression) ||
(les.IsKind(SyntaxKind.StringLiteralExpression) && string.IsNullOrEmpty(les.Token.ValueText)));
bool lhsLiteral = lhs is LiteralExpressionSyntax && lhs.IsKind(SyntaxKind.StringLiteralExpression);
bool rhsEmpty = rhs is LiteralExpressionSyntax res &&
(res.IsKind(SyntaxKind.NullLiteralExpression) ||
(res.IsKind(SyntaxKind.StringLiteralExpression) && string.IsNullOrEmpty(res.Token.ValueText)));
bool rhsLiteral = rhs is LiteralExpressionSyntax && rhs.IsKind(SyntaxKind.StringLiteralExpression);

if (lhsEmpty || rhsEmpty) {
var arg = lhsEmpty ? rhs : lhs;
var nullOrEmpty = SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.IdentifierName("string"), SyntaxFactory.IdentifierName("IsNullOrEmpty")),
SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[] { SyntaxFactory.Argument(arg) })));
return node.IsKind(VBasic.SyntaxKind.EqualsExpression) ? (CSharpSyntaxNode)nullOrEmpty : SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, nullOrEmpty);
} else if (!_optionCompareText && (lhsLiteral || rhsLiteral)) {
// If either side is a literal, and we're in binary comparison mode, we can use normal comparison logic
} else {
_extraUsingDirectives.Add("Microsoft.VisualBasic.CompilerServices");
var textCompare = SyntaxFactory.Argument(SyntaxFactory.NameColon("TextCompare"),
default(SyntaxToken),
_optionCompareText ? SyntaxFactory.LiteralExpression(SyntaxKind.TrueLiteralExpression) : SyntaxFactory.LiteralExpression(SyntaxKind.FalseLiteralExpression));
var compareString = SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, SyntaxFactory.IdentifierName("Operators"), SyntaxFactory.IdentifierName("CompareString")),
SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[] { SyntaxFactory.Argument(lhs), SyntaxFactory.Argument(rhs), textCompare })));
lhs = compareString;
rhs = SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(0));
var objectEqualityType = _visualBasicEqualityComparison.GetObjectEqualityType(node, lhsTypeInfo, rhsTypeInfo);
switch(objectEqualityType) {
case VisualBasicEqualityComparison.RequiredType.StringOnly:
if (lhsTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String &&
rhsTypeInfo.ConvertedType?.SpecialType == SpecialType.System_String &&
_visualBasicEqualityComparison.TryConvertToNullOrEmptyCheck(node, lhs, rhs, out CSharpSyntaxNode visitBinaryExpression)) {
return visitBinaryExpression;
}
}
(lhs, rhs) = _visualBasicEqualityComparison.AdjustForVbStringComparison(node.Left, lhs, lhsTypeInfo, node.Right, rhs, rhsTypeInfo);
break;
case VisualBasicEqualityComparison.RequiredType.Object:
return _visualBasicEqualityComparison.GetFullExpressionForVbObjectComparison(node, lhs, rhs);
}

if (node.IsKind(VBasic.SyntaxKind.ExponentiateExpression,
Expand All @@ -1733,7 +1717,6 @@ public override CSharpSyntaxNode VisitBinaryExpression(VBSyntax.BinaryExpression
ExpressionSyntaxExtensions.CreateArgList(lhs, rhs));
}


var kind = VBasic.VisualBasicExtensions.Kind(node).ConvertToken(TokenContext.Local);
var op = SyntaxFactory.Token(CSharpUtil.GetExpressionOperatorTokenKind(kind));

Expand Down
234 changes: 234 additions & 0 deletions ICSharpCode.CodeConverter/CSharp/VisualBasicEqualityComparison.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using ICSharpCode.CodeConverter.Util;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.VisualBasic.CompilerServices;
using SyntaxFactory = Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using SyntaxKind = Microsoft.CodeAnalysis.CSharp.SyntaxKind;
using VBSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax;
using VBSyntaxKind = Microsoft.CodeAnalysis.VisualBasic.SyntaxKind;
using VBasic = Microsoft.CodeAnalysis.VisualBasic;

namespace ICSharpCode.CodeConverter.CSharp
{

/// <summary>
/// The equals and not equals operators in Visual Basic call ConditionalCompareObjectEqual.
/// This method allows a sort of best effort comparison of different types.
/// There are therefore some surprising results such as "" = Nothing being true.
/// Here we try to coerce the arguments for the CSharp equals method to get as close to the runtime behaviour as possible without inlining hundreds of lines of code.
/// </summary>
internal class VisualBasicEqualityComparison
{
private readonly SemanticModel _semanticModel;
private readonly HashSet<string> _extraUsingDirectives;

public VisualBasicEqualityComparison(SemanticModel semanticModel, HashSet<string> extraUsingDirectives, bool optionCompareTextCaseInsensitive)
{
OptionCompareTextCaseInsensitive = optionCompareTextCaseInsensitive;
_extraUsingDirectives = extraUsingDirectives;
_semanticModel = semanticModel;
}

public enum RequiredType
{
None,
StringOnly,
Object
}

public bool OptionCompareTextCaseInsensitive { get; }

public RequiredType GetObjectEqualityType(VBSyntax.BinaryExpressionSyntax node, TypeInfo leftType, TypeInfo rightType)
{
var typeInfos = new[] {leftType, rightType};
if (!node.IsKind(VBasic.SyntaxKind.EqualsExpression, VBasic.SyntaxKind.NotEqualsExpression)) {
return RequiredType.None;
}

bool requiresVbEqualityCheck = typeInfos.Any(t => t.Type?.SpecialType == SpecialType.System_Object);

if (typeInfos.All(t => t.Type != null) && typeInfos.All(
t => t.Type.SpecialType == SpecialType.System_String ||
t.Type.IsArrayOf(SpecialType.System_Char))) {
return RequiredType.StringOnly;
};

return requiresVbEqualityCheck ? RequiredType.Object : RequiredType.None;
}



public (ExpressionSyntax lhs, ExpressionSyntax rhs) VbCoerceToString(VBSyntax.ExpressionSyntax vbLeft, ExpressionSyntax csLeft, TypeInfo lhsTypeInfo, VBSyntax.ExpressionSyntax vbRight, ExpressionSyntax csRight, TypeInfo rhsTypeInfo)
{
return (VbCoerceToString(vbLeft, csLeft, lhsTypeInfo), VbCoerceToString(vbRight, csRight, rhsTypeInfo));
}

private ExpressionSyntax VbCoerceToString(VBSyntax.ExpressionSyntax vbNode, ExpressionSyntax csNode, TypeInfo typeInfo)
{
bool isStringType = typeInfo.Type.SpecialType == SpecialType.System_String;

if (IsNothingOrEmpty(vbNode)) {
return EmptyStringExpression();
}

if (!CanBeNull(vbNode)) {
return csNode;
}

csNode = isStringType
? SyntaxFactory.ParenthesizedExpression(Coalesce(csNode, EmptyStringExpression()))
: Coalesce(csNode, EmptyCharArrayExpression());

return !isStringType ? NewStringFromArg(csNode) : csNode;
}

private bool CanBeNull(VBSyntax.ExpressionSyntax vbNode)
{
if (vbNode.IsKind(VBSyntaxKind.StringLiteralExpression)) return false;
var constantAnalysis = _semanticModel.GetConstantValue(vbNode);
if (constantAnalysis.HasValue && constantAnalysis.Value != null) return false;
return true;
}


private static ExpressionSyntax Coalesce(ExpressionSyntax lhs, ExpressionSyntax emptyString)
{
return SyntaxFactory.BinaryExpression(SyntaxKind.CoalesceExpression, lhs, emptyString);
}

private static ExpressionSyntax EmptyCharArrayExpression()
{
var literalExpressionSyntax =
SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(0));
var arrayRankSpecifierSyntax = ArrayRankSpecifier(literalExpressionSyntax);
var arrayTypeSyntax = CharArrayType().WithRankSpecifiers(arrayRankSpecifierSyntax);
return SyntaxFactory.ArrayCreationExpression(arrayTypeSyntax);
}

private static SyntaxList<ArrayRankSpecifierSyntax> ArrayRankSpecifier(ExpressionSyntax expressionSyntax)
{
var literalExpressionSyntaxList = SyntaxFactory.SingletonSeparatedList(expressionSyntax);
var arrayRankSpecifierSyntax = SyntaxFactory.ArrayRankSpecifier(literalExpressionSyntaxList);
return SyntaxFactory.SingletonList(arrayRankSpecifierSyntax);
}

private static ArrayTypeSyntax CharArrayType()
{
return SyntaxFactory.ArrayType(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.CharKeyword)));
}

private static ExpressionSyntax EmptyStringExpression()
{
return SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(""));
}

private static ObjectCreationExpressionSyntax NewStringFromArg(ExpressionSyntax lhs)
{
return SyntaxFactory.ObjectCreationExpression(
SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.StringKeyword)),
ExpressionSyntaxExtensions.CreateArgList(lhs), default(InitializerExpressionSyntax));
}

public bool TryConvertToNullOrEmptyCheck(VBSyntax.BinaryExpressionSyntax node, ExpressionSyntax lhs,
ExpressionSyntax rhs, out CSharpSyntaxNode visitBinaryExpression)
{
bool lhsEmpty = IsNothingOrEmpty(node.Left);
bool rhsEmpty = IsNothingOrEmpty(node.Right);

if (lhsEmpty || rhsEmpty)
{
var arg = lhsEmpty ? rhs : lhs;
var nullOrEmpty = SyntaxFactory.InvocationExpression(
SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
SyntaxFactory.IdentifierName("string"),
SyntaxFactory.IdentifierName("IsNullOrEmpty")),
SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[]
{SyntaxFactory.Argument(arg)})));
{
visitBinaryExpression = NegateIfNeeded(node, nullOrEmpty);
return true;
}
}

visitBinaryExpression = null;
return false;
}

private bool IsNothingOrEmpty(VBSyntax.ExpressionSyntax expressionSyntax)
{
if (expressionSyntax is VBSyntax.LiteralExpressionSyntax les &&
(les.IsKind(VBSyntaxKind.NothingLiteralExpression) ||
(les.IsKind(VBSyntaxKind.StringLiteralExpression) &&
String.IsNullOrEmpty(les.Token.ValueText)))) return true;
var constantAnalysis = _semanticModel.GetConstantValue(expressionSyntax);
return constantAnalysis.HasValue && (constantAnalysis.Value == null || constantAnalysis.Value as string == "");
}

private static ExpressionSyntax NegateIfNeeded(VBSyntax.BinaryExpressionSyntax node, InvocationExpressionSyntax positiveExpression)
{
return node.IsKind(VBasic.SyntaxKind.EqualsExpression)
? (ExpressionSyntax) positiveExpression
: SyntaxFactory.PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, positiveExpression);
}

public (ExpressionSyntax csLeft, ExpressionSyntax csRight) AdjustForVbStringComparison(VBSyntax.ExpressionSyntax vbLeft, ExpressionSyntax csLeft, TypeInfo lhsTypeInfo, VBSyntax.ExpressionSyntax vbRight, ExpressionSyntax csRight, TypeInfo rhsTypeInfo)
{
if (OptionCompareTextCaseInsensitive) {
_extraUsingDirectives.Add("System.Globalization");
var compareOptions = SyntaxFactory.Argument(GetCompareTextCaseInsensitiveCompareOptions());
var compareString = SyntaxFactory.InvocationExpression(
MemberAccess(nameof(CultureInfo), nameof(CultureInfo.CurrentCulture),
nameof(CultureInfo.CompareInfo), nameof(CompareInfo.Compare)),
SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[]
{SyntaxFactory.Argument(csLeft), SyntaxFactory.Argument(csRight), compareOptions})));
csLeft = compareString;
csRight = SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression,
SyntaxFactory.Literal(0));
} else {
(csLeft, csRight) = VbCoerceToString(vbLeft, csLeft, lhsTypeInfo, vbRight, csRight, rhsTypeInfo);
}

return (csLeft, csRight);
}

public ExpressionSyntax GetFullExpressionForVbObjectComparison(VBSyntax.BinaryExpressionSyntax node, ExpressionSyntax lhs, ExpressionSyntax rhs)
{
_extraUsingDirectives.Add("Microsoft.VisualBasic.CompilerServices");
var optionCompareTextCaseInsensitive = SyntaxFactory.Argument(SyntaxFactory.LiteralExpression(OptionCompareTextCaseInsensitive ? SyntaxKind.TrueKeyword : SyntaxKind.FalseLiteralExpression));
var compareObject = SyntaxFactory.InvocationExpression(
MemberAccess(nameof(Operators), nameof(Operators.ConditionalCompareObjectEqual)),
SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[]
{SyntaxFactory.Argument(lhs), SyntaxFactory.Argument(rhs), optionCompareTextCaseInsensitive})));
return NegateIfNeeded(node, compareObject);
}

private static BinaryExpressionSyntax GetCompareTextCaseInsensitiveCompareOptions()
{
return SyntaxFactory.BinaryExpression(SyntaxKind.BitwiseOrExpression,
SyntaxFactory.BinaryExpression(SyntaxKind.BitwiseOrExpression,
MemberAccess(nameof(CompareOptions), nameof(CompareOptions.IgnoreCase)),
MemberAccess(nameof(CompareOptions), nameof(CompareOptions.IgnoreKanaType))),
MemberAccess(nameof(CompareOptions), nameof(CompareOptions.IgnoreWidth))
);
}

private static ExpressionSyntax MemberAccess(params string[] nameParts)
{
ExpressionSyntax lhs = null;
foreach (var namePart in nameParts) {
if (lhs == null) lhs = SyntaxFactory.IdentifierName(namePart);
else {
lhs = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
lhs, SyntaxFactory.IdentifierName(namePart));
}
}

return lhs;
}
}
}
3 changes: 2 additions & 1 deletion ICSharpCode.CodeConverter/Util/CSharpCompiler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
Expand All @@ -9,7 +10,7 @@ public class CSharpCompiler : ICompiler
{
public SyntaxTree CreateTree(string text)
{
return SyntaxFactory.ParseSyntaxTree(SourceText.From(text));
return SyntaxFactory.ParseSyntaxTree(text, encoding: Encoding.UTF8);
}

public Compilation CreateCompilationFromTree(SyntaxTree tree, IEnumerable<MetadataReference> references)
Expand Down
5 changes: 5 additions & 0 deletions ICSharpCode.CodeConverter/Util/ITypeSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,11 @@ public static bool IsIEnumerable(this ITypeSymbol typeSymbol)
{
return typeSymbol.ImplementsSpecialTypeInterface(SpecialType.System_Collections_IEnumerable);
}

public static bool IsArrayOf(this ITypeSymbol t, SpecialType specialType)
{
return t is IArrayTypeSymbol ats && ats.ElementType.SpecialType == specialType;
}
}
}

Loading