Comparing version 5.32.0 to 5.33.0
# Changelog | ||
## v5.33.0 | ||
- `reduce_vars` improved when dealing with hoisted function definitions (#1544) | ||
## v5.32.0 | ||
@@ -4,0 +8,0 @@ |
@@ -336,5 +336,5 @@ /*********************************************************************** | ||
/** Check if a ref refers to the name of a function/class it's defined within */ | ||
export function is_recursive_ref(compressor, def) { | ||
export function is_recursive_ref(tw, def) { | ||
var node; | ||
for (var i = 0; node = compressor.parent(i); i++) { | ||
for (var i = 0; node = tw.parent(i); i++) { | ||
if (node instanceof AST_Lambda || node instanceof AST_Class) { | ||
@@ -341,0 +341,0 @@ var name = node.name; |
@@ -97,4 +97,3 @@ /*********************************************************************** | ||
walk_body, | ||
TreeWalker, | ||
walk_parent, | ||
} from "../ast.js"; | ||
@@ -500,3 +499,20 @@ import { HOP, make_node, noop } from "../utils/index.js"; | ||
* | ||
* This function is called on the parent to handle this issue. | ||
* Or even indirectly: | ||
* | ||
* B(); | ||
* var defined_after = true; | ||
* function A() { | ||
* // use defined_after | ||
* } | ||
* function B() { | ||
* A(); | ||
* } | ||
* | ||
* Access a variable before declaration will either throw a ReferenceError | ||
* (if the variable is declared with `let` or `const`), | ||
* or get an `undefined` (if the variable is declared with `var`). | ||
* | ||
* If the variable is inlined into the function, the behavior will change. | ||
* | ||
* This function is called on the parent to disallow inlining of such variables, | ||
*/ | ||
@@ -507,3 +523,6 @@ function handle_defined_after_hoist(parent) { | ||
if (node === parent) return; | ||
if (node instanceof AST_Defun) defuns.push(node); | ||
if (node instanceof AST_Defun) { | ||
defuns.push(node); | ||
return true; | ||
} | ||
if ( | ||
@@ -515,11 +534,13 @@ node instanceof AST_Scope | ||
// `defun` id to array of `defun` it uses | ||
const defun_dependencies_map = new Map(); | ||
// `defun` id to array of enclosing `def` that are used by the function | ||
const dependencies_map = new Map(); | ||
// all symbol ids that will be tracked for read/write | ||
const symbols_of_interest = new Set(); | ||
const defuns_of_interest = new Set(); | ||
const potential_conflicts = []; | ||
for (const defun of defuns) { | ||
const fname_def = defun.name.definition(); | ||
const found_self_ref_in_other_defuns = defuns.some( | ||
d => d !== defun && d.enclosed.indexOf(fname_def) !== -1 | ||
); | ||
const enclosing_defs = []; | ||
@@ -535,3 +556,5 @@ for (const def of defun.enclosed) { | ||
// defun is hoisted, so always safe | ||
symbols_of_interest.add(def.id); | ||
// found a reference to another function | ||
if ( | ||
@@ -542,83 +565,95 @@ def.assignments === 0 | ||
) { | ||
continue; | ||
} | ||
defuns_of_interest.add(def.id); | ||
symbols_of_interest.add(def.id); | ||
if (found_self_ref_in_other_defuns) { | ||
def.fixed = false; | ||
defuns_of_interest.add(fname_def.id); | ||
symbols_of_interest.add(fname_def.id); | ||
if (!defun_dependencies_map.has(fname_def.id)) { | ||
defun_dependencies_map.set(fname_def.id, []); | ||
} | ||
defun_dependencies_map.get(fname_def.id).push(def.id); | ||
continue; | ||
} | ||
// for the slower checks below this loop | ||
potential_conflicts.push({ defun, def, fname_def }); | ||
symbols_of_interest.add(def.id); | ||
enclosing_defs.push(def); | ||
} | ||
if (enclosing_defs.length) { | ||
dependencies_map.set(fname_def.id, enclosing_defs); | ||
defuns_of_interest.add(fname_def.id); | ||
symbols_of_interest.add(fname_def.id); | ||
defuns_of_interest.add(defun); | ||
} | ||
} | ||
// linearize all symbols, and locate defs that are read after the defun | ||
if (potential_conflicts.length) { | ||
// All "symbols of interest", that is, defuns or defs, that we found. | ||
// These are placed in order so we can check which is after which. | ||
const found_symbols = []; | ||
// Indices of `found_symbols` which are writes | ||
const found_symbol_writes = new Set(); | ||
// Defun ranges are recorded because we don't care if a function uses the def internally | ||
const defun_ranges = new Map(); | ||
// No defuns use outside constants | ||
if (!dependencies_map.size) { | ||
return; | ||
} | ||
let tw; | ||
parent.walk((tw = new TreeWalker((node, descend) => { | ||
if (node instanceof AST_Defun && defuns_of_interest.has(node)) { | ||
const start = found_symbols.length; | ||
descend(); | ||
const end = found_symbols.length; | ||
// Increment to count "symbols of interest" (defuns or defs) that we found. | ||
// These are tracked in AST order so we can check which is after which. | ||
let symbol_index = 1; | ||
// Map a defun ID to its first read (a `symbol_index`) | ||
const defun_first_read_map = new Map(); | ||
// Map a symbol ID to its last write (a `symbol_index`) | ||
const symbol_last_write_map = new Map(); | ||
defun_ranges.set(node, { start, end }); | ||
return true; | ||
walk_parent(parent, (node, walk_info) => { | ||
if (node instanceof AST_Symbol && node.thedef) { | ||
const id = node.definition().id; | ||
symbol_index++; | ||
// Track last-writes to symbols | ||
if (symbols_of_interest.has(id)) { | ||
if (node instanceof AST_SymbolDeclaration || is_lhs(node, walk_info.parent())) { | ||
symbol_last_write_map.set(id, symbol_index); | ||
} | ||
} | ||
// if we found a defun on the list, mark IN_DEFUN=id and descend | ||
if (node instanceof AST_Symbol && node.thedef) { | ||
const id = node.definition().id; | ||
if (symbols_of_interest.has(id)) { | ||
if (node instanceof AST_SymbolDeclaration || is_lhs(node, tw)) { | ||
found_symbol_writes.add(found_symbols.length); | ||
} | ||
found_symbols.push(id); | ||
// Track first-reads of defuns (refined later) | ||
if (defuns_of_interest.has(id)) { | ||
if (!defun_first_read_map.has(id) && !is_recursive_ref(walk_info, id)) { | ||
defun_first_read_map.set(id, symbol_index); | ||
} | ||
} | ||
}))); | ||
} | ||
}); | ||
for (const { def, defun, fname_def } of potential_conflicts) { | ||
const defun_range = defun_ranges.get(defun); | ||
// Refine `defun_first_read_map` to be as high as possible | ||
for (const [defun, defun_first_read] of defun_first_read_map) { | ||
// Update all depdencies of `defun` | ||
const queue = new Set(defun_dependencies_map.get(defun)); | ||
for (const enclosed_defun of queue) { | ||
let enclosed_defun_first_read = defun_first_read_map.get(enclosed_defun); | ||
if (enclosed_defun_first_read != null && enclosed_defun_first_read < defun_first_read) { | ||
continue; | ||
} | ||
// find the index in `found_symbols`, with some special rules: | ||
const find = (sym_id, starting_at = 0, must_be_write = false) => { | ||
let index = starting_at; | ||
defun_first_read_map.set(enclosed_defun, defun_first_read); | ||
for (;;) { | ||
index = found_symbols.indexOf(sym_id, index); | ||
for (const enclosed_enclosed_defun of defun_dependencies_map.get(enclosed_defun) || []) { | ||
queue.add(enclosed_enclosed_defun); | ||
} | ||
} | ||
} | ||
if (index === -1) { | ||
break; | ||
} else if (index >= defun_range.start && index < defun_range.end) { | ||
index = defun_range.end; | ||
continue; | ||
} else if (must_be_write && !found_symbol_writes.has(index)) { | ||
index++; | ||
continue; | ||
} else { | ||
break; | ||
} | ||
} | ||
// ensure write-then-read order, otherwise clear `fixed` | ||
// This is safe because last-writes (found_symbol_writes) are assumed to be as late as possible, and first-reads (defun_first_read_map) are assumed to be as early as possible. | ||
for (const [defun, defs] of dependencies_map) { | ||
const defun_first_read = defun_first_read_map.get(defun); | ||
if (defun_first_read === undefined) { | ||
continue; | ||
} | ||
return index; | ||
}; | ||
for (const def of defs) { | ||
if (def.fixed === false) { | ||
continue; | ||
} | ||
const read_defun_at = find(fname_def.id); | ||
const wrote_def_at = find(def.id, read_defun_at + 1, true); | ||
let def_last_write = symbol_last_write_map.get(def.id) || 0; | ||
const wrote_def_after_reading_defun = read_defun_at != -1 && wrote_def_at != -1 && wrote_def_at > read_defun_at; | ||
if (wrote_def_after_reading_defun) { | ||
if (defun_first_read < def_last_write) { | ||
def.fixed = false; | ||
@@ -625,0 +660,0 @@ } |
@@ -7,3 +7,3 @@ { | ||
"license": "BSD-2-Clause", | ||
"version": "5.32.0", | ||
"version": "5.33.0", | ||
"engines": { | ||
@@ -10,0 +10,0 @@ "node": ">=10" |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
2199137
62809