diff --git a/src/js_parser/p.rs b/src/js_parser/p.rs index ae8c8539a8db..5577d4f99072 100644 --- a/src/js_parser/p.rs +++ b/src/js_parser/p.rs @@ -4657,7 +4657,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O match &mut binding.data { js_ast::b::B::BMissing(_) => {} js_ast::b::B::BIdentifier(bind) => { - if !opts.is_typescript_declare || (opts.is_namespace_scope && opts.is_export) { + if !opts.is_typescript_declare || (opts.scope.is_namespace() && opts.is_export) { bind.r#ref = self.declare_symbol( kind, binding.loc, diff --git a/src/js_parser/parse/mod.rs b/src/js_parser/parse/mod.rs index 343377682d6e..c32eca6e0e39 100644 --- a/src/js_parser/parse/mod.rs +++ b/src/js_parser/parse/mod.rs @@ -723,7 +723,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O if Self::IS_TYPESCRIPT_ENABLED { if opts.is_typescript_declare { p.pop_and_discard_scope(scope_index); - if opts.is_namespace_scope && opts.is_export { + if opts.scope.is_namespace() && opts.is_export { p.has_non_local_export_declare_inside_namespace = true; } diff --git a/src/js_parser/parse/parse_entry.rs b/src/js_parser/parse/parse_entry.rs index a2c38296078b..6be8e01b5cd6 100644 --- a/src/js_parser/parse/parse_entry.rs +++ b/src/js_parser/parse/parse_entry.rs @@ -15,7 +15,8 @@ use crate::defines::Define; use crate::lexer as js_lexer; use crate::p::P; use crate::parser::{ - Jest, ParseStatementOptions, RuntimeFeatures, RuntimeImports, ScanPassResult, WrapMode, + Jest, ParseStatementOptions, RuntimeFeatures, RuntimeImports, ScanPassResult, StatementScope, + WrapMode, }; use bun_ast as js_ast; use bun_ast::DeclaredSymbol; @@ -414,7 +415,7 @@ impl<'a> Parser<'a> { // Parse the file in the first pass, but do not bind symbols let mut opts = ParseStatementOptions { - is_module_scope: true, + scope: StatementScope::Module, ..Default::default() }; @@ -695,7 +696,7 @@ impl<'a> Parser<'a> { // Parse the file in the first pass, but do not bind symbols let mut opts = ParseStatementOptions { - is_module_scope: true, + scope: StatementScope::Module, ..Default::default() }; let mut parse_tracer = bun_core::perf::trace("JSParser::parse"); diff --git a/src/js_parser/parse/parse_fn.rs b/src/js_parser/parse/parse_fn.rs index 6a58ff9ae02c..084b395075b0 100644 --- a/src/js_parser/parse/parse_fn.rs +++ b/src/js_parser/parse/parse_fn.rs @@ -117,7 +117,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O p.pop_and_discard_scope(if_stmt_scope_index); } - if opts.is_typescript_declare && opts.is_namespace_scope && opts.is_export { + if opts.is_typescript_declare && opts.scope.is_namespace() && opts.is_export { p.has_non_local_export_declare_inside_namespace = true; } diff --git a/src/js_parser/parse/parse_skip_typescript.rs b/src/js_parser/parse/parse_skip_typescript.rs index 2104c55b1990..1e40e3b5597a 100644 --- a/src/js_parser/parse/parse_skip_typescript.rs +++ b/src/js_parser/parse/parse_skip_typescript.rs @@ -1332,7 +1332,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let name = self.lexer.identifier; self.lexer.expect(T::TIdentifier)?; - if opts.is_module_scope { + if opts.scope.is_module() { self.local_type_names.put(name, true)?; } @@ -1354,7 +1354,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let name = self.lexer.identifier; self.lexer.expect(T::TIdentifier)?; - if opts.is_module_scope { + if opts.scope.is_module() { self.local_type_names.put(name, true)?; } diff --git a/src/js_parser/parse/parse_stmt.rs b/src/js_parser/parse/parse_stmt.rs index b04fd5e523c2..67fb248ab74f 100644 --- a/src/js_parser/parse/parse_stmt.rs +++ b/src/js_parser/parse/parse_stmt.rs @@ -14,7 +14,7 @@ use js_lexer::T; use crate::parser::fs; use crate::parser::{ AwaitOrYield, DeferredTsDecorators, LexicalDecl, ParseStatementOptions, ParsedPath, Ref, - StmtList, + StatementScope, StmtList, }; use crate::typescript; use bun_ast::{ImportKind, ImportRecordFlags, ImportRecordTag}; @@ -828,11 +828,13 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O loc: bun_ast::Loc, ) -> Result { let previous_export_keyword = p.esm_export_keyword; - if opts.is_module_scope { - p.esm_export_keyword = p.lexer.range(); - } else if !opts.is_namespace_scope { - p.lexer.unexpected()?; - return Err(crate::Error::SyntaxError); + match opts.scope { + StatementScope::Module => p.esm_export_keyword = p.lexer.range(), + StatementScope::Namespace => {} + StatementScope::Nested => { + p.lexer.unexpected()?; + return Err(crate::Error::SyntaxError); + } } p.lexer.next()?; @@ -860,8 +862,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O T::TImport => { // "export import foo = bar" - if Self::IS_TYPESCRIPT_ENABLED && (opts.is_module_scope || opts.is_namespace_scope) - { + if Self::IS_TYPESCRIPT_ENABLED && opts.scope != StatementScope::Nested { opts.is_export = true; return p.parse_stmt(opts); } @@ -931,7 +932,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O return Err(crate::Error::SyntaxError); } let mut skipper = ParseStatementOptions { - is_module_scope: opts.is_module_scope, + scope: opts.scope, is_export: true, ..Default::default() }; @@ -964,9 +965,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } T::TDefault => { - if !opts.is_module_scope - && (!opts.is_namespace_scope || !opts.is_typescript_declare) - { + if !opts.allows_esm_import_export() { p.lexer.unexpected()?; return Err(crate::Error::SyntaxError); } @@ -1193,9 +1192,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O )) } T::TAsterisk => { - if !opts.is_module_scope - && (!opts.is_namespace_scope || !opts.is_typescript_declare) - { + if !opts.allows_esm_import_export() { p.lexer.unexpected()?; return Err(crate::Error::SyntaxError); } @@ -1275,9 +1272,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O )) } T::TOpenBrace => { - if !opts.is_module_scope - && (!opts.is_namespace_scope || !opts.is_typescript_declare) - { + if !opts.allows_esm_import_export() { p.lexer.unexpected()?; return Err(crate::Error::SyntaxError); } @@ -1407,7 +1402,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let mut was_originally_bare_import = false; // "export import foo = bar" - if (opts.is_export || (opts.is_namespace_scope && !opts.is_typescript_declare)) + if (opts.is_export || (opts.scope.is_namespace() && !opts.is_typescript_declare)) && p.lexer.token != T::TIdentifier { p.lexer.expected(T::TIdentifier)?; @@ -1431,9 +1426,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } T::TStringLiteral | T::TNoSubstitutionTemplateLiteral => { // "import 'path'" - if !opts.is_module_scope - && (!opts.is_namespace_scope || !opts.is_typescript_declare) - { + if !opts.allows_esm_import_export() { p.lexer.unexpected()?; return Err(crate::Error::SyntaxError); } @@ -1441,9 +1434,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } T::TAsterisk => { // "import * as ns from 'path'" - if !opts.is_module_scope - && (!opts.is_namespace_scope || !opts.is_typescript_declare) - { + if !opts.allows_esm_import_export() { p.lexer.unexpected()?; return Err(crate::Error::SyntaxError); } @@ -1461,9 +1452,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } T::TOpenBrace => { // "import {item1, item2} from 'path'" - if !opts.is_module_scope - && (!opts.is_namespace_scope || !opts.is_typescript_declare) - { + if !opts.allows_esm_import_export() { p.lexer.unexpected()?; return Err(crate::Error::SyntaxError); } @@ -1491,7 +1480,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O T::TIdentifier => { // "import defaultItem from 'path'" // "import foo = bar" - if !opts.is_module_scope && !opts.is_namespace_scope { + if opts.scope == StatementScope::Nested { p.lexer.unexpected()?; return Err(crate::Error::SyntaxError); } @@ -1528,9 +1517,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Same scope restriction as `import * as ns from 'path'`: // ESM import declarations are only valid at module scope // (or inside a TypeScript `declare namespace`). - if !opts.is_module_scope - && (!opts.is_namespace_scope || !opts.is_typescript_declare) - { + if !opts.allows_esm_import_export() { p.lexer.unexpected()?; return Err(crate::Error::SyntaxError); } @@ -1606,7 +1593,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Parse TypeScript import assignment statements if p.lexer.token == T::TEquals || opts.is_export - || (opts.is_namespace_scope && !opts.is_typescript_declare) + || (opts.scope.is_namespace() && !opts.is_typescript_declare) { p.esm_import_keyword = previous_import_keyword; // This wasn't an ESM import statement after all; return p.parse_type_script_import_equals_stmt( @@ -1778,7 +1765,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O if p.lexer.token == T::TIdentifier && !p.lexer.has_newline_before { // "type Foo = any" let mut stmt_opts = ParseStatementOptions { - is_module_scope: opts.is_module_scope, + scope: opts.scope, ..Default::default() }; p.skip_type_script_type_stmt(&mut stmt_opts)?; @@ -1792,7 +1779,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // "declare module 'fs' {}" // "declare module 'fs';" if !p.lexer.has_newline_before - && (opts.is_module_scope || opts.is_namespace_scope) + && opts.scope != StatementScope::Nested && (p.lexer.token == T::TIdentifier || (p.lexer.token == T::TStringLiteral && opts.is_typescript_declare)) { @@ -1805,7 +1792,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // "export default interface \n Foo {}" if !p.lexer.has_newline_before || opts.is_name_optional { let mut stmt_opts = ParseStatementOptions { - is_module_scope: opts.is_module_scope, + scope: opts.scope, ..Default::default() }; @@ -1836,7 +1823,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } js_lexer::TypescriptStmtKeyword::TsStmtGlobal => { // "declare module 'fs' { global { namespace NodeJS {} } }" - if opts.is_namespace_scope + if opts.scope.is_namespace() && opts.is_typescript_declare && p.lexer.token == T::TOpenBrace { @@ -1934,7 +1921,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // inside a namespace with an "export var" statement containing all // of the declared bindings. That "export var" statement will later // cause identifiers to be transformed into property accesses. - if opts.is_namespace_scope && opts.is_export { + if opts.scope.is_namespace() && opts.is_export { let mut decls: G::DeclList = bun_alloc::AstAlloc::vec(); match &stmt.data { js_ast::StmtData::SLocal(local) => { diff --git a/src/js_parser/parse/parse_suffix.rs b/src/js_parser/parse/parse_suffix.rs index f4a165bb6adf..36825b386133 100644 --- a/src/js_parser/parse/parse_suffix.rs +++ b/src/js_parser/parse/parse_suffix.rs @@ -130,9 +130,10 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // Remove unnecessary optional chains if p.options.features.minify_syntax { - let result = SideEffects::to_null_or_undefined(p, &left.data); - if result.ok && !result.value { - optional_start = None; + if let Some(result) = SideEffects::to_null_or_undefined(p, &left.data) { + if !result.value { + optional_start = None; + } } } diff --git a/src/js_parser/parse/parse_typescript.rs b/src/js_parser/parse/parse_typescript.rs index 0be9bef96c78..f6b0794b03f3 100644 --- a/src/js_parser/parse/parse_typescript.rs +++ b/src/js_parser/parse/parse_typescript.rs @@ -4,7 +4,7 @@ use bun_collections::VecExt; use crate::Error; use crate::lexer::{self as js_lexer, T}; use crate::p::P; -use crate::parser::{FnOrArrowDataParse, ParseStatementOptions, Ref, ScopeOrder}; +use crate::parser::{FnOrArrowDataParse, ParseStatementOptions, Ref, ScopeOrder, StatementScope}; use bun_alloc::{ArenaVec as BumpVec, ArenaVecExt as _}; use bun_ast::expr::EFlags; use bun_ast::flags; @@ -250,7 +250,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O let mut _opts = ParseStatementOptions { is_export: true, - is_namespace_scope: true, + scope: StatementScope::Namespace, is_typescript_declare: opts.is_typescript_declare, ..ParseStatementOptions::default() }; @@ -263,7 +263,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } else { p.lexer.expect(T::TOpenBrace)?; let mut _opts = ParseStatementOptions { - is_namespace_scope: true, + scope: StatementScope::Namespace, is_typescript_declare: opts.is_typescript_declare, ..ParseStatementOptions::default() }; @@ -398,7 +398,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O || opts.is_typescript_declare { p.pop_and_discard_scope(scope_index); - if opts.is_module_scope { + if opts.scope.is_module() { p.local_type_names.put(name_text, true)?; } return Ok(p.s(S::TypeScript {}, loc)); @@ -733,7 +733,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O p.lexer.expect(T::TCloseBrace)?; if opts.is_typescript_declare { - if opts.is_namespace_scope && opts.is_export { + if opts.scope.is_namespace() && opts.is_export { p.has_non_local_export_declare_inside_namespace = true; } diff --git a/src/js_parser/parser.rs b/src/js_parser/parser.rs index 0d47bad98750..5b55604c3fd4 100644 --- a/src/js_parser/parser.rs +++ b/src/js_parser/parser.rs @@ -1567,12 +1567,34 @@ pub struct ParseClassOptions<'a> { pub(crate) is_type_script_declare: bool, } +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq, Default)] +pub enum StatementScope { + /// Any nested block/function body. ESM import/export are disallowed here. + #[default] + Nested, + /// Top-level module statement list. + Module, + /// Inside a TypeScript `namespace`/`module` block. + Namespace, +} + +impl StatementScope { + #[inline] + pub(crate) fn is_module(self) -> bool { + matches!(self, Self::Module) + } + #[inline] + pub(crate) fn is_namespace(self) -> bool { + matches!(self, Self::Namespace) + } +} + #[derive(Default, Clone, Copy)] pub struct ParseStatementOptions<'a> { pub(crate) ts_decorators: Option>, pub(crate) lexical_decl: LexicalDecl, - pub(crate) is_module_scope: bool, - pub(crate) is_namespace_scope: bool, + pub(crate) scope: StatementScope, pub(crate) is_export: bool, pub(crate) is_using_statement: bool, /// For "export default" pseudo-statements, @@ -1588,6 +1610,15 @@ impl<'a> ParseStatementOptions<'a> { }; !decs.values.is_empty() } + + #[inline] + pub(crate) fn allows_esm_import_export(&self) -> bool { + match self.scope { + StatementScope::Module => true, + StatementScope::Namespace => self.is_typescript_declare, + StatementScope::Nested => false, + } + } } // Only const-able AST node singletons live here. Callers needing the rest diff --git a/src/js_parser/scan/scan_side_effects.rs b/src/js_parser/scan/scan_side_effects.rs index f2b81ba7de92..f541f38bad57 100644 --- a/src/js_parser/scan/scan_side_effects.rs +++ b/src/js_parser/scan/scan_side_effects.rs @@ -15,20 +15,9 @@ pub enum SideEffects { } #[derive(Clone, Copy, Debug)] -pub struct Result { - pub(crate) side_effects: SideEffects, - pub(crate) ok: bool, +pub struct Known { pub(crate) value: bool, -} - -impl Default for Result { - fn default() -> Self { - Self { - side_effects: SideEffects::CouldHaveSideEffects, - ok: false, - value: false, - } - } + pub(crate) side_effects: SideEffects, } #[derive(Clone, Copy)] @@ -78,25 +67,22 @@ impl SideEffects { } ExprData::EBinary(e) => match e.op { Op::Code::BinLogicalAnd => { - let effects = SideEffects::to_boolean(p, &e.right.data); - if effects.ok - && effects.value - && effects.side_effects == SideEffects::NoSideEffects - { - // "if (anything && truthyNoSideEffects)" => "if (anything)" - *expr = e.left; - continue; + if let Some(effects) = SideEffects::to_boolean(p, &e.right.data) { + if effects.value && effects.side_effects == SideEffects::NoSideEffects { + // "if (anything && truthyNoSideEffects)" => "if (anything)" + *expr = e.left; + continue; + } } } Op::Code::BinLogicalOr => { - let effects = SideEffects::to_boolean(p, &e.right.data); - if effects.ok - && !effects.value - && effects.side_effects == SideEffects::NoSideEffects - { - // "if (anything || falsyNoSideEffects)" => "if (anything)" - *expr = e.left; - continue; + if let Some(effects) = SideEffects::to_boolean(p, &e.right.data) { + if !effects.value && effects.side_effects == SideEffects::NoSideEffects + { + // "if (anything || falsyNoSideEffects)" => "if (anything)" + *expr = e.left; + continue; + } } } _ => {} @@ -796,14 +782,9 @@ impl SideEffects { pub(crate) fn to_null_or_undefined<'a, const TS: bool, const SCAN: bool>( p: &P<'a, TS, SCAN>, exp: &ExprData, - ) -> Result { + ) -> Option { if !p.options.features.dead_code_elimination { - // value should not be read if ok is false, all existing calls already adhere to this - return Result { - ok: false, - value: false, - side_effects: SideEffects::CouldHaveSideEffects, - }; + return None; } match exp { // Never null or undefined @@ -814,22 +795,19 @@ impl SideEffects { | ExprData::ERegExp(_) | ExprData::EFunction(_) | ExprData::EArrow(_) - | ExprData::EBigInt(_) => Result { + | ExprData::EBigInt(_) => Some(Known { value: false, side_effects: SideEffects::NoSideEffects, - ok: true, - }, - ExprData::EObject(_) | ExprData::EArray(_) | ExprData::EClass(_) => Result { + }), + ExprData::EObject(_) | ExprData::EArray(_) | ExprData::EClass(_) => Some(Known { value: false, side_effects: SideEffects::CouldHaveSideEffects, - ok: true, - }, + }), // Always null or undefined - ExprData::ENull(_) | ExprData::EUndefined(_) => Result { + ExprData::ENull(_) | ExprData::EUndefined(_) => Some(Known { value: true, side_effects: SideEffects::NoSideEffects, - ok: true, - }, + }), ExprData::EUnary(e) => match e.op { // Always number or bigint Op::Code::UnPos | Op::Code::UnNeg | Op::Code::UnCpl @@ -837,13 +815,13 @@ impl SideEffects { | Op::Code::UnPostDec | Op::Code::UnPostInc // Always boolean | Op::Code::UnNot | Op::Code::UnTypeof | Op::Code::UnDelete => { - Result { value: false, side_effects: SideEffects::CouldHaveSideEffects, ok: true } + Some(Known { value: false, side_effects: SideEffects::CouldHaveSideEffects }) } // Always undefined Op::Code::UnVoid => { - Result { value: true, side_effects: SideEffects::CouldHaveSideEffects, ok: true } + Some(Known { value: true, side_effects: SideEffects::CouldHaveSideEffects }) } - _ => Result::default(), + _ => None, }, ExprData::EBinary(e) => match e.op { // always string or number or bigint @@ -863,209 +841,163 @@ impl SideEffects { | Op::Code::BinLooseNe | Op::Code::BinLt | Op::Code::BinGt | Op::Code::BinLe | Op::Code::BinGe | Op::Code::BinInstanceof | Op::Code::BinIn => { - Result { ok: true, value: false, side_effects: SideEffects::CouldHaveSideEffects } + Some(Known { value: false, side_effects: SideEffects::CouldHaveSideEffects }) } Op::Code::BinComma => { - let res = Self::to_null_or_undefined(p, &e.right.data); - if res.ok { - Result { value: res.value, side_effects: SideEffects::CouldHaveSideEffects, ok: true } - } else { - Result::default() - } + Self::to_null_or_undefined(p, &e.right.data).map(|res| Known { + value: res.value, + side_effects: SideEffects::CouldHaveSideEffects, + }) } - _ => Result::default(), + _ => None, }, ExprData::EInlinedEnum(e) => Self::to_null_or_undefined(p, &e.value.data), - _ => Result::default(), + _ => None, } } pub(crate) fn to_boolean<'a, const TS: bool, const SCAN: bool>( p: &P<'a, TS, SCAN>, exp: &ExprData, - ) -> Result { + ) -> Option { if !p.options.features.dead_code_elimination { - return Result::default(); + return None; } if !p.stack_check.is_safe_to_recurse() { p.report_stack_overflow(bun_ast::Loc::EMPTY); - return Result::default(); + return None; } match exp { - ExprData::ENull(_) | ExprData::EUndefined(_) => Result { + ExprData::ENull(_) | ExprData::EUndefined(_) => Some(Known { value: false, side_effects: SideEffects::NoSideEffects, - ok: true, - }, - ExprData::EBoolean(e) | ExprData::EBranchBoolean(e) => Result { + }), + ExprData::EBoolean(e) | ExprData::EBranchBoolean(e) => Some(Known { value: e.value, side_effects: SideEffects::NoSideEffects, - ok: true, - }, - ExprData::ENumber(e) => Result { + }), + ExprData::ENumber(e) => Some(Known { value: e.value() != 0.0 && !e.value().is_nan(), side_effects: SideEffects::NoSideEffects, - ok: true, - }, - ExprData::EBigInt(e) => { - let equal = E::BigInt::check_equality(&e.value, b"0"); - Result { - value: equal == Some(false), - side_effects: SideEffects::NoSideEffects, - ok: equal.is_some(), - } - } - ExprData::EString(e) => Result { + }), + ExprData::EBigInt(e) => E::BigInt::check_equality(&e.value, b"0").map(|equal| Known { + value: !equal, + side_effects: SideEffects::NoSideEffects, + }), + ExprData::EString(e) => Some(Known { // Open-coded `isPresent` to dodge an ambiguous inherent `len()` // while E.rs's duplicate `impl EString` blocks are being merged. value: e.rope_len > 0 || !e.data.is_empty(), side_effects: SideEffects::NoSideEffects, - ok: true, - }, - ExprData::EFunction(_) | ExprData::EArrow(_) | ExprData::ERegExp(_) => Result { + }), + ExprData::EFunction(_) | ExprData::EArrow(_) | ExprData::ERegExp(_) => Some(Known { value: true, side_effects: SideEffects::NoSideEffects, - ok: true, - }, - ExprData::EObject(_) | ExprData::EArray(_) | ExprData::EClass(_) => Result { + }), + ExprData::EObject(_) | ExprData::EArray(_) | ExprData::EClass(_) => Some(Known { value: true, side_effects: SideEffects::CouldHaveSideEffects, - ok: true, - }, + }), ExprData::EUnary(e) => match e.op { - Op::Code::UnVoid => Result { + Op::Code::UnVoid => Some(Known { value: false, side_effects: SideEffects::CouldHaveSideEffects, - ok: true, - }, + }), Op::Code::UnTypeof => { // Never an empty string - Result { + Some(Known { value: true, side_effects: SideEffects::CouldHaveSideEffects, - ok: true, - } + }) } - Op::Code::UnNot => { - let res = Self::to_boolean(p, &e.value.data); - if res.ok { - Result { - value: !res.value, - side_effects: res.side_effects, - ok: true, - } - } else { - Result::default() - } - } - _ => Result::default(), + Op::Code::UnNot => Self::to_boolean(p, &e.value.data).map(|res| Known { + value: !res.value, + side_effects: res.side_effects, + }), + _ => None, }, ExprData::EBinary(e) => match e.op { - Op::Code::BinLogicalOr => { - let res = Self::to_boolean(p, &e.right.data); - if res.ok && res.value { - Result { - value: true, - side_effects: SideEffects::CouldHaveSideEffects, - ok: true, - } - } else { - Result::default() - } - } - Op::Code::BinLogicalAnd => { - let res = Self::to_boolean(p, &e.right.data); - if res.ok && !res.value { - Result { - value: false, - side_effects: SideEffects::CouldHaveSideEffects, - ok: true, - } - } else { - Result::default() - } - } - Op::Code::BinComma => { - let res = Self::to_boolean(p, &e.right.data); - if res.ok { - Result { - value: res.value, - side_effects: SideEffects::CouldHaveSideEffects, - ok: true, - } - } else { - Result::default() - } - } + Op::Code::BinLogicalOr => match Self::to_boolean(p, &e.right.data) { + Some(res) if res.value => Some(Known { + value: true, + side_effects: SideEffects::CouldHaveSideEffects, + }), + _ => None, + }, + Op::Code::BinLogicalAnd => match Self::to_boolean(p, &e.right.data) { + Some(res) if !res.value => Some(Known { + value: false, + side_effects: SideEffects::CouldHaveSideEffects, + }), + _ => None, + }, + Op::Code::BinComma => Self::to_boolean(p, &e.right.data).map(|res| Known { + value: res.value, + side_effects: SideEffects::CouldHaveSideEffects, + }), Op::Code::BinGt => { if let Some(left_num) = e.left.data.to_finite_number() { if let Some(right_num) = e.right.data.to_finite_number() { - return Result { - ok: true, + return Some(Known { value: left_num > right_num, side_effects: SideEffects::NoSideEffects, - }; + }); } } - Result::default() + None } Op::Code::BinLt => { if let Some(left_num) = e.left.data.to_finite_number() { if let Some(right_num) = e.right.data.to_finite_number() { - return Result { - ok: true, + return Some(Known { value: left_num < right_num, side_effects: SideEffects::NoSideEffects, - }; + }); } } - Result::default() + None } Op::Code::BinLe => { if let Some(left_num) = e.left.data.to_finite_number() { if let Some(right_num) = e.right.data.to_finite_number() { - return Result { - ok: true, + return Some(Known { value: left_num <= right_num, side_effects: SideEffects::NoSideEffects, - }; + }); } } - Result::default() + None } Op::Code::BinGe => { if let Some(left_num) = e.left.data.to_finite_number() { if let Some(right_num) = e.right.data.to_finite_number() { - return Result { - ok: true, + return Some(Known { value: left_num >= right_num, side_effects: SideEffects::NoSideEffects, - }; + }); } } - Result::default() + None } - _ => Result::default(), + _ => None, }, ExprData::EInlinedEnum(e) => Self::to_boolean(p, &e.value.data), ExprData::ESpecial(special) => match special { E::Special::ModuleExports | E::Special::ResolvedSpecifierString(_) - | E::Special::HotData => Result::default(), + | E::Special::HotData => None, E::Special::HotAccept | E::Special::HotAcceptVisited | E::Special::HotEnabled => { - Result { - ok: true, + Some(Known { value: true, side_effects: SideEffects::NoSideEffects, - } + }) } - E::Special::HotDisabled => Result { - ok: true, + E::Special::HotDisabled => Some(Known { value: false, side_effects: SideEffects::NoSideEffects, - }, + }), }, - _ => Result::default(), + _ => None, } } } diff --git a/src/js_parser/visit/visit_binary.rs b/src/js_parser/visit/visit_binary.rs index 0b7d11afe789..71181f7761dd 100644 --- a/src/js_parser/visit/visit_binary.rs +++ b/src/js_parser/visit/visit_binary.rs @@ -134,8 +134,7 @@ impl BinaryExpressionVisitor { // Mark the control flow as dead if the branch is never taken match e_.op { Op::Code::BinLogicalOr => { - let side_effects = SideEffects::to_boolean(p, &e_.left.data); - if side_effects.ok && side_effects.value { + if SideEffects::to_boolean(p, &e_.left.data).is_some_and(|k| k.value) { // "true || dead" let old = p.is_control_flow_dead; p.is_control_flow_dead = true; @@ -146,8 +145,7 @@ impl BinaryExpressionVisitor { } } Op::Code::BinLogicalAnd => { - let side_effects = SideEffects::to_boolean(p, &e_.left.data); - if side_effects.ok && !side_effects.value { + if SideEffects::to_boolean(p, &e_.left.data).is_some_and(|k| !k.value) { // "false && dead" let old = p.is_control_flow_dead; p.is_control_flow_dead = true; @@ -158,8 +156,7 @@ impl BinaryExpressionVisitor { } } Op::Code::BinNullishCoalescing => { - let side_effects = SideEffects::to_null_or_undefined(p, &e_.left.data); - if side_effects.ok && !side_effects.value { + if SideEffects::to_null_or_undefined(p, &e_.left.data).is_some_and(|k| !k.value) { // "notNullOrUndefined ?? dead" let old = p.is_control_flow_dead; p.is_control_flow_dead = true; @@ -361,8 +358,8 @@ impl BinaryExpressionVisitor { } } Op::Code::BinNullishCoalescing => { - let null_or_undefined = SideEffects::to_null_or_undefined(p, &e_.left.data); - if null_or_undefined.ok { + if let Some(null_or_undefined) = SideEffects::to_null_or_undefined(p, &e_.left.data) + { if !null_or_undefined.value { return e_.left; } else if null_or_undefined.side_effects == SideEffects::NoSideEffects { @@ -384,30 +381,29 @@ impl BinaryExpressionVisitor { } } Op::Code::BinLogicalOr => { - let side_effects = SideEffects::to_boolean(p, &e_.left.data); - if side_effects.ok && side_effects.value { - return e_.left; - } else if side_effects.ok && side_effects.side_effects == SideEffects::NoSideEffects - { - // "(0 || fn)()" => "fn()" - // "(0 || this.fn)" => "this.fn" - // "(0 || this.fn)()" => "(0, this.fn)()" - if is_call_target && e_.right.has_value_for_this_in_call() { - return Expr::join_with_comma( - Expr { - data: prefill::data::ZERO, - loc: e_.left.loc, - }, - e_.right, - ); - } + if let Some(side_effects) = SideEffects::to_boolean(p, &e_.left.data) { + if side_effects.value { + return e_.left; + } else if side_effects.side_effects == SideEffects::NoSideEffects { + // "(0 || fn)()" => "fn()" + // "(0 || this.fn)" => "this.fn" + // "(0 || this.fn)()" => "(0, this.fn)()" + if is_call_target && e_.right.has_value_for_this_in_call() { + return Expr::join_with_comma( + Expr { + data: prefill::data::ZERO, + loc: e_.left.loc, + }, + e_.right, + ); + } - return e_.right; + return e_.right; + } } } Op::Code::BinLogicalAnd => { - let side_effects = SideEffects::to_boolean(p, &e_.left.data); - if side_effects.ok { + if let Some(side_effects) = SideEffects::to_boolean(p, &e_.left.data) { if !side_effects.value { return e_.left; } else if side_effects.side_effects == SideEffects::NoSideEffects { diff --git a/src/js_parser/visit/visit_expr.rs b/src/js_parser/visit/visit_expr.rs index 0d5c834fde53..61534b1804f8 100644 --- a/src/js_parser/visit/visit_expr.rs +++ b/src/js_parser/visit/visit_expr.rs @@ -1237,18 +1237,18 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O e_.value = SideEffects::simplify_boolean(p, e_.value); } - let side_effects = SideEffects::to_boolean(p, &e_.value.data); - if side_effects.ok - && (side_effects.side_effects == SideEffects::NoSideEffects - || p.expr_can_be_removed_if_unused(&e_.value)) - { - *e = p.new_expr( - E::Boolean { - value: !side_effects.value, - }, - expr.loc, - ); - return; + if let Some(side_effects) = SideEffects::to_boolean(p, &e_.value.data) { + if side_effects.side_effects == SideEffects::NoSideEffects + || p.expr_can_be_removed_if_unused(&e_.value) + { + *e = p.new_expr( + E::Boolean { + value: !side_effects.value, + }, + expr.loc, + ); + return; + } } if p.options.features.minify_syntax { @@ -1468,68 +1468,65 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O e_.test = SideEffects::simplify_boolean(p, e_.test); - let side_effects = SideEffects::to_boolean(p, &e_.test.data); + let Some(side_effects) = SideEffects::to_boolean(p, &e_.test.data) else { + p.visit_expr(&mut e_.yes); + p.visit_expr(&mut e_.no); + return; + }; - if !side_effects.ok { + // Mark the control flow as dead if the branch is never taken + if side_effects.value { + // "true ? live : dead" p.visit_expr(&mut e_.yes); + let old = p.is_control_flow_dead; + p.is_control_flow_dead = true; p.visit_expr(&mut e_.no); - } else { - // Mark the control flow as dead if the branch is never taken - if side_effects.value { - // "true ? live : dead" - p.visit_expr(&mut e_.yes); - let old = p.is_control_flow_dead; - p.is_control_flow_dead = true; - p.visit_expr(&mut e_.no); - p.is_control_flow_dead = old; - - if side_effects.side_effects == SideEffects::CouldHaveSideEffects { - *e = SideEffects::simplify_unused_expr(p, e_.test) - .unwrap_or_else(|| p.new_expr(E::Missing {}, e_.test.loc)) - .join_with_comma(e_.yes); - return; - } + p.is_control_flow_dead = old; - // "(1 ? fn : 2)()" => "fn()" - // "(1 ? this.fn : 2)" => "this.fn" - // "(1 ? this.fn : 2)()" => "(0, this.fn)()" - if is_call_target && e_.yes.has_value_for_this_in_call() { - *e = p - .new_expr(E::Number::new(0.0), e_.test.loc) - .join_with_comma(e_.yes); - return; - } + if side_effects.side_effects == SideEffects::CouldHaveSideEffects { + *e = SideEffects::simplify_unused_expr(p, e_.test) + .unwrap_or_else(|| p.new_expr(E::Missing {}, e_.test.loc)) + .join_with_comma(e_.yes); + return; + } - *e = e_.yes; + // "(1 ? fn : 2)()" => "fn()" + // "(1 ? this.fn : 2)" => "this.fn" + // "(1 ? this.fn : 2)()" => "(0, this.fn)()" + if is_call_target && e_.yes.has_value_for_this_in_call() { + *e = p + .new_expr(E::Number::new(0.0), e_.test.loc) + .join_with_comma(e_.yes); return; - } else { - // "false ? dead : live" - let old = p.is_control_flow_dead; - p.is_control_flow_dead = true; - p.visit_expr(&mut e_.yes); - p.is_control_flow_dead = old; - p.visit_expr(&mut e_.no); - - // "(a, false) ? b : c" => "a, c" - if side_effects.side_effects == SideEffects::CouldHaveSideEffects { - *e = SideEffects::simplify_unused_expr(p, e_.test) - .unwrap_or_else(|| p.new_expr(E::Missing {}, e_.test.loc)) - .join_with_comma(e_.no); - return; - } + } - // "(1 ? fn : 2)()" => "fn()" - // "(1 ? this.fn : 2)" => "this.fn" - // "(1 ? this.fn : 2)()" => "(0, this.fn)()" - if is_call_target && e_.no.has_value_for_this_in_call() { - *e = p - .new_expr(E::Number::new(0.0), e_.test.loc) - .join_with_comma(e_.no); - return; - } - *e = e_.no; + *e = e_.yes; + } else { + // "false ? dead : live" + let old = p.is_control_flow_dead; + p.is_control_flow_dead = true; + p.visit_expr(&mut e_.yes); + p.is_control_flow_dead = old; + p.visit_expr(&mut e_.no); + + // "(a, false) ? b : c" => "a, c" + if side_effects.side_effects == SideEffects::CouldHaveSideEffects { + *e = SideEffects::simplify_unused_expr(p, e_.test) + .unwrap_or_else(|| p.new_expr(E::Missing {}, e_.test.loc)) + .join_with_comma(e_.no); + return; + } + + // "(1 ? fn : 2)()" => "fn()" + // "(1 ? this.fn : 2)" => "this.fn" + // "(1 ? this.fn : 2)()" => "(0, this.fn)()" + if is_call_target && e_.no.has_value_for_this_in_call() { + *e = p + .new_expr(E::Number::new(0.0), e_.test.loc) + .join_with_comma(e_.no); return; } + *e = e_.no; } } diff --git a/src/js_parser/visit/visit_stmt.rs b/src/js_parser/visit/visit_stmt.rs index c28c444521cf..be22500b5b4a 100644 --- a/src/js_parser/visit/visit_stmt.rs +++ b/src/js_parser/visit/visit_stmt.rs @@ -1653,14 +1653,15 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O data.body = p.visit_loop_body(data.body); data.test = SideEffects::simplify_boolean(p, data.test); - let result = SideEffects::to_boolean(p, &data.test.data); - if result.ok && result.side_effects == SideEffects::NoSideEffects { - data.test = p.new_expr( - E::Boolean { - value: result.value, - }, - data.test.loc, - ); + if let Some(result) = SideEffects::to_boolean(p, &data.test.data) { + if result.side_effects == SideEffects::NoSideEffects { + data.test = p.new_expr( + E::Boolean { + value: result.value, + }, + data.test.loc, + ); + } } stmts.push(*stmt); @@ -1697,7 +1698,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } let effects = SideEffects::to_boolean(p, &data.test.data); - if effects.ok && !effects.value { + if effects.is_some_and(|k| !k.value) { let old = p.is_control_flow_dead; p.is_control_flow_dead = true; data.yes = p.visit_single_stmt(data.yes, StmtsKind::None); @@ -1708,7 +1709,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O // The "else" clause is optional if let Some(no) = data.no { - if effects.ok && effects.value { + if effects.is_some_and(|k| k.value) { let old = p.is_control_flow_dead; p.is_control_flow_dead = true; data.no = Some(p.visit_single_stmt(no, StmtsKind::None)); @@ -1739,7 +1740,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O } if p.options.features.minify_syntax { - if effects.ok { + if let Some(effects) = effects { if effects.value { if data.no.is_none() || !SideEffects::should_keep_stmt_in_dead_control_flow( @@ -1837,9 +1838,10 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O p.visit_expr(&mut test); data.test = Some(SideEffects::simplify_boolean(p, test)); - let result = SideEffects::to_boolean(p, &data.test.unwrap().data); - if result.ok && result.value && result.side_effects == SideEffects::NoSideEffects { - data.test = None; + if let Some(result) = SideEffects::to_boolean(p, &data.test.unwrap().data) { + if result.value && result.side_effects == SideEffects::NoSideEffects { + data.test = None; + } } }