Skip to content

Commit c8b6a12

Browse files
committed
Translating Goto statements which go to the final statement in a block into return statements
1 parent 3bf3787 commit c8b6a12

File tree

4 files changed

+93
-24
lines changed

4 files changed

+93
-24
lines changed

NextVersion.txt

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +0,0 @@
1-
Goto return label:
2-
3-
if (products != Enumerable<Product>.Empty)
4-
{
5-
goto Return;
6-
}
7-
8-
Select on Constant DbSet<Person>:
9-
10-
Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[Phoneta.Domain.People.Person]
11-
.Select(

ReadableExpressions.UnitTests/WhenTranslatingGotos.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,37 @@ public void ShouldUnindentGotoTargetLabels()
9292
Assert.AreEqual(EXPECTED.TrimStart(), translated);
9393
}
9494

95+
[TestMethod]
96+
public void ShouldTranslateAGotoReturnStatement()
97+
{
98+
var returnTarget = Expression.Label(typeof(int), "Return");
99+
100+
var numberParameter = Expression.Parameter(typeof(string), "i");
101+
var numberEqualsOne = Expression.Equal(numberParameter, Expression.Constant("One"));
102+
var returnOne = Expression.Goto(returnTarget, Expression.Constant(1));
103+
var ifOneReturnOne = Expression.IfThen(numberEqualsOne, returnOne);
104+
105+
var returnLabel = Expression.Label(returnTarget, Expression.Constant(0));
106+
var gotoBlock = Expression.Block(ifOneReturnOne, returnLabel);
107+
108+
var gotoLambda = Expression.Lambda<Func<string, int>>(gotoBlock, numberParameter);
109+
gotoLambda.Compile();
110+
111+
var translated = gotoLambda.ToReadableString();
112+
113+
const string EXPECTED = @"
114+
i =>
115+
{
116+
if (i == ""One"")
117+
{
118+
return 1;
119+
}
120+
121+
return 0;
122+
}";
123+
Assert.AreEqual(EXPECTED.TrimStart(), translated);
124+
}
125+
95126
[TestMethod]
96127
public void ShouldTranslateAReturnStatementWithAValue()
97128
{
@@ -228,7 +259,6 @@ public void ShouldTranslateALabelWithABlockDefaultValue()
228259
229260
return i;
230261
};";
231-
232262
var translated = returnBlock.ToReadableString();
233263

234264
Assert.AreEqual(EXPECTED.TrimStart(), translated);

ReadableExpressions/TranslationContext.cs

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,7 @@ private static TranslationSettings GetTranslationSettings(
7171
/// </summary>
7272
/// <param name="expression">The <see cref="Expression"/> to translate.</param>
7373
/// <returns>A source code translation of the given <paramref name="expression"/>.</returns>
74-
public string Translate(Expression expression)
75-
{
76-
return _globalTranslator.Invoke(expression, this);
77-
}
74+
public string Translate(Expression expression) => _globalTranslator.Invoke(expression, this);
7875

7976
internal string TranslateAsCodeBlock(Expression expression)
8077
{
@@ -130,7 +127,7 @@ internal ParameterSet TranslateParameters(
130127
public bool IsNotJoinedAssignment(Expression expression)
131128
{
132129
return (expression.NodeType != ExpressionType.Assign) ||
133-
!_analyzer.JoinedAssignments.Contains(expression);
130+
!_analyzer.JoinedAssignments.Contains(expression);
134131
}
135132

136133
/// <summary>
@@ -143,9 +140,19 @@ public bool IsNotJoinedAssignment(Expression expression)
143140
/// otherwise false.
144141
/// </returns>
145142
public bool IsReferencedByGoto(LabelTarget labelTarget)
146-
{
147-
return _analyzer.NamedLabelTargets.Contains(labelTarget);
148-
}
143+
=> _analyzer.NamedLabelTargets.Contains(labelTarget);
144+
145+
/// <summary>
146+
/// Returns a value indicating whether the given <paramref name="goto"/> goes to the
147+
/// final statement in a block, and so should be rendered as a return statement.
148+
/// </summary>
149+
/// <param name="goto">The GotoExpression for which to make the determination.</param>
150+
/// <returns>
151+
/// True if the given <paramref name="goto"/> goes to the final statement in a block,
152+
/// otherwise false.
153+
/// </returns>
154+
public bool GoesToReturnLabel(GotoExpression @goto)
155+
=> _analyzer.GotoReturnGotos.Contains(@goto);
149156

150157
/// <summary>
151158
/// Returns a value indicating whether the given <paramref name="methodCall"/> is part of a chain
@@ -161,11 +168,14 @@ public bool IsPartOfMethodCallChain(Expression methodCall)
161168
return _analyzer.ChainedMethodCalls.Contains(methodCall);
162169
}
163170

171+
#region Helper Class
172+
164173
private class ExpressionAnalysisVisitor : ExpressionVisitor
165174
{
166175
private readonly Dictionary<BinaryExpression, object> _constructsByAssignment;
167176
private readonly List<ParameterExpression> _accessedVariables;
168177
private readonly List<Expression> _assignedAssignments;
178+
private readonly Stack<BlockExpression> _blocks;
169179
private readonly Stack<object> _constructs;
170180

171181
private ExpressionAnalysisVisitor()
@@ -176,7 +186,9 @@ private ExpressionAnalysisVisitor()
176186
JoinedAssignments = new List<BinaryExpression>();
177187
_assignedAssignments = new List<Expression>();
178188
NamedLabelTargets = new List<LabelTarget>();
189+
GotoReturnGotos = new List<GotoExpression>();
179190
ChainedMethodCalls = new List<MethodCallExpression>();
191+
_blocks = new Stack<BlockExpression>();
180192
_constructs = new Stack<object>();
181193
}
182194

@@ -226,6 +238,8 @@ private static Expression GetCoreExpression(Expression expression)
226238

227239
public ICollection<LabelTarget> NamedLabelTargets { get; }
228240

241+
public ICollection<GotoExpression> GotoReturnGotos { get; }
242+
229243
public List<MethodCallExpression> ChainedMethodCalls { get; }
230244

231245
protected override Expression VisitParameter(ParameterExpression variable)
@@ -263,6 +277,17 @@ protected override Expression VisitParameter(ParameterExpression variable)
263277
return base.VisitParameter(variable);
264278
}
265279

280+
protected override Expression VisitBlock(BlockExpression block)
281+
{
282+
_blocks.Push(block);
283+
284+
var result = base.VisitBlock(block);
285+
286+
_blocks.Pop();
287+
288+
return result;
289+
}
290+
266291
protected override Expression VisitBinary(BinaryExpression binary)
267292
{
268293
if ((binary.NodeType == ExpressionType.Assign) &&
@@ -320,11 +345,27 @@ private void AddAssignmentIfAppropriate(Expression assignedValue)
320345

321346
protected override Expression VisitGoto(GotoExpression @goto)
322347
{
323-
if (@goto.Kind == GotoExpressionKind.Goto)
348+
if (@goto.Kind != GotoExpressionKind.Goto)
349+
{
350+
return base.VisitGoto(@goto);
351+
}
352+
353+
var currentBlockFinalExpression = _blocks.Peek()?.Expressions.Last();
354+
355+
if (currentBlockFinalExpression?.NodeType == ExpressionType.Label)
324356
{
325-
NamedLabelTargets.Add(@goto.Target);
357+
var returnLabel = (LabelExpression)currentBlockFinalExpression;
358+
359+
if (@goto.Target == returnLabel.Target)
360+
{
361+
GotoReturnGotos.Add(@goto);
362+
363+
return base.VisitGoto(@goto);
364+
}
326365
}
327366

367+
NamedLabelTargets.Add(@goto.Target);
368+
328369
return base.VisitGoto(@goto);
329370
}
330371

@@ -398,5 +439,7 @@ private TResult VisitConstruct<TArg, TResult>(TArg expression, Func<TArg, TResul
398439

399440
#endregion
400441
}
442+
443+
#endregion
401444
}
402445
}

ReadableExpressions/Translators/GotoExpressionTranslator.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,15 @@ private static string TranslateBreak(GotoExpression breakExpression, Translation
3333
private static string TranslateContinue(GotoExpression continueExpression, TranslationContext context)
3434
=> "continue;";
3535

36-
private static string TranslateGoto(GotoExpression gotoExpression, TranslationContext context)
37-
=> $"goto {gotoExpression.Target.Name};";
36+
private static string TranslateGoto(GotoExpression @goto, TranslationContext context)
37+
{
38+
if (context.GoesToReturnLabel(@goto))
39+
{
40+
return TranslateReturn(@goto, context);
41+
}
42+
43+
return $"goto {@goto.Target.Name};";
44+
}
3845

3946
private static string TranslateReturn(GotoExpression returnExpression, TranslationContext context)
4047
{

0 commit comments

Comments
 (0)