Skip to content

Commit bf98217

Browse files
Improvements to for loop with missing semantic info - fixes #482
1 parent 9f225f2 commit bf98217

File tree

6 files changed

+99
-3
lines changed

6 files changed

+99
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
1313
* Improve multi-declaration field conversion for arrrays - [#559](https://github.com/icsharpcode/CodeConverter/issues/559)
1414
* Add parentheses around ternary statement - [#565](https://github.com/icsharpcode/CodeConverter/issues/565)
1515
* When converting ForEach loop, avoid duplicate variable compilation issue [#558](https://github.com/icsharpcode/CodeConverter/issues/558)
16+
* Improvements to for loop with missing semantic info - [#482](https://github.com/icsharpcode/CodeConverter/issues/482)
1617

1718
### C# -> VB
1819

CodeConverter/CSharp/MethodBodyExecutableStatementVisitor.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,8 @@ public override async Task<SyntaxList<StatementSyntax>> VisitForBlock(VBSyntax.F
462462
VariableDeclarationSyntax declaration = null;
463463
ExpressionSyntax id;
464464
var controlVarOp = _semanticModel.GetOperation(stmt.ControlVariable) as IVariableDeclaratorOperation;
465-
var controlVarType = controlVarOp?.Symbol.Type;
465+
var controlVarSymbol = controlVarOp?.Symbol;
466+
var controlVarType = controlVarSymbol?.Type;
466467
var initializers = new List<ExpressionSyntax>();
467468
if (stmt.ControlVariable is VBSyntax.VariableDeclaratorSyntax) {
468469
var v = (VBSyntax.VariableDeclaratorSyntax)stmt.ControlVariable;
@@ -471,9 +472,14 @@ public override async Task<SyntaxList<StatementSyntax>> VisitForBlock(VBSyntax.F
471472
id = SyntaxFactory.IdentifierName(declaration.Variables[0].Identifier);
472473
} else {
473474
id = (ExpressionSyntax) await stmt.ControlVariable.AcceptAsync(_expressionVisitor);
474-
var controlVarSymbol = controlVarOp?.Symbol;
475+
476+
// If missing semantic info, the compiler just guesses object. In this branch there was no explicit type, so let's try to improve on that guess:
477+
var bestType = controlVarType.Yield()
478+
.Concat(new[] { stmt.FromValue, stmt.ToValue, stmt.StepClause?.StepValue }.Select(exp => _semanticModel.GetTypeInfo(exp).Type))
479+
.FirstOrDefault(t => t != null && t.SpecialType != SpecialType.System_Object);
480+
475481
if (controlVarSymbol != null && controlVarSymbol.DeclaringSyntaxReferences.Any(r => r.Span.OverlapsWith(stmt.ControlVariable.Span))) {
476-
declaration = CommonConversions.CreateVariableDeclarationAndAssignment(controlVarSymbol.Name, startValue, CommonConversions.GetTypeSyntax(controlVarType));
482+
declaration = CommonConversions.CreateVariableDeclarationAndAssignment(controlVarSymbol.Name, startValue, CommonConversions.GetTypeSyntax(bestType));
477483
} else {
478484
startValue = SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, id, startValue);
479485
initializers.Add(startValue);

CodeConverter/Util/EnumerableExtensions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,18 @@ public static T OnlyOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predi
7777
return count == 1 ? previous : default(T);
7878
}
7979

80+
public static T SelectFirst<T>(this IEnumerable<T> source, Func<T, bool> predicate = null)
81+
{
82+
if (predicate != null) source = source.Where(predicate);
83+
T previous = default(T);
84+
int count = 0;
85+
foreach (var element in source) {
86+
previous = element;
87+
if (++count > 1) return default(T);
88+
}
89+
return count == 1 ? previous : default(T);
90+
}
91+
8092
public static IEnumerable<T> Yield<T>(this T singleElement)
8193
{
8294
yield return singleElement;

Tests/CSharp/MissingSemanticModelInfo/ExpressionTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ private void TestMethod()
3232
1 target compilation errors:
3333
CS0234: The type or namespace name 'Some' does not exist in the namespace 'System' (are you missing an assembly reference?)");
3434
}
35+
3536
[Fact]
3637
public async Task InvokeMethodWithUnknownReturnTypeAsync()
3738
{
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using ICSharpCode.CodeConverter.Tests.TestRunners;
7+
using Xunit;
8+
9+
namespace ICSharpCode.CodeConverter.Tests.CSharp.MissingSemanticModelInfo
10+
{
11+
public class StatementTests : ConverterTestBase
12+
{
13+
[Fact]
14+
public async Task MissingLoopTypeAsync()
15+
{
16+
await TestConversionVisualBasicToCSharpAsync(@"Class MissingLoopType
17+
Public Sub Test()
18+
Dim x As Asadf = Nothing
19+
20+
For i = 1 To x.SomeInteger
21+
22+
Next
23+
End Sub
24+
End Class", @"
25+
internal partial class MissingLoopType
26+
{
27+
public void Test()
28+
{
29+
Asadf x = default;
30+
for (int i = 1, loopTo = x.SomeInteger; i <= loopTo; i++)
31+
{
32+
}
33+
}
34+
}
35+
1 source compilation errors:
36+
BC30002: Type 'Asadf' is not defined.
37+
1 target compilation errors:
38+
CS0246: The type or namespace name 'Asadf' could not be found (are you missing a using directive or an assembly reference?)", missingSemanticInfo: true);
39+
}
40+
}
41+
}

Tests/CSharp/StatementTests/LoopStatementTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,41 @@ public void PrintLoop(int startIndex, int endIndex, int step)
379379
}");
380380
}
381381

382+
[Fact]
383+
public async Task ForEnumAsync()
384+
{
385+
await TestConversionVisualBasicToCSharpAsync(@"Friend Enum MyEnum
386+
Zero,
387+
One
388+
End Enum
389+
390+
Friend Class ForEnumAsync
391+
Sub PrintLoop(startIndex As MyEnum, endIndex As MyEnum, [step] As MyEnum)
392+
For i = startIndex To endIndex Step [step]
393+
Debug.WriteLine(i)
394+
Next
395+
For i2 As MyEnum = startIndex To endIndex Step [step]
396+
Debug.WriteLine(i2)
397+
Next
398+
For i3 As MyEnum = startIndex To endIndex Step 3
399+
Debug.WriteLine(i3)
400+
Next
401+
For i4 As MyEnum = startIndex To 4
402+
Debug.WriteLine(i4)
403+
Next
404+
End Sub
405+
End Class", @"using System.Diagnostics;
406+
407+
internal partial class ForEnumAsync
408+
{
409+
public void PrintLoop(int startIndex, int endIndex, int step)
410+
{
411+
for (int i = startIndex, loopTo = endIndex; step >= 0 ? i <= loopTo : i >= loopTo; i += step)
412+
Debug.WriteLine(i);
413+
}
414+
}");
415+
}
416+
382417
[Fact]
383418
public async Task ForeachWithObjectCollectionAsync()
384419
{

0 commit comments

Comments
 (0)