Skip to content

CrossChunkCodeMotion breaks --chunk_output_type ES_MODULES #4264

Description

@DavidANeil

Given the following program:

// basepoint.js
const foo = {instance: null};

/**
 * @nocollapse
 * @noinline
 */
export function setFoo(v) {
    foo.instance = v;
}

export function getFoo() {
    return foo.instance;
}

console.log(getFoo());
// otherfile.js
import {setFoo} from './basepoint.js';

setFoo('hi from otherfile.js');

Running the compiler produces a JSC_IMPORT_ASSIGN diagnostic:

$ bazel run //:compiler_uberjar -- --chunk_output_type ES_MODULES --chunk base:1 --chunk other:1:base --js /home/davidneil/github/closure-compiler/basepoint.js --js /home/davidneil/github/closure-compiler/otherfile.js  --compilation_level=ADVANCED_OPTIMIZATIONS --use_types_for_optimization=false --chunk_output_path_prefix /home/davidneil/github/closure-compiler/out/
/home/davidneil/github/closure-compiler/basepoint.js:10:8: ERROR - [JSC_IMPORT_ASSIGN] Imported symbol "a" in chunk "other.js" cannot be assigned (defined in "base.js")
  10|     foo.instance = v;
              ^^^^^^^^

If we inspect the output with debugging enabled we see when the bug is introduced: (output is trimmed to relevant parts)

 $ bazel run //:compiler_uberjar -- --chunk_output_type ES_MODULES --chunk base:1 --chunk other:1:base --js /home/davidneil/github/closure-compiler/basepoint.js --js /home/davidneil/github/closure-compiler/otherfile.js  --compilation_level=ADVANCED_OPTIMIZATIONS --use_types_for_optimization=false --chunk_output_path_prefix /home/davidneil/github/closure-compiler/out/ --formatting PRETTY_PRINT --debug --print_source_after_each_pass
// parseInputs yields:
// ************************************
// module 'base'
const foo = {instance:null};
export function setFoo(v) {
  foo.instance = v;
}
export function getFoo() {
  return foo.instance;
}
console.log(getFoo());
// module 'other'
import{setFoo}from "./basepoint.js";
setFoo("hi from otherfile.js");

// ************************************

// earlyInlineVariables yields:
// ************************************
// module 'base'
var foo$$module$home$davidneil$github$closure_compiler$basepoint$instance = null;
function setFoo$$module$home$davidneil$github$closure_compiler$basepoint(v$jscomp$1) {
  foo$$module$home$davidneil$github$closure_compiler$basepoint$instance = v$jscomp$1;
}
console.log(function() {
  return foo$$module$home$davidneil$github$closure_compiler$basepoint$instance;
}());
// module 'other'
setFoo$$module$home$davidneil$github$closure_compiler$basepoint("hi from otherfile.js");

// ************************************

// crossChunkCodeMotion yields:
// ************************************
// module 'base'
var foo$$module$home$davidneil$github$closure_compiler$basepoint$instance = null;
console.log(function() {
  return foo$$module$home$davidneil$github$closure_compiler$basepoint$instance;
}());
// module 'other'
function setFoo$$module$home$davidneil$github$closure_compiler$basepoint(v$jscomp$1) {
  foo$$module$home$davidneil$github$closure_compiler$basepoint$instance = v$jscomp$1;
}
setFoo$$module$home$davidneil$github$closure_compiler$basepoint("hi from otherfile.js");

// ************************************

// inlineFunctions yields:
// ************************************
// module 'base'
var foo$$module$home$davidneil$github$closure_compiler$basepoint$instance = null;
console.log(foo$$module$home$davidneil$github$closure_compiler$basepoint$instance);
// module 'other'
function setFoo$$module$home$davidneil$github$closure_compiler$basepoint(v$jscomp$1) {
  foo$$module$home$davidneil$github$closure_compiler$basepoint$instance = v$jscomp$1;
}
setFoo$$module$home$davidneil$github$closure_compiler$basepoint("hi from otherfile.js");

// ************************************

// convertChunksToESModules yields:
// ************************************
// module 'base'
var $foo$$module$home$davidneil$github$closure_compiler$basepoint$instance$$ = null;
console.log($foo$$module$home$davidneil$github$closure_compiler$basepoint$instance$$);
export{$foo$$module$home$davidneil$github$closure_compiler$basepoint$instance$$};
// module 'other'
import{$foo$$module$home$davidneil$github$closure_compiler$basepoint$instance$$}from "./base.js";
function $setFoo$$module$home$davidneil$github$closure_compiler$basepoint$$($v$jscomp$1$$) {
  $foo$$module$home$davidneil$github$closure_compiler$basepoint$instance$$ = $v$jscomp$1$$;
}
$setFoo$$module$home$davidneil$github$closure_compiler$basepoint$$("hi from otherfile.js");

// ************************************

Either CrossChunkCodeMotion needs to not move setFoo to another chunk if it has non-importable symbol dependencies, or it needs to add exports/imports for any symbols that setFoo depends on. I think the former is the better solution.

Related, if the @nocollapse and @noinline are removed, then it looks like the bug is actually introduced by the earlyInlineVariables pass


// parseInputs yields:
// ************************************
// module 'base'
const foo = {instance:null};
export function setFoo(v) {
  foo.instance = v;
}
export function getFoo() {
  return foo.instance;
}
console.log(getFoo());
// module 'other'
import{setFoo}from "./basepoint.js";
setFoo("hi from otherfile.js");

// ************************************

// es6RewriteModule yields:
// ************************************
// module 'base'
const foo$$module$home$davidneil$github$closure_compiler$basepoint = {instance:null};
function setFoo$$module$home$davidneil$github$closure_compiler$basepoint(v) {
  foo$$module$home$davidneil$github$closure_compiler$basepoint.instance = v;
}
function getFoo$$module$home$davidneil$github$closure_compiler$basepoint() {
  return foo$$module$home$davidneil$github$closure_compiler$basepoint.instance;
}
console.log(getFoo$$module$home$davidneil$github$closure_compiler$basepoint());
var module$home$davidneil$github$closure_compiler$basepoint = {};
module$home$davidneil$github$closure_compiler$basepoint.getFoo = getFoo$$module$home$davidneil$github$closure_compiler$basepoint;
module$home$davidneil$github$closure_compiler$basepoint.setFoo = setFoo$$module$home$davidneil$github$closure_compiler$basepoint;
// module 'other'
setFoo$$module$home$davidneil$github$closure_compiler$basepoint("hi from otherfile.js");
var module$home$davidneil$github$closure_compiler$otherfile = {};

// ************************************

// normalize yields:
// ************************************
// module 'base'
const foo$$module$home$davidneil$github$closure_compiler$basepoint = {instance:null};
function setFoo$$module$home$davidneil$github$closure_compiler$basepoint(v$jscomp$1) {
  foo$$module$home$davidneil$github$closure_compiler$basepoint.instance = v$jscomp$1;
}
function getFoo$$module$home$davidneil$github$closure_compiler$basepoint() {
  return foo$$module$home$davidneil$github$closure_compiler$basepoint.instance;
}
console.log(getFoo$$module$home$davidneil$github$closure_compiler$basepoint());
var module$home$davidneil$github$closure_compiler$basepoint = {};
module$home$davidneil$github$closure_compiler$basepoint.getFoo = getFoo$$module$home$davidneil$github$closure_compiler$basepoint;
module$home$davidneil$github$closure_compiler$basepoint.setFoo = setFoo$$module$home$davidneil$github$closure_compiler$basepoint;
// module 'other'
setFoo$$module$home$davidneil$github$closure_compiler$basepoint("hi from otherfile.js");
var module$home$davidneil$github$closure_compiler$otherfile = {};

// ************************************

// inlineAndCollapseProperties yields:
// ************************************
// module 'base'
var foo$$module$home$davidneil$github$closure_compiler$basepoint$instance = null;
function setFoo$$module$home$davidneil$github$closure_compiler$basepoint(v$jscomp$1) {
  foo$$module$home$davidneil$github$closure_compiler$basepoint$instance = v$jscomp$1;
}
function getFoo$$module$home$davidneil$github$closure_compiler$basepoint() {
  return foo$$module$home$davidneil$github$closure_compiler$basepoint$instance;
}
console.log(getFoo$$module$home$davidneil$github$closure_compiler$basepoint());
var module$home$davidneil$github$closure_compiler$basepoint$getFoo = null;
var module$home$davidneil$github$closure_compiler$basepoint$setFoo = null;
// module 'other'
setFoo$$module$home$davidneil$github$closure_compiler$basepoint("hi from otherfile.js");

// ************************************

// earlyInlineVariables yields:
// ************************************
// module 'base'
var foo$$module$home$davidneil$github$closure_compiler$basepoint$instance = null;
console.log(function() {
  return foo$$module$home$davidneil$github$closure_compiler$basepoint$instance;
}());
// module 'other'
(function(v$jscomp$1) {
  foo$$module$home$davidneil$github$closure_compiler$basepoint$instance = v$jscomp$1;
})("hi from otherfile.js");

// ************************************

// inlineFunctions yields:
// ************************************
// module 'base'
var foo$$module$home$davidneil$github$closure_compiler$basepoint$instance = null;
console.log(foo$$module$home$davidneil$github$closure_compiler$basepoint$instance);
// module 'other'
 {
  foo$$module$home$davidneil$github$closure_compiler$basepoint$instance = "hi from otherfile.js";
}
;
// ************************************

// peepholeOptimizations yields:
// ************************************
// module 'base'
var foo$$module$home$davidneil$github$closure_compiler$basepoint$instance = null;
console.log(foo$$module$home$davidneil$github$closure_compiler$basepoint$instance);
// module 'other'
foo$$module$home$davidneil$github$closure_compiler$basepoint$instance = "hi from otherfile.js";

// ************************************

// renameVars yields:
// ************************************
// module 'base'
var $foo$$module$home$davidneil$github$closure_compiler$basepoint$instance$$ = null;
console.log($foo$$module$home$davidneil$github$closure_compiler$basepoint$instance$$);
// module 'other'
$foo$$module$home$davidneil$github$closure_compiler$basepoint$instance$$ = "hi from otherfile.js";

// ************************************

// convertChunksToESModules yields:
// ************************************
// module 'base'
var $foo$$module$home$davidneil$github$closure_compiler$basepoint$instance$$ = null;
console.log($foo$$module$home$davidneil$github$closure_compiler$basepoint$instance$$);
export{$foo$$module$home$davidneil$github$closure_compiler$basepoint$instance$$};
// module 'other'
import{$foo$$module$home$davidneil$github$closure_compiler$basepoint$instance$$}from "./base.js";
$foo$$module$home$davidneil$github$closure_compiler$basepoint$instance$$ = "hi from otherfile.js";

// ************************************

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions