Skip to content

Commit ca624cb

Browse files
Lots of WIP for generating a local function
1 parent 669b604 commit ca624cb

3 files changed

Lines changed: 111 additions & 35 deletions

File tree

CodeConverter/CSharp/CommonConversions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,11 @@ public static AttributeArgumentListSyntax CreateAttributeArgumentList(params Att
508508
return SyntaxFactory.AttributeArgumentList(SyntaxFactory.SeparatedList(attributeArgumentSyntaxs));
509509
}
510510

511+
public static CSSyntax.LocalDeclarationStatementSyntax CreateLocalVariableDeclarationAndAssignment(string variableName, ExpressionSyntax initValue)
512+
{
513+
return SyntaxFactory.LocalDeclarationStatement(CreateVariableDeclarationAndAssignment(variableName, initValue));
514+
}
515+
511516
public static VariableDeclarationSyntax CreateVariableDeclarationAndAssignment(string variableName,
512517
ExpressionSyntax initValue, TypeSyntax explicitType = null)
513518
{

CodeConverter/CSharp/ExpressionNodeVisitor.cs

Lines changed: 105 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -408,44 +408,111 @@ public override async Task<CSharpSyntaxNode> VisitSimpleArgument(VBasic.Syntax.S
408408
SyntaxToken token = default(SyntaxToken);
409409
string argName = null;
410410
RefKind refKind = RefKind.None;
411+
AdditionalLocal local = null;
411412
if (symbol is IMethodSymbol methodSymbol) {
412413
var parameters = (CommonConversions.GetCsOriginalSymbolOrNull(methodSymbol.OriginalDefinition) ?? methodSymbol).GetParameters();
413-
var parameter = !node.IsNamed ? parameters.ElementAtOrDefault(argList.Arguments.IndexOf(node)) : parameters.FirstOrDefault(p => p.Name.Equals(node.NameColonEquals.Name.Identifier.Text, StringComparison.OrdinalIgnoreCase));
414-
if (parameter != null) {
415-
refKind = parameter.RefKind;
416-
argName = parameter.Name;
417-
}
418-
switch (refKind) {
419-
case RefKind.None:
420-
token = default(SyntaxToken);
421-
break;
422-
case RefKind.Ref:
423-
token = SyntaxFactory.Token(SyntaxKind.RefKeyword);
424-
break;
425-
case RefKind.Out:
426-
token = SyntaxFactory.Token(SyntaxKind.OutKeyword);
427-
break;
428-
default:
429-
throw new ArgumentOutOfRangeException();
414+
var refType = GetRefType(node, argList, parameters, out argName, out refKind);
415+
var expression = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, (ExpressionSyntax)await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor), defaultToCast: refKind != RefKind.None);
416+
417+
if (refType == RefType.Variable) {
418+
var expressionTypeInfo = _semanticModel.GetTypeInfo(node.Expression);
419+
bool useVar = expressionTypeInfo.Type?.Equals(expressionTypeInfo.ConvertedType) == true && !CommonConversions.ShouldPreferExplicitType(node.Expression, expressionTypeInfo.ConvertedType, out var _);
420+
var typeSyntax = CommonConversions.GetTypeSyntax(expressionTypeInfo.ConvertedType, useVar);
421+
string prefix = $"arg{argName}";
422+
local = _additionalLocals.AddAdditionalLocal(new AdditionalLocal(prefix, expression, typeSyntax));
423+
} else if (refType == RefType.Property && invocation is VBSyntax.InvocationExpressionSyntax ies) {
424+
//TODO: Don't break for multiple byref args in one method
425+
var expressionTypeInfo = _semanticModel.GetTypeInfo(node.Expression);
426+
bool useVar = expressionTypeInfo.Type?.Equals(expressionTypeInfo.ConvertedType) == true && !CommonConversions.ShouldPreferExplicitType(node.Expression, expressionTypeInfo.ConvertedType, out var _);
427+
var typeSyntax = CommonConversions.GetTypeSyntax(expressionTypeInfo.ConvertedType, useVar);
428+
var (localFuncName, functionDecl) = CreateLocalByRefFunction(ies, argName, expression, methodSymbol);
429+
local = _additionalLocals.AddAdditionalLocal(new AdditionalLocal(localFuncName, expression, typeSyntax));
430430
}
431431
}
432-
var expression = CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, (ExpressionSyntax) await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor), defaultToCast: refKind != RefKind.None);
433-
AdditionalLocal local = null;
434-
if (refKind != RefKind.None && NeedsVariableForArgument(node)) {
435-
var expressionTypeInfo = _semanticModel.GetTypeInfo(node.Expression);
436-
bool useVar = expressionTypeInfo.Type?.Equals(expressionTypeInfo.ConvertedType) == true && !CommonConversions.ShouldPreferExplicitType(node.Expression, expressionTypeInfo.ConvertedType, out var _);
437-
var typeSyntax = CommonConversions.GetTypeSyntax(expressionTypeInfo.ConvertedType, useVar);
438-
string prefix = $"arg{argName}";
439-
local = _additionalLocals.AddAdditionalLocal(new AdditionalLocal(prefix, expression, typeSyntax));
432+
433+
switch (refKind) {
434+
case RefKind.None:
435+
token = default(SyntaxToken);
436+
break;
437+
case RefKind.Ref:
438+
token = SyntaxFactory.Token(SyntaxKind.RefKeyword);
439+
break;
440+
case RefKind.Out:
441+
token = SyntaxFactory.Token(SyntaxKind.OutKeyword);
442+
break;
443+
default:
444+
throw new ArgumentOutOfRangeException();
440445
}
441-
var nameColon = node.IsNamed ? SyntaxFactory.NameColon((IdentifierNameSyntax) await node.NameColonEquals.Name.AcceptAsync(TriviaConvertingExpressionVisitor)) : null;
446+
var simpleExpression = await ConvertArgExpression(node, refKind);
447+
448+
var nameColon = node.IsNamed ? SyntaxFactory.NameColon((IdentifierNameSyntax)await node.NameColonEquals.Name.AcceptAsync(TriviaConvertingExpressionVisitor)) : null;
442449
if (local == null) {
443-
return SyntaxFactory.Argument(nameColon, token, expression);
450+
return SyntaxFactory.Argument(nameColon, token, simpleExpression);
444451
} else {
445452
return SyntaxFactory.Argument(nameColon, token, local.IdentifierName);
446453
}
447454
}
448455

456+
private async Task<ExpressionSyntax> ConvertArgExpression(VBSyntax.SimpleArgumentSyntax node, RefKind refKind)
457+
{
458+
return CommonConversions.TypeConversionAnalyzer.AddExplicitConversion(node.Expression, (ExpressionSyntax)await node.Expression.AcceptAsync(TriviaConvertingExpressionVisitor), defaultToCast: refKind != RefKind.None);
459+
}
460+
461+
private RefType GetRefType(VBSyntax.SimpleArgumentSyntax node, VBSyntax.ArgumentListSyntax argList, System.Collections.Immutable.ImmutableArray<IParameterSymbol> parameters, out string argName, out RefKind refKind)
462+
{
463+
var parameter = !node.IsNamed ? parameters.ElementAtOrDefault(argList.Arguments.IndexOf(node)) : parameters.FirstOrDefault(p => p.Name.Equals(node.NameColonEquals.Name.Identifier.Text, StringComparison.OrdinalIgnoreCase));
464+
if (parameter != null) {
465+
refKind = parameter.RefKind;
466+
argName = parameter.Name;
467+
} else {
468+
refKind = RefKind.None;
469+
argName = null;
470+
}
471+
return NeedsVariableForArgument(node, refKind);
472+
}
473+
474+
private async Task<(string Id, StatementSyntax FunctionDeclaration)> CreateLocalByRefFunction(VBSyntax.InvocationExpressionSyntax invocation, string argName, ExpressionSyntax expression, IMethodSymbol invocationSymbol)
475+
{
476+
var originalArgsWithRefTypes = invocation.ArgumentList.Arguments
477+
.Select(a => (Arg: (VBSyntax.SimpleArgumentSyntax)a, RefType: GetRefType((VBSyntax.SimpleArgumentSyntax) a, invocation.ArgumentList, invocationSymbol.Parameters, out var argName, out var refKind), Name: argName, RefKind: refKind))
478+
.ToArray();
479+
480+
var argsForCallInLocalFunction = (await originalArgsWithRefTypes.SelectAsync(async a => a.RefType switch {
481+
RefType.Inline => await ConvertArgExpression(a.Arg, a.RefKind),
482+
_ => SyntaxFactory.IdentifierName(a.Name)
483+
})).Select(SyntaxFactory.Argument).ToArray();
484+
var argList = SyntaxFactory.ArgumentList(GetAdditionalRequiredArgs(invocationSymbol, argsForCallInLocalFunction));
485+
var tmpArgName = $"arg{argName}";
486+
var tempArg = SyntaxFactory.IdentifierName(tmpArgName);
487+
var localFuncName = $"local{invocationSymbol.Name}";
488+
const string retVariableName = "ret";
489+
var localFuncId = SyntaxFactory.IdentifierName(localFuncName);
490+
var declareTemp = CommonConversions.CreateLocalVariableDeclarationAndAssignment(tmpArgName, expression);
491+
// Need essentially the original invocation with any byref args swapped out for temporaries
492+
var callAndStoreResult = CommonConversions.CreateLocalVariableDeclarationAndAssignment(retVariableName,
493+
SyntaxFactory.InvocationExpression(expression,
494+
);
495+
var block = SyntaxFactory.Block(
496+
declareTemp,
497+
callAndStoreResult,
498+
AssignStmt(expression, tempArg),
499+
SyntaxFactory.ReturnStatement(SyntaxFactory.IdentifierName(retVariableName))
500+
);
501+
var localfunction = SyntaxFactory.LocalFunctionStatement(CommonConversions.GetTypeSyntax(invocationSymbol.ReturnType),
502+
localFuncId.Identifier).WithBody(block);
503+
return (localFuncName, localfunction);
504+
}
505+
506+
private static StatementSyntax AssignStmt(ExpressionSyntax left, IdentifierNameSyntax right)
507+
{
508+
return SyntaxFactory.ExpressionStatement(Assign(left, right));
509+
}
510+
511+
private static AssignmentExpressionSyntax Assign(ExpressionSyntax left, IdentifierNameSyntax right)
512+
{
513+
return SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, left, right);
514+
}
515+
449516
public override async Task<CSharpSyntaxNode> VisitNameOfExpression(VBasic.Syntax.NameOfExpressionSyntax node)
450517
{
451518
return SyntaxFactory.InvocationExpression(NameOf(), SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList<ArgumentSyntax>(SyntaxFactory.Argument((ExpressionSyntax)await node.Argument.AcceptAsync(TriviaConvertingExpressionVisitor)))));
@@ -1291,7 +1358,7 @@ private ArgumentSyntax CreateOptionalRefArg(IParameterSymbol p)
12911358
return (ArgumentSyntax)CommonConversions.CsSyntaxGenerator.Argument(p.Name, p.RefKind, local.IdentifierName);
12921359
}
12931360

1294-
private bool NeedsVariableForArgument(VBasic.Syntax.SimpleArgumentSyntax node)
1361+
private RefType NeedsVariableForArgument(VBasic.Syntax.SimpleArgumentSyntax node, RefKind refKind)
12951362
{
12961363
bool isIdentifier = node.Expression is VBasic.Syntax.IdentifierNameSyntax;
12971364
bool isMemberAccess = node.Expression is VBasic.Syntax.MemberAccessExpressionSyntax;
@@ -1303,7 +1370,16 @@ private bool NeedsVariableForArgument(VBasic.Syntax.SimpleArgumentSyntax node)
13031370
var typeInfo = _semanticModel.GetTypeInfo(node.Expression);
13041371
bool isTypeMismatch = typeInfo.Type == null || !typeInfo.Type.Equals(typeInfo.ConvertedType);
13051372

1306-
return (!isIdentifier && !isMemberAccess) || isProperty || isTypeMismatch || isUsing;
1373+
if (isProperty) return RefType.Property;
1374+
if ((!isIdentifier && !isMemberAccess) || isTypeMismatch || isUsing) return RefType.Variable;
1375+
return RefType.Inline;
1376+
}
1377+
1378+
private enum RefType
1379+
{
1380+
Inline,
1381+
Variable,
1382+
Property
13071383
}
13081384

13091385
private ISymbol GetInvocationSymbol(SyntaxNode invocation)

CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,7 @@ public override async Task<SyntaxList<StatementSyntax>> VisitSelectBlock(VBSynta
669669
if (forceVariable || !await CanEvaluateMultipleTimesAsync(vbExpr)) {
670670
var contextNode = vbExpr.GetAncestor<VBSyntax.MethodBlockBaseSyntax>() ?? (VBasic.VisualBasicSyntaxNode) vbExpr.Parent;
671671
var varName = GetUniqueVariableNameInScope(contextNode, variableNameBase);
672-
var stmt = CreateLocalVariableDeclarationAndAssignment(varName, expr);
672+
var stmt = CommonConversions.CreateLocalVariableDeclarationAndAssignment(varName, expr);
673673
stmts = stmts.Add(stmt);
674674
exprWithoutSideEffects = SyntaxFactory.IdentifierName(varName);
675675
reusableExprWithoutSideEffects = exprWithoutSideEffects;
@@ -732,11 +732,6 @@ public override async Task<SyntaxList<StatementSyntax>> VisitWithBlock(VBSyntax.
732732
}
733733
}
734734

735-
private LocalDeclarationStatementSyntax CreateLocalVariableDeclarationAndAssignment(string variableName, ExpressionSyntax initValue)
736-
{
737-
return SyntaxFactory.LocalDeclarationStatement(CommonConversions.CreateVariableDeclarationAndAssignment(variableName, initValue));
738-
}
739-
740735
private string GetUniqueVariableNameInScope(VBasic.VisualBasicSyntaxNode node, string variableNameBase)
741736
{
742737
return NameGenerator.GetUniqueVariableNameInScope(_semanticModel, _generatedNames, node, variableNameBase);

0 commit comments

Comments
 (0)