Fable icon indicating copy to clipboard operation
Fable copied to clipboard

Object expression should not inline variable in getter in `Release` mode

Open MangelMaxime opened this issue 1 year ago • 4 comments

Description

[<AbstractClass>]
type Reader() =
    abstract Warnings: ResizeArray<string> with get

let reader =
    let warnins = ResizeArray<string>()
    { new Reader() with
        member __.Warnings = warnins
    }

reader.Warnings.Add("Warning 1")
reader.Warnings.Add("Warning 2")

printfn "Count: %A" reader.Warnings.Count

In watch mode:

export const reader = (() => {
    const warnins = [];
    return new (class extends Reader {
        constructor() {
            super();
        }
        "Glutinum.Web.Main.Reader.get_Warnings"() {
            return warnins;
        }
    }
    )();
})();

In build mode:

export const reader = new (class extends Reader {
    constructor() {
        super();
    }
    "Glutinum.Web.Main.Reader.get_Warnings"() {
        return [];
    }
}
)();

This cause a problem because, now the getter always reset the value of warnings in build mode.

Meaning that in Release mode we get 0 instead of 2.

Expected and actual results

Should returns 2 in Release mode too.

Related information

  • Fable version: 4.14.0
  • Operating system: OSX

MangelMaxime avatar Mar 09 '24 09:03 MangelMaxime

@MangelMaxime Perhaps I'm doing something wrong, but I cannot reproduce with v4.14, with or without --watch.

import { class_type } from "../fable_modules/fable-library-js.4.14.0/Reflection.js";
import { format } from "../fable_modules/fable-library-js.4.14.0/String.js";

export class Reader {
    constructor() {
    }
}

export function Reader_$reflection() {
    return class_type("TestApp.Reader", void 0, Reader);
}

export function Reader_$ctor() {
    return new Reader();
}

export const reader = (() => {
    let warnings = [];
    return new (class extends Reader {
        constructor() {
            super();
        }
        "TestApp.Reader.get_Warnings"() {
            return warnings;
        }
    }
    )();
})();

Can you post your full build command line?

ncave avatar Mar 09 '24 11:03 ncave

@ncave Here are the steps I used to test it out:

  1. Create test.fsx with the following content

    [<AbstractClass>]
    type Reader() =
        abstract Warnings: ResizeArray<string> with get
    
    let reader =
        let warnins = ResizeArray<string>()
        { new Reader() with
            member __.Warnings = warnins
        }
    
    reader.Warnings.Add("Warning 1")
    reader.Warnings.Add("Warning 2")
    
    printfn "Count: %A" reader.Warnings.Count
    
  2. Run dotnet fable test.fsx, it should generates:

import { class_type } from "./fable_modules/fable-library-js.4.14.0/Reflection.js";
import { printf, toConsole } from "./fable_modules/fable-library-js.4.14.0/String.js";

export class Reader {
    constructor() {
    }
}

export function Reader_$reflection() {
    return class_type("Test.Reader", void 0, Reader);
}

export function Reader_$ctor() {
    return new Reader();
}

export const reader = new (class extends Reader {
    constructor() {
        super();
    }
    "Test.Reader.get_Warnings"() {
        return [];
    }
}
)();

void (reader["Test.Reader.get_Warnings"]().push("Warning 1"));

void (reader["Test.Reader.get_Warnings"]().push("Warning 2"));

(function () {
    const arg = reader["Test.Reader.get_Warnings"]().length | 0;
    toConsole(printf("Count: %A"))(arg);
})();

The problematic lines are:

    "Test.Reader.get_Warnings"() {
        return [];
    }

If you run dotnet fable -c Debug now you should get:

import { class_type } from "./fable_modules/fable-library-js.4.14.0/Reflection.js";
import { printf, toConsole } from "./fable_modules/fable-library-js.4.14.0/String.js";

export class Reader {
    constructor() {
    }
}

export function Reader_$reflection() {
    return class_type("Test.Reader", void 0, Reader);
}

export function Reader_$ctor() {
    return new Reader();
}

export const reader = (() => {
    const warnins = [];
    return new (class extends Reader {
        constructor() {
            super();
        }
        "Test.Reader.get_Warnings"() {
            return warnins;
        }
    }
    )();
})();

void (reader["Test.Reader.get_Warnings"]().push("Warning 1"));

void (reader["Test.Reader.get_Warnings"]().push("Warning 2"));

(function () {
    const arg = reader["Test.Reader.get_Warnings"]().length | 0;
    toConsole(printf("Count: %A"))(arg);
})();

Which is "correct".

I tested it on OSX and Linux (dev container) with Fable 4.14 instead from NuGet for both of them.

MangelMaxime avatar Mar 11 '24 10:03 MangelMaxime

@MangelMaxime Probably different default configurations when using a project file vs a script.

In theory an array is mutable so it shouldn't be beta reduced (like other mutables or side effects), we have to see if it's the F# compiler optimizing it, or Fable.

ncave avatar Mar 11 '24 21:03 ncave

@ncave Here I used fsx because it was easier for the reproduction but in my case the problem happens also when using a project file.

Here is a reproduction project:

https://github.com/MangelMaxime/fable-reproduction-issue-3779

If you run dotnet fable you will see that the array has been inlined. But if you run dotnet build -c Debug it is not.

Steps used to setup that repository
  1. dotnet new console -lang F#
  2. dotnet new tool-manifest
  3. dotnet tool install fable
  4. Change the content of Program.fs to the reproduction code (see comments above)
  5. Run dotnet fable and dotnet fable -c Debug

MangelMaxime avatar Mar 12 '24 08:03 MangelMaxime