icsharpcode / ILSpy

.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
21.45k stars 3.35k forks source link

[3.0 Preview 1] F# code decompiles to ... unoptimal C# #888

Closed 0x53A closed 6 years ago

0x53A commented 7 years ago

EDIT: changed the samples

The following F# code

open System

let disposable() = { new IDisposable with member x.Dispose() = () }

let getSeq() = seq { yield 1; }
let getList() = [ 1 ]
let getArray() = [| 1 |]

[<EntryPoint>]
let main argv =

    // nested using scopes?
    use disp1 =
        use disp2 = disposable()
        Console.WriteLine "Hello 1"
        disposable()

    // for loop over seq
    for i in getSeq() do
        Console.WriteLine i

    // for loop over list
    for i in getList() do
        Console.WriteLine i

    // for loop over array
    for i in getArray() do
        Console.WriteLine i

    0 // return an integer exit code

compiles in release to this assembly ...

FscILSpyTest.exe.txt

which decompiles to this C#:

// Program
using Microsoft.FSharp.Collections;
using Microsoft.FSharp.Core;
using System;
using System.Collections.Generic;

[EntryPoint]
public static int main(string[] argv)
{
    IDisposable disp5 = Program.disposable();
    IDisposable disposable;
    try
    {
        Console.WriteLine("Hello 1");
        disposable = Program.disposable();
    }
    finally
    {
        IDisposable disposable2 = disp5 as IDisposable;
        if (disposable2 != null)
        {
            disposable2.Dispose();
        }
        else
        {
        }
    }
    IDisposable disp4 = disposable;
    try
    {
        IEnumerable<int> seq = Program.getSeq();
        IEnumerator<int> enumerator = seq.GetEnumerator();
        Unit unit;
        try
        {
            while (true)
            {
                if (!enumerator.MoveNext())
                    break;
                Console.WriteLine(enumerator.Current);
            }
            unit = null;
        }
        finally
        {
            disp5 = (enumerator as IDisposable);
            if (disp5 != null)
            {
                disp5.Dispose();
            }
            else
            {
            }
        }
        Unit _ = unit;
        FSharpList<int> fSharpList = FSharpList<int>.Cons(1, FSharpList<int>.Empty);
        FSharpList<int> tailOrNull = fSharpList.TailOrNull;
        while (true)
        {
            if (tailOrNull == null)
                break;
            int j = fSharpList.HeadOrDefault;
            Console.WriteLine(j);
            fSharpList = tailOrNull;
            tailOrNull = fSharpList.TailOrNull;
        }
        int[] array = new int[1]
        {
            1
        };
        for (int j = 0; j < array.Length; j++)
        {
            Console.WriteLine(array[j]);
        }
        return 0;
    }
    finally
    {
        disp5 = (disp4 as IDisposable);
        if (disp5 != null)
        {
            disp5.Dispose();
        }
        else
        {
        }
    }
}

1) It would be dope if it decompiled a F# use to a C# using. I added this transformation to the old decompiler engine: https://github.com/icsharpcode/ILSpy/pull/671 1) It would be cool if it emitted a foreach for all of seq/list/array. 2) It would be nice if it dropped the empty else blocks 3) the C# is invalid due to the variable naming: Unit _ = unit; and _ = unit;.

siegfriedpammer commented 7 years ago

ad 1. This is likely because of the empty else branch, but might need further adjustments. ad 2. Likely the same problem as foreach currently depends on the UsingTransform ad 3. Yes ad 4. I don't see the point of this at all... is this dead code?

0x53A commented 7 years ago

The Unit code is dead code.

Unit in F# is kind of like void in C# ... but it actually has a value. That means there is no need for a differentiation between Action and Func. Action is just Func<Unit>.

The compiler should normally erase unit to void, but in generic code, remnants of unit are sometimes left.

siegfriedpammer commented 6 years ago

F# using is now properly decompiled again (see #900). The remaining problems need some adjustments to the loop detection. @dgrunwald would be nice if you could take over from here.