Search code examples
javascriptbindingffireasonbucklescript

module name is shadowing global name used in binding


tl;dr: How to change the following binding to be able to write Intl.DateTimeFormat.make() instead of Intl_.DateTimeFormat.make()?

type dateTimeFormat;

[@bs.deriving abstract]
type formatOptions = {
  [@bs.optional]
  weekday: string,
  [@bs.optional]
  day: string,
  [@bs.optional]
  month: string,
};

module Intl_ {
    module DateTimeFormat {
      module Impl {
        type t;
      };

      [@bs.new] external make: unit => Impl.t = "Intl.DateTimeFormat";
      [@bs.send] external format: (Impl.t, Js.Date.t) => string = "";
    };
}

Intl_.DateTimeFormat.make()
  ->Intl_.DateTimeFormat.format(Js.Date.make())
  ->Js.log;

The issue

Without the underscore, this would compile to:

var Impl = /* module */[];

var DateTimeFormat = /* module */[/* Impl */Impl];

var Intl = /* module */[/* DateTimeFormat */DateTimeFormat];

console.log(new Intl.DateTimeFormat().format(new Date()));

exports.Intl = Intl;

The issue is that var Intl = ... shadows the global Intl, and thus breaks new Intl.DateTimeFormat().


Solution

  • First of all, I'd consider this a bug in BuckleScript. This problem was most recently brought up in issue #3268 and partially resolved in this commit, but it still leaves a lot of names unreserved. You should consider bringing this up there or in a new issue.

    In the meantime, you can work around this by fully qualifying the name. Intl isn't actually a global object, but attached to the global object, which in the context of a web page is window. But since JavaScript will look for names on the global object when it's not found in the local environment, it looks very much like a global name.

    So if you change make to:

    [@bs.new] external make: unit => Impl.t = "window.Intl.DateTimeFormat";
    

    it should work fine.