Skip to content

Commit fb20877

Browse files
Merge pull request #1209 from icsharpcode/delegates
Delegates
2 parents 73137da + 77c44f7 commit fb20877

26 files changed

+2693
-2381
lines changed

CodeConverter.sln

Lines changed: 0 additions & 65 deletions
This file was deleted.

CodeConverter/CSharp/AccessorDeclarationNodeConverter.cs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
using ICSharpCode.CodeConverter.Util.FromRoslyn;
2-
using Microsoft.CodeAnalysis.VisualBasic;
1+
using Microsoft.CodeAnalysis.VisualBasic;
2+
using static Microsoft.CodeAnalysis.VisualBasic.VisualBasicExtensions;
3+
using ICSharpCode.CodeConverter.Util.FromRoslyn;
34

45
namespace ICSharpCode.CodeConverter.CSharp;
56

@@ -35,7 +36,9 @@ public async Task<CSharpSyntaxNode> ConvertPropertyStatementAsync(VBSyntax.Prope
3536
var convertibleModifiers = node.Modifiers.Where(m => !m.IsKind(VBasic.SyntaxKind.ReadOnlyKeyword, VBasic.SyntaxKind.WriteOnlyKeyword, VBasic.SyntaxKind.DefaultKeyword));
3637
var modifiers = CommonConversions.ConvertModifiers(node, convertibleModifiers.ToList(), node.GetMemberContext());
3738
var isIndexer = CommonConversions.IsDefaultIndexer(node);
38-
var propSymbol = ModelExtensions.GetDeclaredSymbol(_semanticModel, node) as IPropertySymbol;
39+
IPropertySymbol propSymbol = node.Parent is VBSyntax.PropertyBlockSyntax pb
40+
? _semanticModel.GetDeclaredSymbol(pb)
41+
: _semanticModel.GetDeclaredSymbol(node);
3942
var accessedThroughMyClass = IsAccessedThroughMyClass(node, node.Identifier, propSymbol);
4043

4144
var directlyConvertedCsIdentifier = CommonConversions.CsEscapedIdentifier(node.Identifier.Value as string);
@@ -444,15 +447,28 @@ public CSSyntax.BlockSyntax WithImplicitReturnStatements(VBSyntax.MethodBlockBas
444447
CSSyntax.IdentifierNameSyntax csReturnVariableOrNull)
445448
{
446449
if (!node.MustReturn()) return convertedStatements;
447-
if (_semanticModel.GetDeclaredSymbol(node) is { } ms && ms.ReturnsVoidOrAsyncTask()) {
450+
var methodSymbol = node switch {
451+
VBSyntax.MethodBlockSyntax mb => _semanticModel.GetDeclaredSymbol(mb.SubOrFunctionStatement),
452+
VBSyntax.AccessorBlockSyntax ab => _semanticModel.GetDeclaredSymbol(ab.AccessorStatement),
453+
_ => _semanticModel.GetDeclaredSymbol(node)
454+
} as IMethodSymbol;
455+
if (methodSymbol?.ReturnsVoidOrAsyncTask() == true) {
448456
return convertedStatements;
449457
}
450458

451459

452460
var preBodyStatements = new List<CSSyntax.StatementSyntax>();
453461
var postBodyStatements = new List<CSSyntax.StatementSyntax>();
454462

455-
var functionSym = ModelExtensions.GetDeclaredSymbol(_semanticModel, node);
463+
var symbol = node switch {
464+
VBSyntax.MethodBlockSyntax mb => _semanticModel.GetDeclaredSymbol(mb.SubOrFunctionStatement),
465+
VBSyntax.AccessorBlockSyntax ab => _semanticModel.GetDeclaredSymbol(ab.AccessorStatement),
466+
_ => null
467+
};
468+
var functionSym = symbol switch {
469+
IMethodSymbol ms => ms,
470+
_ => _semanticModel.GetDeclaredSymbol(node) as IMethodSymbol
471+
};
456472
if (functionSym != null) {
457473
var returnType = CommonConversions.GetTypeSyntax(functionSym.GetReturnType());
458474

CodeConverter/CSharp/BuiltInVisualBasicOperatorSubstitutions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ private async Task<ExpressionSyntax> ConvertMyGroupCollectionPropertyGetWithUnde
9090
switch (operation) {
9191
case IConversionOperation co:
9292
return await ConvertMyGroupCollectionPropertyGetWithUnderlyingFieldAsync(co.Operand.Syntax);
93-
case IPropertyReferenceOperation pro when pro.Property.IsMyGroupCollectionProperty():
94-
var associatedField = pro.Property.GetAssociatedField();
93+
case IPropertyReferenceOperation pro when pro.Property.IsMyGroupCollectionProperty:
94+
var associatedField = pro.Property.AssociatedField;
9595
var propertyReferenceSyntax = (VisualBasicSyntaxNode)((IPropertyReferenceOperation)pro.Instance).Syntax;
9696
var qualification = await propertyReferenceSyntax.AcceptAsync<ExpressionSyntax>(_triviaConvertingVisitor);
9797
return SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, qualification, ValidSyntaxFactory.IdentifierName(associatedField.Name));
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System.Diagnostics;
2+
3+
namespace ICSharpCode.CodeConverter.CSharp;
4+
5+
internal class CachedReflectedDelegate<TArg, TResult>
6+
{
7+
private Func<TArg, TResult> _cachedDelegate;
8+
private readonly string _propertyName;
9+
10+
public CachedReflectedDelegate(string propertyName)
11+
{
12+
_propertyName = propertyName;
13+
}
14+
15+
public TResult GetValue(TArg instance)
16+
{
17+
if (_cachedDelegate != null) return _cachedDelegate(instance);
18+
19+
var getDelegate = instance.ReflectedPropertyGetter(_propertyName)
20+
?.CreateOpenInstanceDelegateForcingType<TArg, TResult>();
21+
if (getDelegate == null) {
22+
Debug.Fail($"Delegate not found for {instance.GetType()}");
23+
return default;
24+
}
25+
26+
_cachedDelegate = getDelegate;
27+
return _cachedDelegate(instance);
28+
}
29+
30+
public TResult GetValueOrDefault(TArg instance, TResult defaultValue = default)
31+
{
32+
try {
33+
return GetValue(instance);
34+
} catch (Exception) {
35+
return defaultValue;
36+
}
37+
}
38+
}
Lines changed: 27 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,51 @@
11
using System.Collections;
2-
using System.Diagnostics;
32

43
namespace ICSharpCode.CodeConverter.CSharp;
54

65
/// <summary>
76
/// Use and add to this class with great care. There is great potential for issues in different VS versions if something private/internal got renamed.
87
/// </summary>
98
/// <remarks>
10-
/// Lots of cases need a method and a backing field in a way that's awkward to wrap into a single object, so I've put them adjacent for easy reference.
9+
/// Each extension block groups extension methods by type, with the cached delegates stored in the containing class.
1110
/// I've opted for using the minimal amount of hardcoded internal names (instead getting the runtime type of the argument) in the hope of being resilient against a simple rename.
1211
/// However, if extra subclasses are created and there's no common base type containing the property, or the property itself is renamed, this will obviously break.
1312
/// It'd be great to run a CI build configuration that updates all nuget packages to the latest prerelease and runs all the tests (this would also catch other breaking API changes before they hit).
1413
/// </remarks>
1514
internal static class CachedReflectedDelegates
1615
{
17-
public static bool IsMyGroupCollectionProperty(this IPropertySymbol declaredSymbol) =>
18-
GetCachedReflectedPropertyDelegate(declaredSymbol, "IsMyGroupCollectionProperty", ref _isMyGroupCollectionProperty);
19-
private static Func<ISymbol, bool> _isMyGroupCollectionProperty;
16+
private static CachedReflectedDelegate<ISymbol, bool> IsMyGroupCollectionPropertyDelegate { get; } =
17+
new CachedReflectedDelegate<ISymbol, bool>("IsMyGroupCollectionProperty");
2018

21-
public static ISymbol GetAssociatedField(this IPropertySymbol declaredSymbol) =>
22-
GetCachedReflectedPropertyDelegate(declaredSymbol, "AssociatedField", ref _associatedField);
23-
private static Func<ISymbol, ISymbol> _associatedField;
19+
private static CachedReflectedDelegate<ISymbol, ISymbol> AssociatedFieldDelegate { get; } =
20+
new CachedReflectedDelegate<ISymbol, ISymbol>("AssociatedField");
2421

25-
public static SyntaxTree GetEmbeddedSyntaxTree(this Location loc) =>
26-
GetCachedReflectedPropertyDelegate(loc, "PossiblyEmbeddedOrMySourceTree", ref _possiblyEmbeddedOrMySourceTree);
27-
private static Func<Location, SyntaxTree> _possiblyEmbeddedOrMySourceTree;
28-
29-
public static bool GetIsUsing(this ILocalSymbol l)
22+
extension(IPropertySymbol declaredSymbol)
3023
{
31-
try {
32-
return GetCachedReflectedPropertyDelegate(l, "IsUsing", ref _isUsing);
33-
} catch (Exception) {
34-
return false;
35-
}
36-
}
37-
38-
private static Func<ILocalSymbol, bool> _isUsing;
39-
24+
public bool IsMyGroupCollectionProperty => IsMyGroupCollectionPropertyDelegate.GetValue(declaredSymbol);
4025

26+
public ISymbol AssociatedField => AssociatedFieldDelegate.GetValue(declaredSymbol);
27+
}
4128

42-
/// <remarks>Unfortunately the roslyn UnassignedVariablesWalker and all useful collections created from it are internal only
43-
/// Other attempts using DataFlowsIn on each reference showed that "DataFlowsIn" even from an uninitialized variable (at least in the case of ints)
44-
/// https://github.com/dotnet/roslyn/blob/007022c37c6d21ee100728954bd75113e0dfe4bd/src/Compilers/VisualBasic/Portable/Analysis/FlowAnalysis/UnassignedVariablesWalker.vb#L15
45-
/// It'd be possible to see the result of the diagnostic analysis, but that would miss out value types, which don't cause a warning in VB
46-
/// PERF: Assume we'll only be passed one type of data flow analysis (VisualBasicDataFlowAnalysis)
47-
/// </remarks>
48-
public static IEnumerable<ISymbol> GetVbUnassignedVariables(this DataFlowAnalysis methodFlow) =>
49-
GetCachedReflectedPropertyDelegate(methodFlow, "UnassignedVariables", ref _vbUnassignedVariables).Cast<ISymbol>();
50-
private static Func<DataFlowAnalysis, IEnumerable> _vbUnassignedVariables;
29+
private static CachedReflectedDelegate<Location, SyntaxTree> PossiblyEmbeddedOrMySourceTreeDelegate { get; } =
30+
new CachedReflectedDelegate<Location, SyntaxTree>("PossiblyEmbeddedOrMySourceTree");
5131

52-
private static TDesiredTarget GetCachedReflectedPropertyDelegate<TDesiredArg, TDesiredTarget>(TDesiredArg instance, string propertyToAccess,
53-
ref Func<TDesiredArg, TDesiredTarget> cachedDelegate)
32+
extension(Location loc)
5433
{
55-
if (cachedDelegate != null) return cachedDelegate(instance);
34+
public SyntaxTree EmbeddedSyntaxTree => PossiblyEmbeddedOrMySourceTreeDelegate.GetValue(loc);
35+
}
5636

57-
var getDelegate = instance.ReflectedPropertyGetter(propertyToAccess)
58-
?.CreateOpenInstanceDelegateForcingType<TDesiredArg, TDesiredTarget>();
59-
if (getDelegate == null) {
60-
Debug.Fail($"Delegate not found for {instance.GetType()}");
61-
return default;
62-
}
37+
private static CachedReflectedDelegate<DataFlowAnalysis, IEnumerable<ISymbol>> VbUnassignedVariablesDelegate { get; } =
38+
new CachedReflectedDelegate<DataFlowAnalysis, IEnumerable<ISymbol>>("UnassignedVariables");
6339

64-
cachedDelegate = getDelegate;
65-
return cachedDelegate(instance);
40+
extension(DataFlowAnalysis methodFlow)
41+
{
42+
/// <remarks>Unfortunately the roslyn UnassignedVariablesWalker and all useful collections created from it are internal only
43+
/// Other attempts using DataFlowsIn on each reference showed that "DataFlowsIn" even from an uninitialized variable (at least in the case of ints)
44+
/// https://github.com/dotnet/roslyn/blob/007022c37c6d21ee100728954bd75113e0dfe4bd/src/Compilers/VisualBasic/Portable/Analysis/FlowAnalysis/UnassignedVariablesWalker.vb#L15
45+
/// It'd be possible to see the result of the diagnostic analysis, but that would miss out value types, which don't cause a warning in VB
46+
/// PERF: Assume we'll only be passed one type of data flow analysis (VisualBasicDataFlowAnalysis)
47+
/// </remarks>
48+
public IEnumerable<ISymbol> VbUnassignedVariables =>
49+
VbUnassignedVariablesDelegate.GetValue(methodFlow);
6650
}
67-
}
51+
}

CodeConverter/CSharp/CommonConversions.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.CodeAnalysis.FindSymbols;
99
using Microsoft.CodeAnalysis.Operations;
1010
using Microsoft.CodeAnalysis.Simplification;
11+
using static Microsoft.CodeAnalysis.VisualBasic.VisualBasicExtensions;
1112
using ArgumentListSyntax = Microsoft.CodeAnalysis.VisualBasic.Syntax.ArgumentListSyntax;
1213
using ArrayRankSpecifierSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.ArrayRankSpecifierSyntax;
1314
using ArrayTypeSyntax = Microsoft.CodeAnalysis.CSharp.Syntax.ArrayTypeSyntax;
@@ -598,7 +599,12 @@ private async Task<ExpressionSyntax> GetParameterizedSetterArgAsync(IOperation o
598599
public CSSyntax.IdentifierNameSyntax GetRetVariableNameOrNull(VBSyntax.MethodBlockBaseSyntax node)
599600
{
600601
if (!node.MustReturn()) return null;
601-
if (SemanticModel.GetDeclaredSymbol(node) is IMethodSymbol ms && ms.ReturnsVoidOrAsyncTask()) {
602+
var methodSymbol = node switch {
603+
VBSyntax.MethodBlockSyntax mb => SemanticModel.GetDeclaredSymbol(mb.SubOrFunctionStatement),
604+
VBSyntax.AccessorBlockSyntax ab => SemanticModel.GetDeclaredSymbol(ab.AccessorStatement),
605+
_ => SemanticModel.GetDeclaredSymbol(node)
606+
} as IMethodSymbol;
607+
if (methodSymbol?.ReturnsVoidOrAsyncTask() == true) {
602608
return null;
603609
}
604610

0 commit comments

Comments
 (0)