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

v12 alpha: incorrect compiler output #7023

Open
cknitt opened this issue Sep 7, 2024 · 19 comments
Open

v12 alpha: incorrect compiler output #7023

cknitt opened this issue Sep 7, 2024 · 19 comments

Comments

@cknitt
Copy link
Member

cknitt commented Sep 7, 2024

When compiling rescript-schema with v12 alpha.3, I noticed invalid output. Simplified, the problem is this:

In v11, the following ReScript code:

type error

let reason = (error: error, ~nestedLevel as _ = ?): string => String.make(error)
let reason: error => string = Obj.magic(reason)

let message = (error: error) => `Failed reason: ${error->reason}`

compiles to

function reason(error, param) {
  return String(error);
}

function message(error) {
  return "Failed reason: " + reason(error);
}

whereas in v12 alpha, it compiles to the invalid code

function reason(error, param) {
  return String(error);
}

function message(error) {
  return "Failed reason: " + param => reason(error, param);
}

/cc @cristianoc @cometkim

@cknitt cknitt added the bug label Sep 7, 2024
@cknitt cknitt added this to the v12 milestone Sep 7, 2024
@cometkim
Copy link
Member

cometkim commented Sep 7, 2024

Hmm, that's confusing. Isn't this a change in type rather than a change in output? Let me find out.

@cometkim
Copy link
Member

cometkim commented Sep 7, 2024

v12 alpha.1 output

function reason(error, param) {
  return String(error);
}

function message(error) {
  return "Failed reason: " + (function (param) {
    return reason(error, param);
  });
}

@cknitt
Copy link
Member Author

cknitt commented Sep 7, 2024

So there seem to be two different issues:

  1. Since alpha.2: The output is invalid JS (parenthesis missing).
  2. Since alpha.1: The output is incorrect, we want to append reason(errror), not a function.

The latter may be related to some changes around removing curried mode?

@cometkim
Copy link
Member

cometkim commented Sep 7, 2024

Since alpha.2: The output is invalid JS (parenthesis missing).

well, I think it shouldn't be able to produce without %raw, and %raw block will be wrapped correctly

@cometkim
Copy link
Member

cometkim commented Sep 7, 2024

The latter may be related to some changes around removing curried mode?

This one: f603cf1

@cknitt
Copy link
Member Author

cknitt commented Sep 14, 2024

Played a bit more with this:

let f1: string => string = Obj.magic("not actually a function, but never mind")
let f2: string => string = Obj.magic((a, b) => a ++ b)
let f3: string => string = Obj.magic((a, b, c) => a ++ b ++ c)

let x1 = f1("test")
let x2 = f2("test")
let x3 = f3("test")

outputs

let f1 = "not actually a function, but never mind";

function f2(a, b) {
  return a + b;
}

function f3(a, b, c) {
  return a + b + c;
}

let x1 = f1("test");

function x2(param) {
  return f2("test", param);
}

function x3(param, param$1) {
  return f3("test", param, param$1);
}

in v12 alpha.

So somehow even though the type of each function is string => string after "casting" with Obj.magic, the original arity of the function is preserved and affects the generated code.

@fhammerschmidt
Copy link
Member

I tried a slightly modified example in the playground with v10:

type error

let reason = (. error: error, ~nestedLevel as _): string => error->Obj.magic
let reason: error => string = Obj.magic(reason)

let message = (error: error) => `Failed reason: ${error->reason}

which gives me
v10:

function message(error) {
  return "Failed reason: " + (function (param) {
            return reason(error, param);
          }) + "";
}

v11:

function message(error) {
  return "Failed reason: " + reason(error);
}

v12:

function message(error) {
  return "Failed reason: " + param => reason(error, param);
}

@cristianoc
Copy link
Collaborator

cristianoc commented Oct 4, 2024

First, simplified example:

let first = (x, y) => x
let hack: int => int = Obj.magic(first)
let test = x => hack(x)

It's pretty questionable whether this should work, but one can see why the behaviour is like it is and what changed in v12.
For sure, moving hack to another file would make it impossible to even know that there exists first underneath and the issue would go away.

@cristianoc
Copy link
Collaborator

The generated code looks fine on master. But not on playground alpha.3 https://rescript-lang.org/try?version=v12.0.0-alpha.3&code=FAGwpgLgBAZglgJwM7QLxQBQA8A0UCeAlFKgHxRaiRQAWAhgMYDWAXFHAHZrmdpQDyAIwBWAOgC2dAOZwGGeMgiEq0CGBQkKJcvWbZCQA

% ./bsc tst.res
// Generated by ReScript, PLEASE EDIT WITH CARE
'use strict';

let Obj = require("rescript/lib/js/obj.js");

function first(x, _y) {
  return x;
}

let hack = Obj.magic(first);

function test(x) {
  return hack(x);
}

exports.first = first;
exports.hack = hack;
exports.test = test;
/* hack Not a pure module */

@cknitt was this fixed?

@cknitt
Copy link
Member Author

cknitt commented Oct 4, 2024

@cristianoc No, it was not fixed. I can reproduce it with your example if I add your example to tests/tests/src and run node ./scripts/test.js -node.

I get

function test(x) {
  return param => first(x, param);
}

then.

@cristianoc
Copy link
Collaborator

We have a couple of small issues.
One is the bsc commands used are a bit opaque, and verbose mode should show them in detail.
The other is rescript does not use default options while it should.

Here's the repro:

./bsc -bs-cross-module-opt tst.res

If rescript always uses that option, then it should be the default.

@cristianoc
Copy link
Collaborator

So what's going on is that it's inlining the function, so it kind of does what you tell it to do.

@cristianoc
Copy link
Collaborator

cristianoc commented Oct 4, 2024

Here's the "fix":

let opaque_cast =  x => Obj.magic(x)

let first = (x, _y) => x
let hack: int => int = opaque_cast(first)
let test = x => hack(x)

@cristianoc cristianoc removed the bug label Oct 4, 2024
@cristianoc cristianoc removed this from the v12 milestone Oct 4, 2024
@cknitt
Copy link
Member Author

cknitt commented Oct 4, 2024

Hmm, -bs-cross-module-opt is not the default though. In the compiler repo, it is set explicitly in bsc-flags when building the runtime + tests.

But as far as I can see, it is not set in rescript-schema.

@cristianoc
Copy link
Collaborator

Hmm, -bs-cross-module-opt is not the default though. In the compiler repo, it is set explicitly in bsc-flags when building the runtime + tests.

But as far as I can see, it is not set in rescript-schema.

Then we should be consistent, and use defaults in tests, unless we are explicitly testing the non-default options.

@cknitt
Copy link
Member Author

cknitt commented Oct 4, 2024

Then we should be consistent, and use defaults in tests, unless we are explicitly testing the non-default options.

Agreed! And this unearthes other interesting stuff: #7071

@zth
Copy link
Collaborator

zth commented Oct 4, 2024

Is there a reason cross module opt isn't the default?

@cknitt
Copy link
Member Author

cknitt commented Oct 4, 2024

Is there a reason cross module opt isn't the default?

It is labeled as experimental

    "-bs-cross-module-opt", set Js_config.cross_module_inline, 
    "*internal* Enable cross module inlining(experimental), default(false)";

@zth
Copy link
Collaborator

zth commented Oct 4, 2024

Yes but does it need to be?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Ready
Development

No branches or pull requests

5 participants