Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

--precompiledLib switch is broken #3818

Closed
DunetsNM opened this issue May 18, 2024 · 8 comments
Closed

--precompiledLib switch is broken #3818

DunetsNM opened this issue May 18, 2024 · 8 comments

Comments

@DunetsNM
Copy link
Contributor

Description

After upgrading from Fable 3 to Fable 4 I can no longer use --precompileLib switch. I still can precompile the lib but can't link it to my app project, compilation fails.

Repro code

I don't have a short repro steps but you can just try to serialize and deserialize an instance of Fable.AST.Fable.MemberRefInfo via System.Text.Json. It will throw exception (happens rather early during compilation)

error EXCEPTION: Deserialization of interface types is not supported. Type 'Fable.AST.Fable.Attribute'. Path: $[0] | LineNumber: 0 | BytePositionInLine: 2.
at System.Text.Json.ThrowHelper.ThrowNotSupportedException(ReadStack& state, Utf8JsonReader& reader, NotSupportedException ex)

Another problem is Fable.AST.Fable.ValueKind.NumberConstant of obj * NumberKind * info: NumberInfo. Upon deserialization it deserializes a simple Int32 constant as JsonElement (which becomes a problem later during compilation)

Related information

  • Fable version: 4.17.0
  • Operating system: Windows 11
@DunetsNM
Copy link
Contributor Author

opened a PR #3817 to fix those

@DunetsNM DunetsNM changed the title --precompileLib switch is broken --precompiledLib switch is broken May 19, 2024
@DunetsNM
Copy link
Contributor Author

Discovered one more problem with pre-compilation. When a precompiled lib has a member with a numeric parameter adorned with a unit of measure such as size: int<kB>, it can't be resolved in application.

Js error looks like this:

export 'Input__Input_File_Static_Z69237D4C' (imported as 'Input__Input_File_Static_Z69237D4C')
was not found in ...
(possible exports: Input__Input_File_Static_Z2AFD3704)

The reason is because in precompiled code stored in Fable.Precompiled.dll units of measures are erased, which results in a different hash suffix.

Member links successfully when units of measure are removed from parameter types, as well as without --precompiledLib switch

@ncave
Copy link
Collaborator

ncave commented May 20, 2024

@DunetsNM I have to say I'm not very familiar with how the --precompiledLib works, I wonder if it's possible to make a minimal repro with units of measure?

@DunetsNM
Copy link
Contributor Author

@ncave OK will do. I'm experimenting with a possible solution at the moment

@DunetsNM
Copy link
Contributor Author

DunetsNM commented May 21, 2024

@ncave Repro steps for precompile+UoM bug. It's an unlikely (yet ubiquitous in the project I migrate from Fable 3 to Fable 4) combination of pre-compilation, type extensions and units of measure

TO REPRODUCE

Download and unpack: Fable_precompile_test.zip

Run build.bat

==

Inspect ./.build/MyLib/Components.js

export function MyLib_Components_Input__Input_BadOne_Static_27F9613C(maxFileSize) {
    return "can\'t link me :(";
}

export function MyLib_Components_Input__Input_GoodOne_Static_Z524259A4(maxFileSize) {
    return "can link me! :)";
}

export function UsageInsideLib_UseItJustFine() {
    MyLib_Components_Input__Input_BadOne_Static_27F9613C(10);
    MyLib_Components_Input__Input_GoodOne_Static_Z524259A4(10);
}

Inspect ./.build/MyApp/Entry.js

export function CannotUseItProperly() {
    MyLib_Components_Input__Input_BadOne_Static_Z113C6639(22);
    return 1;
}

export function CanUseItProperly() {
    MyLib_Components_Input__Input_GoodOne_Static_Z524259A4(22);
    return 1;
}

See that BadOne has different overload suffix in lib and in app (27F9613C vs Z113C6639), while GoodOne has same suffix both in lib and app (Z524259A4)

Repeating .fs code for better visibility :

Lib:

namespace MyLib.Components

[<Measure>] type KB

type Input = class end

[<AutoOpen>]
module MyLib_Extensions =
    type Input with
        static member BadOne(maxFileSize: int<KB>) : string =
            ignore maxFileSize
            "can't link me :("

        static member GoodOne(maxFileSize: int) : string =
            ignore maxFileSize
            "can link me! :)"

module UsageInsideLib =
    let UseItJustFine() =
        Input.BadOne(10<KB>) |> ignore
        Input.GoodOne(10) |> ignore

App:

module MyApp.Entry

open MyLib.Components

let CannotUseItProperly() =
    let res = Input.BadOne 22<KB>
    ignore res
    1

let CanUseItProperly() =
    let res = Input.GoodOne 22
    ignore res
    1


[<EntryPoint>]
let main _args =
    1

KNOWN WORKAROUNDS

  • Don't use --precompiledLib, build all sources at once instead (slows down my build by 50%+ which is a big deal for the team)
  • Don't use UoM (probably worse than above)

IDEAS OF FIX

My first idea was simply to ignore UoM / pretend that it's a naked number primitive in getOverloadSuffixFrom in Fable.Cli. This however breaks tests:

.........\tests\Js\Main\CustomOperatorsTests.fs(1,1): error FABLE: Cannot have two module members with same name: ToLength_op_AmpDot_1A39F8D9

type ToLength = ToLength with
    static member (&.) (ToLength, x: int<px>) = fun D1 _ _ -> String.Join("", string x, "px")
    static member (&.) (ToLength, x: int<em>) = fun D1 D2 _ -> String.Join("", string x, "em")

Looking further.

@DunetsNM
Copy link
Contributor Author

DunetsNM commented May 21, 2024

Scratch getOverloadSuffixFrom, this path is not executed for extension members anyway.

Apparently measure type is still available in the dll only in a different form / as a "Microsoft.FSharp.Core.CompilerServices.MeasureProduct`2" type instead of just "MyLib.Components.KB"

It's a known thing, will try to reuse existing code if I can

        // Not sure why, but when precompiling F# changes measure types to MeasureProduct<'M, MeasureOne>
        match tdef.FullName, genArgs with
        | Types.measureProduct2, [ measure; Types.measureOne ] -> measure

@DunetsNM
Copy link
Contributor Author

pushed a fix to #3817 while I was there.

I think it may still break for aggregate units of measure such as <m/s> but I don't have capacity for it right now and it's an improvement regardless.

In fact my impression from working with the compiler is that Fable doesn't try to implement netstandard strictly (probably too ambitious task?), and rather uses case-by-case approach / solves problems as they come

@ncave
Copy link
Collaborator

ncave commented May 23, 2024

Closed by #3817

@ncave ncave closed this as completed May 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants