Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 89 additions & 11 deletions src/ast/ts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,40 @@ impl Data {
// Data-only; the parser-state predicates that depend on `P` stay in
// `bun_js_parser::typescript`.

/// tsconfig's `emitDecoratorMetadata` widened with the effective
/// `strictNullChecks`, because tsc's type serializer reads both.
Comment thread
robobun marked this conversation as resolved.
Resolved
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
pub enum DecoratorMetadata {
/// `emitDecoratorMetadata` is off.
#[default]
Off = 0,
/// `emitDecoratorMetadata` is on and `strictNullChecks` is off.
Loose = 1,
/// `emitDecoratorMetadata` is on and `strictNullChecks` is on.
Strict = 2,
}

impl DecoratorMetadata {
pub const fn new(emit_decorator_metadata: bool, strict_null_checks: bool) -> Self {
match (emit_decorator_metadata, strict_null_checks) {
(false, _) => Self::Off,
(true, false) => Self::Loose,
(true, true) => Self::Strict,
}
}

#[inline]
pub const fn is_on(self) -> bool {
!matches!(self, Self::Off)
}

#[inline]
pub const fn strict_null_checks(self) -> bool {
matches!(self, Self::Strict)
}
}

#[derive(Clone, Default)]
pub enum Metadata {
#[default]
Expand Down Expand Up @@ -182,13 +216,28 @@ impl Metadata {
// the logic in finish_union, merge_union, finish_intersection and merge_intersection is
// translated from:
// https://github.com/microsoft/TypeScript/blob/e0a324b0503be479f2b33fd2e17c6e86c94d1297/src/compiler/transformers/typeSerializer.ts#L402
//
// With `strict_null_checks` off, `null` and `undefined` are elided. With it
// on they serialize to `void 0` like `void`, so `string | undefined` is `Object`.
Comment thread
robobun marked this conversation as resolved.
Resolved

/// Serializes to `void 0`. `MNever` is handled before any comparison.
fn is_void_like(&self) -> bool {
matches!(
self,
Metadata::MNull | Metadata::MUndefined | Metadata::MVoid
)
}

/// Return the final union type if possible, or return None to continue merging.
///
/// If the current type is MNever, MNull, or MUndefined assign the current type
/// to MNone and return None to ensure it's always replaced by the next type.
/// MNever (and MNull / MUndefined without strict null checks) becomes MNone
/// so the next type replaces it.
Comment thread
robobun marked this conversation as resolved.
Resolved
/// `load_name`: closure form of `p.load_name_from_ref` to avoid coupling Metadata to P.
pub fn finish_union<'b, F: Fn(Ref) -> &'b [u8]>(&mut self, load_name: F) -> Option<Self> {
pub fn finish_union<'b, F: Fn(Ref) -> &'b [u8]>(
&mut self,
strict_null_checks: bool,
load_name: F,
) -> Option<Self> {
let current = self;
match current {
Metadata::MIdentifier(r) => {
Expand All @@ -200,7 +249,12 @@ impl Metadata {

Metadata::MUnknown | Metadata::MAny | Metadata::MObject => Some(Metadata::MObject),

Metadata::MNever | Metadata::MNull | Metadata::MUndefined => {
Metadata::MNever => {
*current = Metadata::MNone;
None
}

Metadata::MNull | Metadata::MUndefined if !strict_null_checks => {
*current = Metadata::MNone;
None
}
Comment thread
robobun marked this conversation as resolved.
Resolved
Comment thread
robobun marked this conversation as resolved.
Resolved
Expand All @@ -209,12 +263,21 @@ impl Metadata {
}
}

pub fn merge_union(&mut self, left: Self) {
pub fn merge_union(&mut self, strict_null_checks: bool, left: Self) {
let result = self;
if !matches!(left, Metadata::MNone) {
if core::mem::discriminant(result) != core::mem::discriminant(&left) {
*result = match result {
Metadata::MNever | Metadata::MUndefined | Metadata::MNull => left,
Metadata::MNever => left,

// both sides are `void 0`
Metadata::MNull | Metadata::MUndefined | Metadata::MVoid
if left.is_void_like() =>
{
left
}

Metadata::MNull | Metadata::MUndefined if !strict_null_checks => left,

_ => Metadata::MObject,
};
Expand All @@ -236,10 +299,11 @@ impl Metadata {

/// Return the final intersection type if possible, or return None to continue merging.
///
/// If the current type is MUnknown, MNull, or MUndefined assign the current type
/// to MNone and return None to ensure it's always replaced by the next type.
/// MUnknown (and MNull / MUndefined without strict null checks) becomes
/// MNone so the next type replaces it.
Comment thread
robobun marked this conversation as resolved.
Resolved
pub fn finish_intersection<'b, F: Fn(Ref) -> &'b [u8]>(
&mut self,
strict_null_checks: bool,
load_name: F,
) -> Option<Self> {
let current = self;
Expand All @@ -256,7 +320,12 @@ impl Metadata {

Metadata::MAny | Metadata::MObject => Some(Metadata::MObject),

Metadata::MUnknown | Metadata::MNull | Metadata::MUndefined => {
Metadata::MUnknown => {
*current = Metadata::MNone;
None
}

Metadata::MNull | Metadata::MUndefined if !strict_null_checks => {
*current = Metadata::MNone;
None
}
Expand All @@ -265,16 +334,25 @@ impl Metadata {
}
}

pub fn merge_intersection(&mut self, left: Self) {
pub fn merge_intersection(&mut self, strict_null_checks: bool, left: Self) {
let result = self;
if !matches!(left, Metadata::MNone) {
if core::mem::discriminant(result) != core::mem::discriminant(&left) {
*result = match result {
Metadata::MUnknown | Metadata::MUndefined | Metadata::MNull => left,
Metadata::MUnknown => left,

// ensure MNever is the final type
Metadata::MNever => Metadata::MNever,

// both sides are `void 0`
Metadata::MNull | Metadata::MUndefined | Metadata::MVoid
if left.is_void_like() =>
{
left
}

Metadata::MNull | Metadata::MUndefined if !strict_null_checks => left,

_ => Metadata::MObject,
};
} else {
Expand Down
13 changes: 7 additions & 6 deletions src/bundler/ParseTask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use core::sync::atomic::{AtomicU32, Ordering};
use crate::Error as AnyError;
use bun_alloc::Arena as Bump; // bumpalo::Bump re-export
use bun_ast::ImportRecord;
use bun_ast::ts::DecoratorMetadata;
use bun_ast::{Loc, Location, Log, Msg, Source};
use bun_collections::VecExt;
use bun_core::strings;
Expand Down Expand Up @@ -107,7 +108,7 @@ pub struct ParseTask {

pub(crate) known_target: options::Target,
pub(crate) module_type: options::ModuleType,
pub(crate) emit_decorator_metadata: bool,
pub(crate) decorator_metadata: DecoratorMetadata,
pub(crate) experimental_decorators: bool,
pub(crate) use_define_for_class_fields: bool,
// BACKREF; `None` only before enqueue (`Default`, runtime source).
Expand Down Expand Up @@ -287,7 +288,7 @@ impl ParseTask {
jsx: resolve_result.jsx.clone(),
source_index,
module_type: resolve_result.module_type,
emit_decorator_metadata: resolve_result.flags.emit_decorator_metadata(),
decorator_metadata: resolve_result.flags.decorator_metadata(),
experimental_decorators: resolve_result.flags.experimental_decorators(),
use_define_for_class_fields: resolve_result.flags.use_define_for_class_fields(),
package_version,
Expand Down Expand Up @@ -341,7 +342,7 @@ impl Default for ParseTask {
stage: ParseTaskStage::NeedsSourceCode,
known_target: options::Target::default(),
module_type: options::ModuleType::Unknown,
emit_decorator_metadata: false,
decorator_metadata: DecoratorMetadata::Off,
experimental_decorators: false,
use_define_for_class_fields: true,
package_version: ast::StoreStr::EMPTY,
Expand Down Expand Up @@ -620,7 +621,7 @@ pub mod parse_worker {
},
stage: ParseTaskStage::NeedsSourceCode,
module_type: options::ModuleType::Unknown,
emit_decorator_metadata: false,
decorator_metadata: DecoratorMetadata::Off,
experimental_decorators: false,
use_define_for_class_fields: true,
package_version: ast::StoreStr::EMPTY,
Expand Down Expand Up @@ -2539,12 +2540,12 @@ pub mod parse_worker {
opts.features.minify_keep_names = topts.keep_names;
opts.features.minify_whitespace = topts.minify_whitespace;
opts.use_define_for_class_fields = task.use_define_for_class_fields;
opts.features.emit_decorator_metadata = task.emit_decorator_metadata;
opts.features.decorator_metadata = task.decorator_metadata;
// emitDecoratorMetadata implies legacy/experimental decorators, as it only
// makes sense with TypeScript's legacy decorator system (reflect-metadata).
// TC39 standard decorators have their own metadata mechanism.
opts.features.standard_decorators = !loader.is_typescript()
|| !(task.experimental_decorators || task.emit_decorator_metadata);
|| !(task.experimental_decorators || task.decorator_metadata.is_on());
opts.features.unwrap_commonjs_packages = topts.unwrap_commonjs_packages;
opts.features.no_macros = topts.no_macros;
// Modeled as
Expand Down
1 change: 0 additions & 1 deletion src/bundler/bundle_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4128,7 +4128,6 @@ pub mod bv2_impl {
jsx,
source_index: bun_ast::Index::init(source_index.get()),
module_type,
emit_decorator_metadata: false, // TODO
package_version: bun_ast::StoreStr::EMPTY,
loader: Some(loader),
known_target,
Expand Down
7 changes: 4 additions & 3 deletions src/bundler/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//! into the concrete `BundleOptions` the bundler and runtime consume.

use bun_analytics as analytics;
use bun_ast::ts::DecoratorMetadata;
use bun_collections::{StringArrayHashMap, StringHashMap};
use bun_core::strings;
use bun_core::{Global, Output};
Expand Down Expand Up @@ -1207,7 +1208,7 @@ pub struct BundleOptions<'a> {
pub bundler_feature_flags: Option<Box<StringSet>>,
pub loaders: LoaderHashTable,
pub jsx: jsx::Pragma,
pub emit_decorator_metadata: bool,
pub decorator_metadata: DecoratorMetadata,
pub experimental_decorators: bool,
pub use_define_for_class_fields: bool,
pub auto_import_jsx: bool,
Expand Down Expand Up @@ -1459,7 +1460,7 @@ impl<'a> BundleOptions<'a> {
.map(|s| Box::new(bun_core::handle_oom(s.clone()))),
loaders: bun_core::handle_oom(self.loaders.clone()),
jsx: self.jsx.clone(),
emit_decorator_metadata: self.emit_decorator_metadata,
decorator_metadata: self.decorator_metadata,
experimental_decorators: self.experimental_decorators,
use_define_for_class_fields: self.use_define_for_class_fields,
auto_import_jsx: self.auto_import_jsx,
Expand Down Expand Up @@ -1724,7 +1725,7 @@ impl<'a> BundleOptions<'a> {
bundler_feature_flags,

jsx: jsx::Pragma::default(),
emit_decorator_metadata: false,
decorator_metadata: DecoratorMetadata::Off,
experimental_decorators: false,
use_define_for_class_fields: true,
auto_import_jsx: true,
Expand Down
14 changes: 8 additions & 6 deletions src/bundler/transpiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// ══════════════════════════════════════════════════════════════════════════

use bun_alloc::Arena;
use bun_ast::ts::DecoratorMetadata;
use bun_collections::HashMap;
use bun_collections::VecExt;
use bun_dotenv as dot_env;
Expand Down Expand Up @@ -734,7 +735,7 @@ impl<'a> Transpiler<'a> {
if self.options.transform_options.jsx.is_none() {
self.options.jsx = jsx_pragma_from_resolver(&tsconfig.jsx);
}
self.options.emit_decorator_metadata = tsconfig.emit_decorator_metadata;
self.options.decorator_metadata = tsconfig.decorator_metadata();
self.options.experimental_decorators = tsconfig.experimental_decorators;
if let Some(v) = tsconfig.use_define_for_class_fields {
self.options.use_define_for_class_fields = v;
Expand Down Expand Up @@ -995,7 +996,7 @@ pub struct ParseOptions<'a, 'b> {
pub replace_exports: bun_collections::StringArrayHashMap<bun_ast::runtime::ReplaceableExport>,
pub inject_jest_globals: bool,
pub set_breakpoint_on_first_line: bool,
pub emit_decorator_metadata: bool,
pub decorator_metadata: DecoratorMetadata,
pub experimental_decorators: bool,
pub use_define_for_class_fields: bool,
pub remove_cjs_module_wrapper: bool,
Expand Down Expand Up @@ -1602,12 +1603,13 @@ impl<'a> Transpiler<'a> {
is_entry_point: false,
};

opts.features.emit_decorator_metadata = this_parse.emit_decorator_metadata;
opts.features.decorator_metadata = this_parse.decorator_metadata;
// emitDecoratorMetadata implies legacy/experimental decorators, as it only
// makes sense with TypeScript's legacy decorator system (reflect-metadata).
// TC39 standard decorators have their own metadata mechanism.
opts.features.standard_decorators = !loader.is_typescript()
|| !(this_parse.experimental_decorators || this_parse.emit_decorator_metadata);
|| !(this_parse.experimental_decorators
|| this_parse.decorator_metadata.is_on());
opts.features.allow_runtime = self.options.allow_runtime;
opts.features.set_breakpoint_on_first_line =
this_parse.set_breakpoint_on_first_line;
Expand Down Expand Up @@ -2938,7 +2940,7 @@ impl<'a> Transpiler<'a> {
// the option fields needed for `ParseOptions` first.
let jsx = jsx_pragma_from_resolver(&resolve_result.jsx);
let dirname_fd = resolve_result.dirname_fd;
let emit_decorator_metadata = resolve_result.flags.emit_decorator_metadata();
let decorator_metadata = resolve_result.flags.decorator_metadata();
let experimental_decorators = resolve_result.flags.experimental_decorators();
let use_define_for_class_fields =
resolve_result.flags.use_define_for_class_fields();
Expand Down Expand Up @@ -2968,7 +2970,7 @@ impl<'a> Transpiler<'a> {
macro_remappings,
macro_js_ctx: default_macro_js_value(),
jsx,
emit_decorator_metadata,
decorator_metadata,
experimental_decorators,
use_define_for_class_fields,
virtual_source: None,
Expand Down
6 changes: 3 additions & 3 deletions src/js_parser/p.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7440,7 +7440,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O

let mut array = BumpVec::<Expr>::new_in(self.arena);

if self.options.features.emit_decorator_metadata {
if self.options.features.decorator_metadata.is_on() {
self.emit_decorator_metadata_for_prop(prop, &mut array, loc);
}

Expand Down Expand Up @@ -7677,7 +7677,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
if s_class.class.ts_decorators.len_u32() > 0 {
let mut array: Vec<Expr> = s_class.class.ts_decorators.move_to_list_managed();

if self.options.features.emit_decorator_metadata {
if self.options.features.decorator_metadata.is_on() {
if let Some(cf) = constructor_function {
// design:paramtypes
let constructor_args: &[G::Arg] = cf.func.args.slice();
Expand Down Expand Up @@ -9705,7 +9705,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O

if !TYPESCRIPT {
// This is so it doesn't impact runtime transpiler caching when not in use
opts.features.emit_decorator_metadata = false;
opts.features.decorator_metadata = bun_ast::ts::DecoratorMetadata::Off;
}

let unwrap_all_requires = 'brk: {
Expand Down
2 changes: 1 addition & 1 deletion src/js_parser/parse/parse_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ impl<'a> Options<'a> {
unwrap_commonjs_packages: f.unwrap_commonjs_packages,
commonjs_at_runtime: f.commonjs_at_runtime,
unwrap_commonjs_to_esm: f.unwrap_commonjs_to_esm,
emit_decorator_metadata: f.emit_decorator_metadata,
decorator_metadata: f.decorator_metadata,
standard_decorators: f.standard_decorators,
remove_cjs_module_wrapper: f.remove_cjs_module_wrapper,
runtime_transpiler_cache: None,
Expand Down
6 changes: 3 additions & 3 deletions src/js_parser/parse/parse_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
if p.lexer.token == T::TColon {
p.lexer.next()?;
if !rest_arg {
if p.options.features.emit_decorator_metadata
if p.options.features.decorator_metadata.is_on()
&& opts.allow_ts_decorators
&& (opts.has_argument_decorators
|| opts.has_decorators
Expand Down Expand Up @@ -372,15 +372,15 @@ impl<'a, const TYPESCRIPT: bool, const SCAN_ONLY: bool> P<'a, TYPESCRIPT, SCAN_O
if p.lexer.token == T::TColon {
p.lexer.next()?;

if p.options.features.emit_decorator_metadata
if p.options.features.decorator_metadata.is_on()
&& opts.allow_ts_decorators
&& (opts.has_argument_decorators || opts.has_decorators)
{
func.return_ts_metadata = p.skip_typescript_return_type_with_metadata()?;
} else {
p.skip_typescript_return_type()?;
}
} else if p.options.features.emit_decorator_metadata
} else if p.options.features.decorator_metadata.is_on()
&& opts.allow_ts_decorators
&& (opts.has_argument_decorators || opts.has_decorators)
{
Expand Down
Loading
Loading