Skip to content

Dangerous string interpolation  #2461

@rafayepes

Description

@rafayepes

tl;dr: For {j|This is a function: $someFunction|j} compiler doesn't throw type errors even if someFunction is a function, variant or anything not "stringable".

Bucklescript string interpolation seems that was introduced in #1316. @glennsl in this comment expresses his concerns with the current implementation, doesn't seem his comment got much attention. After discussing with him in Discord, we agreed there seem to be even more issues than stated before, which lead me to decide not to use string interpolation for now. I'll try to summarise the pros/cons of the current implementation, in hopes something can be improved.

Implicit vs explicit

Input

let ok = 1;

/*
    String interpolation seems handy here. TBH I prefer the explicitness of the
    string concatenation, but auto transforming to String here seems kind of ok.
 */
Js.log({j|This is $ok|j});
Js.log("This is " ++ string_of_int(ok));

Output

console.log("This is " + (String(1) + ""));

console.log("This is " + Pervasives.string_of_int(1));

Unnecessary String call

Input

let unnecessary = "unnecessary";

/* Compiled output of string interpolation adds unnecessary `String` call */
Js.log({j|This is $unnecessary|j});
Js.log("This is " ++ unnecessary);

Output

var unnecessary = "unnecessary";

console.log("This is " + (String(unnecessary) + ""));

console.log("This is unnecessary");

Unexpected error, due partial application

Input

let add = (~x, ~y=1, ()) => x + y;

let result = add(~x=1, ~y=3);

/*
Here, this aparently seems ok, but because result is still a partially applied
function and not a number, this result will be wrong.
Uncommenting string concatenation example will result in proper compiler error.
*/
/* Js.log("This is not ok" ++ result); */
Js.log({j|This is not ok $result))|j});

Output

function add(x, $staropt$star, _) {
  var y = $staropt$star ? $staropt$star[0] : 1;
  return x + y | 0;
}

var partial_arg = /* Some */[3];

function result(param) {
  return add(1, partial_arg, param);
}

console.log("This is not ok " + (String(result) + "))"));

Unexpected error, due option type

Input

let getMyString = () => {
    if (Js.Math.random() > 0.5) {
        Some("Something");
    }
    else {
        None;
    }
};

let myString = getMyString();

/*
    It might be obvious what's the problem here, but in a real world scenario might
    be a bit more confusing. By mistake we passed an optional string to the string
    interpolation, where we should have stracted the actual string before.
    Uncommenting string concatenation example will result in proper compiler error.
*/
/* Js.log("This is actually optional " ++ myString); */
Js.log({j|This is actually optional $myString|j})

Output

function getMyString() {
  if (Math.random() > 0.5) {
    return /* Some */["Something"];
  } else {
    return /* None */0;
  }
}

var myString = getMyString(/* () */0);

console.log("This is actually optional " + (String(myString) + ""));

And basically, you can add to the list any type error you can imagine

Input

let thisIsJustWrong = () => Js.log("This is wrong");

Js.log({j|This is just wrong $thisIsJustWrong|j});

Output

function thisIsJustWrong() {
  console.log("This is wrong");
  return /* () */0;
}

console.log("This is just wrong " + (String(thisIsJustWrong) + ""));

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions