Skip to content

Invalid TypeScript code generated when running Fable in watch mode / release mode #3520

@MangelMaxime

Description

@MangelMaxime

Description

  1. dotnet run -c Release --project /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Cli -- tests/TypeScript --outDir build/tests/TypeScript --lang typescript --exclude Fable.Core --noCache
  2. npx tsc -p build/tests/TypeScript --outDir build/tests/TypeScriptCompiled

See that no errors are reported.

  1. dotnet run -c Release --project /Users/mmangel/Workspaces/Github/fable-compiler/fable/src/Fable.Cli -- tests/TypeScript --outDir build/tests/TypeScript --lang typescript --exclude Fable.Core --noCache --watch
  2. Kill Fable
  3. npx tsc -p build/tests/TypeScript --outDir build/tests/TypeScriptCompiled

See that errors are reported.

CleanShot 2023-09-05 at 16 13 38@2x
Fable git:(infra_clean_up) ✗ npx tsc -p build/tests/TypeScript --outDir build/tests/TypeScriptCompiled
build/tests/TypeScript/Js/Main/ApplicativeTests.ts:928:29 - error TS2322: Type '(a2: number) => number' is not assignable to type 'number'.

928         const f1_1: int32 = curry2(f_3)(x_4);
                                ~~~~~~~~~~~~~~~~

  build/tests/TypeScript/Js/Main/ApplicativeTests.ts:928:29
    928         const f1_1: int32 = curry2(f_3)(x_4);
                                    ~~~~~~~~~~~~~~~~
    Did you mean to call this expression?

build/tests/TypeScript/Js/Main/ApplicativeTests.ts:930:32 - error TS2349: This expression is not callable.
  Type 'Number' has no call signatures.

930             const y_2: int32 = f1_1(arg_1) | 0;
                                   ~~~~

build/tests/TypeScript/Js/Main/ApplicativeTests.ts:1948:104 - error TS2322: Type '(a3: (arg0: unknown) => number) => (a4: unknown) => [number, number]' is not assignable to type '(arg0: (arg0: $a) => number) => (arg0: $a) => [number, number]'.
  Types of parameters 'a3' and 'arg0' are incompatible.
    Types of parameters 'arg0' and 'arg0' are incompatible.
      Type 'unknown' is not assignable to type '$a'.
        '$a' could be instantiated with an arbitrary type which could be unrelated to 'unknown'.

1948     const bar = <$a>(a_1: int32): ((arg0: ((arg0: $a) => int32)) => ((arg0: $a) => [int32, int32])) => curry4(foo)(1)(a_1);
                                                                                                            ~~~~~~~~~~~~~~~~~~~

build/tests/TypeScript/Js/Main/ApplicativeTests.ts:1949:35 - error TS2554: Expected 1 arguments, but got 0.

1949     const baz_1: [int32, int32] = bar(2)((): int32 => 3)();
                                       ~~~~~~~~~~~~~~~~~~~~~~~~

  build/tests/TypeScript/Js/Main/ApplicativeTests.ts:1948:71
    1948     const bar = <$a>(a_1: int32): ((arg0: ((arg0: $a) => int32)) => ((arg0: $a) => [int32, int32])) => curry4(foo)(1)(a_1);
                                                                               ~~~~~~~~
    An argument for 'arg0' was not provided.

build/tests/TypeScript/Js/Main/MiscTests.ts:1311:28 - error TS2554: Expected 0 arguments, but got 1.

1311     equal<int32>(9, func2b(void 0));
                                ~~~~~~

build/tests/TypeScript/Js/Main/ReflectionTests.ts:966:27 - error TS2367: This comparison appears to be unintentional because the types '"Maybe`1"' and '""' have no overlap.

966     equal<boolean>(false, name === "");
                              ~~~~~~~~~~~

build/tests/TypeScript/Js/Main/ReflectionTests.ts:969:27 - error TS2367: This comparison appears to be unintentional because the types '"Maybe`1"' and '""' have no overlap.

969     equal<boolean>(false, name_1 === "");
                              ~~~~~~~~~~~~~

build/tests/TypeScript/Js/Main/StringTests.ts:165:5 - error TS2571: Object is of type 'unknown'.

165     linef(printf("open %s"))("Foo");
        ~~~~~~~~~~~~~~~~~~~~~~~~

build/tests/TypeScript/Js/Main/TailCallTests.ts:586:23 - error TS2345: Argument of type '<$a, $b>(model_1: FSharpMap<$a, $b>, dispatch: (arg0: $a) => void) => Element$' is not assignable to parameter of type '(a1: FSharpMap<number, boolean>, a2: (arg0: never) => void) => Element$'.
  Types of parameters 'dispatch' and 'a2' are incompatible.
    Types of parameters 'arg0' and 'arg0' are incompatible.
      Type 'number' is not assignable to type 'never'.

586     main(init, curry2(view), curry2(update));
                          ~~~~

build/tests/TypeScript/Js/Main/TypeTests.ts:1696:27 - error TS2741: Property 'name' is missing in type 'MyEx' but required in type 'Error'.

1696                     const e_1: Error = matchValue;
                               ~~~

  node_modules/typescript/lib/lib.es5.d.ts:1054:5
    1054     name: string;
             ~~~~
    'name' is declared here.

build/tests/TypeScript/Js/Main/TypeTests.ts:1701:27 - error TS2322: Type 'MyEx' is not assignable to type 'Error'.

1701                     const e_2: Error = matchValue;
                               ~~~


Found 11 errors in 6 files.

Errors  Files
     4  build/tests/TypeScript/Js/Main/ApplicativeTests.ts:928
     1  build/tests/TypeScript/Js/Main/MiscTests.ts:1311
     2  build/tests/TypeScript/Js/Main/ReflectionTests.ts:966
     1  build/tests/TypeScript/Js/Main/StringTests.ts:165
     1  build/tests/TypeScript/Js/Main/TailCallTests.ts:586
     2  build/tests/TypeScript/Js/Main/TypeTests.ts:1696

Expected and actual results

The reason for this difference between production and watch mode is that the generated code is not the same.

In release mode

testCase("Arity is checked also when constructing records", (): void => {
    equal<string>("foo23", (new ArityRecord((x_4: int32, arg_1: int32): string => {
        const y_2: int32 = ((j_1: int32): int32 => ((x_4 * 2) + (j_1 * 3)))(arg_1) | 0;
        return toText(printf("foo%i"))(y_2);
    })).arity2(4, 5));
})

In watch mode

testCase("Arity is checked also when constructing records", (): void => {
    const f_3 = (i_1: int32, j_1: int32): int32 => ((i_1 * 2) + (j_1 * 3));
    const r: ArityRecord = new ArityRecord(uncurry2((x_4: int32): ((arg0: int32) => string) => {
        const f1_1: int32 = curry2(f_3)(x_4);
        return (arg_1: int32): string => {
            const y_2: int32 = f1_1(arg_1) | 0;
            return toText(printf("foo%i"))(y_2);
        };
    }));
    equal<string>("foo23", r.arity2(4, 5));
})

I suspect this is because of different optimisation done by Fable or the F# compiler, which I guess is not problematic per se.

However, the generated code in watch mode is wrongly type.

For example, const f1_1: int32 = curry2(f_3)(x_4); should be written as const f1_1: ((x : int32) => int32) = curry2(f_3)(x_4); instead.

In release mode

testCase("Name can be extracted from RecWithGenDU", (): void => {
    equal<boolean>(false, false);
})

In watch mode

testCase("Name can be extracted from RecWithGenDU", (): void => {
    const name = "Maybe`1";
    equal<boolean>(false, name === "");
})

In this case, the correct code seems to be the watch mode, because release mode just seems to not generate anything at all...

Related information

  • Fable version: 4.1.4
  • Operating system

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions