From 9f1cf788140264023f55d53fdaed056621504416 Mon Sep 17 00:00:00 2001 From: TheHypnoo Date: Sun, 12 Jul 2026 18:49:30 +0200 Subject: [PATCH] Fix namespace variable export ABI --- crates/perry-codegen/src/codegen/closure.rs | 1 - crates/perry-codegen/src/codegen/entry.rs | 2 - crates/perry-codegen/src/codegen/function.rs | 1 - crates/perry-codegen/src/codegen/method.rs | 2 - crates/perry-codegen/src/codegen/mod.rs | 1 - crates/perry-codegen/src/codegen/opts.rs | 17 - crates/perry-codegen/src/expr/mod.rs | 8 - .../perry-codegen/src/expr/static_method.rs | 16 +- .../tests/argless_builtin_extra_args.rs | 1 - .../perry-codegen/tests/class_keys_gc_root.rs | 1 - .../tests/constructor_recursion.rs | 1 - .../tests/destructure_call_location.rs | 1 - .../tests/large_object_barriers.rs | 1 - .../tests/macos_bundle_chdir_gate.rs | 1 - .../tests/native_proof_buffer_views.rs | 1 - .../tests/native_proof_regressions.rs | 1 - .../tests/shadow_slot_hygiene.rs | 1 - .../tests/static_symbol_hygiene.rs | 1 - crates/perry-codegen/tests/typed_feedback.rs | 1 - .../tests/typed_shape_descriptor.rs | 1 - .../tests/typed_shape_descriptors.rs | 1 - .../src/commands/compile/object_cache.rs | 15 - .../src/commands/compile/run_pipeline.rs | 30 +- .../tests/namespace_variable_export_abi.rs | 132 +++ .../arraybuffer-dataview-semantics.md | 1035 ----------------- target/deepwiki/object-literal-semantics.md | 380 ------ .../main.ts | 5 +- 27 files changed, 149 insertions(+), 1509 deletions(-) create mode 100644 crates/perry/tests/namespace_variable_export_abi.rs delete mode 100644 target/deepwiki/arraybuffer-dataview-semantics.md delete mode 100644 target/deepwiki/object-literal-semantics.md diff --git a/crates/perry-codegen/src/codegen/closure.rs b/crates/perry-codegen/src/codegen/closure.rs index 782207c5ff..8ef6594031 100644 --- a/crates/perry-codegen/src/codegen/closure.rs +++ b/crates/perry-codegen/src/codegen/closure.rs @@ -804,7 +804,6 @@ pub(super) fn compile_closure( option_object_locals: HashMap::new(), object_literal_locals: HashSet::new(), namespace_imports: &cross_module.namespace_imports, - namespace_reexport_named_imports: &cross_module.namespace_reexport_named_imports, namespace_member_prefixes: &cross_module.namespace_member_prefixes, namespace_member_origin_names: &cross_module.namespace_member_origin_names, imported_async_funcs: &cross_module.imported_async_funcs, diff --git a/crates/perry-codegen/src/codegen/entry.rs b/crates/perry-codegen/src/codegen/entry.rs index 9aea84e19a..759f2ac244 100644 --- a/crates/perry-codegen/src/codegen/entry.rs +++ b/crates/perry-codegen/src/codegen/entry.rs @@ -612,7 +612,6 @@ pub(super) fn compile_module_entry( option_object_locals: HashMap::new(), object_literal_locals: HashSet::new(), namespace_imports: &cross_module.namespace_imports, - namespace_reexport_named_imports: &cross_module.namespace_reexport_named_imports, namespace_member_prefixes: &cross_module.namespace_member_prefixes, namespace_member_origin_names: &cross_module.namespace_member_origin_names, imported_async_funcs: &cross_module.imported_async_funcs, @@ -1147,7 +1146,6 @@ pub(super) fn compile_module_entry( option_object_locals: HashMap::new(), object_literal_locals: HashSet::new(), namespace_imports: &cross_module.namespace_imports, - namespace_reexport_named_imports: &cross_module.namespace_reexport_named_imports, namespace_member_prefixes: &cross_module.namespace_member_prefixes, namespace_member_origin_names: &cross_module.namespace_member_origin_names, imported_async_funcs: &cross_module.imported_async_funcs, diff --git a/crates/perry-codegen/src/codegen/function.rs b/crates/perry-codegen/src/codegen/function.rs index 9389fa3d5f..b2e764f6b3 100644 --- a/crates/perry-codegen/src/codegen/function.rs +++ b/crates/perry-codegen/src/codegen/function.rs @@ -515,7 +515,6 @@ pub(super) fn compile_function( option_object_locals: HashMap::new(), object_literal_locals: HashSet::new(), namespace_imports: &cross_module.namespace_imports, - namespace_reexport_named_imports: &cross_module.namespace_reexport_named_imports, namespace_member_prefixes: &cross_module.namespace_member_prefixes, namespace_member_origin_names: &cross_module.namespace_member_origin_names, imported_async_funcs: &cross_module.imported_async_funcs, diff --git a/crates/perry-codegen/src/codegen/method.rs b/crates/perry-codegen/src/codegen/method.rs index e0167a8dce..a39adbd2ef 100644 --- a/crates/perry-codegen/src/codegen/method.rs +++ b/crates/perry-codegen/src/codegen/method.rs @@ -433,7 +433,6 @@ pub(super) fn compile_method( option_object_locals: HashMap::new(), object_literal_locals: HashSet::new(), namespace_imports: &cross_module.namespace_imports, - namespace_reexport_named_imports: &cross_module.namespace_reexport_named_imports, namespace_member_prefixes: &cross_module.namespace_member_prefixes, namespace_member_origin_names: &cross_module.namespace_member_origin_names, imported_async_funcs: &cross_module.imported_async_funcs, @@ -1342,7 +1341,6 @@ pub(super) fn compile_static_method( option_object_locals: HashMap::new(), object_literal_locals: HashSet::new(), namespace_imports: &cross_module.namespace_imports, - namespace_reexport_named_imports: &cross_module.namespace_reexport_named_imports, namespace_member_prefixes: &cross_module.namespace_member_prefixes, namespace_member_origin_names: &cross_module.namespace_member_origin_names, imported_async_funcs: &cross_module.imported_async_funcs, diff --git a/crates/perry-codegen/src/codegen/mod.rs b/crates/perry-codegen/src/codegen/mod.rs index 84dab0d044..70b1d5f332 100644 --- a/crates/perry-codegen/src/codegen/mod.rs +++ b/crates/perry-codegen/src/codegen/mod.rs @@ -1465,7 +1465,6 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result> let mut cross_module = CrossModuleCtx { namespace_imports: opts.namespace_imports.iter().cloned().collect(), - namespace_reexport_named_imports: opts.namespace_reexport_named_imports.clone(), namespace_member_prefixes: opts.namespace_member_prefixes, namespace_member_origin_names: opts.namespace_member_origin_names, imported_async_funcs: opts.imported_async_funcs, diff --git a/crates/perry-codegen/src/codegen/opts.rs b/crates/perry-codegen/src/codegen/opts.rs index bec9a8333f..25517ed796 100644 --- a/crates/perry-codegen/src/codegen/opts.rs +++ b/crates/perry-codegen/src/codegen/opts.rs @@ -203,21 +203,6 @@ pub struct CompileOptions { /// Codegen uses this to know that `X.foo()` should be dispatched as /// a cross-module call rather than an object method call. pub namespace_imports: Vec, - /// Issue #321: subset of `namespace_imports` populated by the - /// "named import resolves to a `export * as Foo from "./Foo"`" branch - /// in `compile.rs`. When the user wrote `import { Effect } from - /// "effect"` and effect's index.ts has `export * as Effect from - /// "./Effect.js"`, Effect lands in `namespace_imports` (so member - /// dispatch works) AND in this set (so the StaticMethodCall codegen - /// arm knows it's safe to route var-shape members through - /// `js_closure_callN`). Plain `import * as Effect from "./Effect"` - /// (used heavily by effect's INTERNAL modules) populates only - /// `namespace_imports`, NOT this set — the pre-existing direct-call - /// path preserves their long-standing silently-wrong-but-doesn't-throw - /// behavior on var-shape static calls (the right fix there is a - /// broader audit; doing it together with the named-import fix - /// surfaces init-order issues hiding behind the silent-wrong shape). - pub namespace_reexport_named_imports: std::collections::HashSet, /// Imported class definitions from other native modules, keyed by /// the local alias (or original name when no alias). Each entry /// carries the class HIR, the module prefix of its origin, and an @@ -569,8 +554,6 @@ impl ImportedCtor { /// Built once in `compile_module` from `CompileOptions`. pub(crate) struct CrossModuleCtx { pub namespace_imports: std::collections::HashSet, - /// Issue #321: see `CompileOptions::namespace_reexport_named_imports`. - pub namespace_reexport_named_imports: std::collections::HashSet, /// Issue #680: per-namespace member resolution. See doc on /// `CompileOptions::namespace_member_prefixes`. pub namespace_member_prefixes: std::collections::HashMap<(String, String), String>, diff --git a/crates/perry-codegen/src/expr/mod.rs b/crates/perry-codegen/src/expr/mod.rs index e96f549af7..02cdbb075a 100644 --- a/crates/perry-codegen/src/expr/mod.rs +++ b/crates/perry-codegen/src/expr/mod.rs @@ -448,14 +448,6 @@ pub(crate) struct FnCtx<'a> { /// Codegen uses this to know that `X.foo()` should be dispatched as /// a cross-module call rather than an object method call. pub namespace_imports: &'a std::collections::HashSet, - /// Issue #321: subset of `namespace_imports` populated only by the - /// "named import resolves to a `export * as Foo from "./Foo"`" branch - /// in `compile.rs`. The StaticMethodCall arm uses this to decide - /// whether to route var-shape members through `js_closure_callN` - /// (safe for the user-import shape) vs. preserving the pre-fix - /// direct-call (silently-wrong-but-doesn't-throw) path used by - /// `import * as` namespaces in effect's internal modules. - pub namespace_reexport_named_imports: &'a std::collections::HashSet, /// Issue #680: per-namespace member resolution. Keyed by /// `(namespace_local_name, member_name)` → `source_prefix`. Consulted /// by namespace member access lowering to disambiguate when the same diff --git a/crates/perry-codegen/src/expr/static_method.rs b/crates/perry-codegen/src/expr/static_method.rs index 5f19a03083..8fa58b251e 100644 --- a/crates/perry-codegen/src/expr/static_method.rs +++ b/crates/perry-codegen/src/expr/static_method.rs @@ -262,19 +262,9 @@ pub(crate) fn lower(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result { // which `runSync` then read `._tag` off and threw // `Cannot read properties of undefined`. // - // SCOPE: only fire when the class_name was registered as a - // namespace via the *named-import-of-namespace-reexport* - // branch (`import { Effect } from "effect"` where effect's - // index.ts has `export * as Effect from "./Effect.js"`). - // Plain `import * as X from "./X.js"` (used in effect's - // INTERNAL modules) deliberately preserves the pre-fix - // direct-call (silently-wrong-but-doesn't-throw) path — - // switching them all over surfaces init-order bugs that - // were hiding behind the silent shape. Those need a - // separate audit. - if ctx.namespace_reexport_named_imports.contains(class_name) - && ctx.imported_vars.contains(method_name) - { + // Both wildcard imports and namespaces reached through a + // re-export use the same getter ABI. + if ctx.imported_vars.contains(method_name) { let mut lowered: Vec = Vec::with_capacity(args.len()); for a in args { lowered.push(lower_expr(ctx, a)?); diff --git a/crates/perry-codegen/tests/argless_builtin_extra_args.rs b/crates/perry-codegen/tests/argless_builtin_extra_args.rs index 278c077ebd..21e12fe85d 100644 --- a/crates/perry-codegen/tests/argless_builtin_extra_args.rs +++ b/crates/perry-codegen/tests/argless_builtin_extra_args.rs @@ -26,7 +26,6 @@ fn empty_opts() -> CompileOptions { verify_native_regions: false, disable_buffer_fast_path: false, namespace_imports: Vec::new(), - namespace_reexport_named_imports: std::collections::HashSet::new(), imported_classes: Vec::new(), imported_enums: Vec::new(), imported_async_funcs: std::collections::HashSet::new(), diff --git a/crates/perry-codegen/tests/class_keys_gc_root.rs b/crates/perry-codegen/tests/class_keys_gc_root.rs index d300b854d3..ce588b6972 100644 --- a/crates/perry-codegen/tests/class_keys_gc_root.rs +++ b/crates/perry-codegen/tests/class_keys_gc_root.rs @@ -45,7 +45,6 @@ fn entry_opts() -> CompileOptions { verify_native_regions: false, disable_buffer_fast_path: false, namespace_imports: Vec::new(), - namespace_reexport_named_imports: std::collections::HashSet::new(), imported_classes: Vec::new(), imported_enums: Vec::new(), imported_async_funcs: std::collections::HashSet::new(), diff --git a/crates/perry-codegen/tests/constructor_recursion.rs b/crates/perry-codegen/tests/constructor_recursion.rs index 2ec991984e..ed705a0163 100644 --- a/crates/perry-codegen/tests/constructor_recursion.rs +++ b/crates/perry-codegen/tests/constructor_recursion.rs @@ -20,7 +20,6 @@ fn empty_opts() -> CompileOptions { verify_native_regions: false, disable_buffer_fast_path: false, namespace_imports: Vec::new(), - namespace_reexport_named_imports: std::collections::HashSet::new(), imported_classes: Vec::new(), imported_enums: Vec::new(), imported_async_funcs: std::collections::HashSet::new(), diff --git a/crates/perry-codegen/tests/destructure_call_location.rs b/crates/perry-codegen/tests/destructure_call_location.rs index 8dd2ed1ec5..2d82e52b4e 100644 --- a/crates/perry-codegen/tests/destructure_call_location.rs +++ b/crates/perry-codegen/tests/destructure_call_location.rs @@ -37,7 +37,6 @@ fn base_opts() -> CompileOptions { verify_native_regions: false, disable_buffer_fast_path: false, namespace_imports: Vec::new(), - namespace_reexport_named_imports: std::collections::HashSet::new(), imported_classes: Vec::new(), imported_enums: Vec::new(), imported_async_funcs: std::collections::HashSet::new(), diff --git a/crates/perry-codegen/tests/large_object_barriers.rs b/crates/perry-codegen/tests/large_object_barriers.rs index 76a61cec15..01ee01c103 100644 --- a/crates/perry-codegen/tests/large_object_barriers.rs +++ b/crates/perry-codegen/tests/large_object_barriers.rs @@ -20,7 +20,6 @@ fn empty_opts() -> CompileOptions { verify_native_regions: false, disable_buffer_fast_path: false, namespace_imports: Vec::new(), - namespace_reexport_named_imports: std::collections::HashSet::new(), imported_classes: Vec::new(), imported_enums: Vec::new(), imported_async_funcs: std::collections::HashSet::new(), diff --git a/crates/perry-codegen/tests/macos_bundle_chdir_gate.rs b/crates/perry-codegen/tests/macos_bundle_chdir_gate.rs index 8748055364..56c611b5a4 100644 --- a/crates/perry-codegen/tests/macos_bundle_chdir_gate.rs +++ b/crates/perry-codegen/tests/macos_bundle_chdir_gate.rs @@ -26,7 +26,6 @@ fn entry_opts(target: Option<&str>) -> CompileOptions { verify_native_regions: false, disable_buffer_fast_path: false, namespace_imports: Vec::new(), - namespace_reexport_named_imports: std::collections::HashSet::new(), imported_classes: Vec::new(), imported_enums: Vec::new(), imported_async_funcs: std::collections::HashSet::new(), diff --git a/crates/perry-codegen/tests/native_proof_buffer_views.rs b/crates/perry-codegen/tests/native_proof_buffer_views.rs index ae3bdd2bca..ae61ca9b4e 100644 --- a/crates/perry-codegen/tests/native_proof_buffer_views.rs +++ b/crates/perry-codegen/tests/native_proof_buffer_views.rs @@ -25,7 +25,6 @@ fn empty_opts() -> CompileOptions { verify_native_regions: false, disable_buffer_fast_path: false, namespace_imports: Vec::new(), - namespace_reexport_named_imports: std::collections::HashSet::new(), imported_classes: Vec::new(), imported_enums: Vec::new(), imported_async_funcs: std::collections::HashSet::new(), diff --git a/crates/perry-codegen/tests/native_proof_regressions.rs b/crates/perry-codegen/tests/native_proof_regressions.rs index 409996bdaf..436f3757bf 100644 --- a/crates/perry-codegen/tests/native_proof_regressions.rs +++ b/crates/perry-codegen/tests/native_proof_regressions.rs @@ -26,7 +26,6 @@ fn empty_opts() -> CompileOptions { verify_native_regions: false, disable_buffer_fast_path: false, namespace_imports: Vec::new(), - namespace_reexport_named_imports: std::collections::HashSet::new(), imported_classes: Vec::new(), imported_enums: Vec::new(), imported_async_funcs: std::collections::HashSet::new(), diff --git a/crates/perry-codegen/tests/shadow_slot_hygiene.rs b/crates/perry-codegen/tests/shadow_slot_hygiene.rs index 059f9e49b0..69ae8ade80 100644 --- a/crates/perry-codegen/tests/shadow_slot_hygiene.rs +++ b/crates/perry-codegen/tests/shadow_slot_hygiene.rs @@ -28,7 +28,6 @@ fn empty_opts() -> CompileOptions { imported_func_has_rest: std::collections::HashSet::new(), imported_func_synthetic_arguments: std::collections::HashSet::new(), imported_func_return_types: std::collections::HashMap::new(), - namespace_reexport_named_imports: std::collections::HashSet::new(), imported_vars: std::collections::HashSet::new(), output_type: "executable".to_string(), needs_stdlib: false, diff --git a/crates/perry-codegen/tests/static_symbol_hygiene.rs b/crates/perry-codegen/tests/static_symbol_hygiene.rs index 495306dd4a..44b9c3dc9c 100644 --- a/crates/perry-codegen/tests/static_symbol_hygiene.rs +++ b/crates/perry-codegen/tests/static_symbol_hygiene.rs @@ -20,7 +20,6 @@ fn empty_opts() -> CompileOptions { verify_native_regions: false, disable_buffer_fast_path: false, namespace_imports: Vec::new(), - namespace_reexport_named_imports: std::collections::HashSet::new(), imported_classes: Vec::new(), imported_enums: Vec::new(), imported_async_funcs: std::collections::HashSet::new(), diff --git a/crates/perry-codegen/tests/typed_feedback.rs b/crates/perry-codegen/tests/typed_feedback.rs index e6f8474e4c..723a45596a 100644 --- a/crates/perry-codegen/tests/typed_feedback.rs +++ b/crates/perry-codegen/tests/typed_feedback.rs @@ -51,7 +51,6 @@ fn empty_opts() -> CompileOptions { verify_native_regions: false, disable_buffer_fast_path: false, namespace_imports: Vec::new(), - namespace_reexport_named_imports: std::collections::HashSet::new(), imported_classes: Vec::new(), imported_enums: Vec::new(), imported_async_funcs: std::collections::HashSet::new(), diff --git a/crates/perry-codegen/tests/typed_shape_descriptor.rs b/crates/perry-codegen/tests/typed_shape_descriptor.rs index 3aa85b3d1c..d3cd2dfdc0 100644 --- a/crates/perry-codegen/tests/typed_shape_descriptor.rs +++ b/crates/perry-codegen/tests/typed_shape_descriptor.rs @@ -20,7 +20,6 @@ fn empty_opts() -> CompileOptions { verify_native_regions: false, disable_buffer_fast_path: false, namespace_imports: Vec::new(), - namespace_reexport_named_imports: std::collections::HashSet::new(), imported_classes: Vec::new(), imported_enums: Vec::new(), imported_async_funcs: std::collections::HashSet::new(), diff --git a/crates/perry-codegen/tests/typed_shape_descriptors.rs b/crates/perry-codegen/tests/typed_shape_descriptors.rs index f9290a0cea..57d4c2a7ed 100644 --- a/crates/perry-codegen/tests/typed_shape_descriptors.rs +++ b/crates/perry-codegen/tests/typed_shape_descriptors.rs @@ -50,7 +50,6 @@ fn empty_opts() -> CompileOptions { verify_native_regions: false, disable_buffer_fast_path: false, namespace_imports: Vec::new(), - namespace_reexport_named_imports: std::collections::HashSet::new(), imported_classes: Vec::new(), imported_enums: Vec::new(), imported_async_funcs: std::collections::HashSet::new(), diff --git a/crates/perry/src/commands/compile/object_cache.rs b/crates/perry/src/commands/compile/object_cache.rs index 0a3e3f6689..9714103214 100644 --- a/crates/perry/src/commands/compile/object_cache.rs +++ b/crates/perry/src/commands/compile/object_cache.rs @@ -433,20 +433,6 @@ fn compute_object_cache_key_with_env( ); } - // Issue #321: namespace reexport named imports — separate subset that - // gates the codegen's StaticMethodCall var-shape routing. Cache must - // discriminate between two modules whose `namespace_imports` are - // identical but whose `namespace_reexport_named_imports` differ, else - // the wrong code-path winds up in the object file. - { - let mut v: Vec<&String> = opts.namespace_reexport_named_imports.iter().collect(); - v.sort(); - h.field( - "ns_reexport_named_imports", - &v.iter().map(|s| s.as_str()).collect::>().join(","), - ); - } - // Import function prefixes (HashMap — MUST sort). { let mut v: Vec<(&String, &String)> = opts.import_function_prefixes.iter().collect(); @@ -1064,7 +1050,6 @@ mod object_cache_tests { verify_native_regions: false, disable_buffer_fast_path: false, namespace_imports: Vec::new(), - namespace_reexport_named_imports: std::collections::HashSet::new(), imported_classes: Vec::new(), imported_enums: Vec::new(), imported_async_funcs: std::collections::HashSet::new(), diff --git a/crates/perry/src/commands/compile/run_pipeline.rs b/crates/perry/src/commands/compile/run_pipeline.rs index b43c777e2c..8e8e843f45 100644 --- a/crates/perry/src/commands/compile/run_pipeline.rs +++ b/crates/perry/src/commands/compile/run_pipeline.rs @@ -2121,14 +2121,6 @@ pub fn run_with_parse_cache( std::collections::HashMap<(String, String), String> = std::collections::HashMap::new(); let mut namespace_imports: Vec = Vec::new(); - // Issue #321: subset of `namespace_imports` populated only by the - // named-import-of-namespace-reexport branch below (`import { Effect - // } from "effect"` where effect's index.ts has `export * as Effect - // from "./Effect.js"`). The codegen's StaticMethodCall arm consults - // this to decide whether it can route var-shape members through - // `js_closure_callN`; see the field doc in codegen.rs. - let mut namespace_reexport_named_imports: std::collections::HashSet = - std::collections::HashSet::new(); let mut imported_classes: Vec = Vec::new(); let mut imported_enums: Vec<(String, Vec<(String, perry_hir::EnumValue)>)> = Vec::new(); let mut imported_async_set: std::collections::HashSet = @@ -2600,13 +2592,6 @@ pub fn run_with_parse_cache( break; }; namespace_imports.push(local_name.clone()); - // Issue #321: tag this local as a "named-import- - // of-namespace-reexport" so codegen's - // StaticMethodCall arm knows to route var-shape - // members through `js_closure_callN`. See the - // expr.rs StaticMethodCall comment for why this - // is scoped narrowly. - namespace_reexport_named_imports.insert(local_name.clone()); for (export_name, origin_path) in target_exports { let origin_prefix = compute_module_prefix(origin_path, &ctx.project_root); @@ -2686,7 +2671,9 @@ pub fn run_with_parse_cache( // else the export name itself. namespace_member_origin_names.insert( (local_name.clone(), export_name.clone()), - resolved_origin_name.unwrap_or_else(|| export_name.clone()), + resolved_origin_name + .clone() + .unwrap_or_else(|| export_name.clone()), ); let key = (origin_path.clone(), export_name.clone()); @@ -2720,7 +2707,15 @@ pub fn run_with_parse_cache( // `Cannot read properties of undefined` // on `program._tag`. Mirrors the // `Namespace { local }` branch above. - if exported_var_names.contains(&key) { + let origin_key_under_origin_name = resolved_origin_name + .as_ref() + .map(|name| (origin_path.clone(), name.clone())); + if exported_var_names.contains(&key) + || origin_key_under_origin_name + .as_ref() + .map(|key| exported_var_names.contains(key)) + .unwrap_or(false) + { imported_vars.insert(export_name.clone()); } if let Some(class) = exported_classes.get(&key) { @@ -4034,7 +4029,6 @@ pub fn run_with_parse_cache( verify_native_regions, disable_buffer_fast_path, namespace_imports, - namespace_reexport_named_imports, imported_classes, imported_enums, imported_async_funcs: imported_async_set, diff --git a/crates/perry/tests/namespace_variable_export_abi.rs b/crates/perry/tests/namespace_variable_export_abi.rs new file mode 100644 index 0000000000..3d2eb854b3 --- /dev/null +++ b/crates/perry/tests/namespace_variable_export_abi.rs @@ -0,0 +1,132 @@ +//! Characterization coverage for variable-shaped exports reached through a +//! namespace binding. The export ABI is a zero-argument getter returning the +//! closure, not a function symbol with the closure's user-facing arity. + +use std::path::PathBuf; +use std::process::Command; + +fn perry_bin() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_perry")) +} + +#[test] +fn namespace_variable_exports_use_their_getter_then_call_the_closure() { + let dir = tempfile::tempdir().expect("tempdir"); + let root = dir.path(); + + std::fs::write( + root.join("package.json"), + r#"{ + "name": "namespace-variable-export-abi", + "type": "module", + "perry": { + "compilePackages": ["mini-cjs"], + "allow": { "compilePackages": ["mini-cjs"] } + } +}"#, + ) + .expect("write package manifest"); + + std::fs::write( + root.join("vars.ts"), + r#" +export const make = (value: string) => "make:" + value; +export const alsoMake = (value: string) => "also:" + value; +export { alsoMake as aliasedMake }; +export function declared(value: string) { return "declared:" + value; } +export default (value: string) => "default:" + value; +"#, + ) + .expect("write vars"); + std::fs::write( + root.join("barrel.ts"), + r#"export * as Reexported from "./vars.js";"#, + ) + .expect("write barrel"); + // The cycle must retain module-init ordering: evaluating the imported + // closure is deferred until after both modules finish initialization. + std::fs::write( + root.join("cycle-a.ts"), + r#" +import * as CycleB from "./cycle-b.js"; +export const make = (value: string) => CycleB.prefix(value) + ":a"; +export const ready = () => "ready"; +"#, + ) + .expect("write cycle-a"); + std::fs::write( + root.join("cycle-b.ts"), + r#" +import * as CycleA from "./cycle-a.js"; +export const prefix = (value: string) => "b:" + value; +export const readReady = () => CycleA.ready(); +"#, + ) + .expect("write cycle-b"); + let cjs = root.join("node_modules/mini-cjs"); + std::fs::create_dir_all(&cjs).expect("create cjs package"); + std::fs::write( + cjs.join("package.json"), + r#"{ "name": "mini-cjs", "version": "1.0.0", "main": "index.js" }"#, + ) + .expect("write cjs manifest"); + std::fs::write( + cjs.join("index.js"), + "var make = require('./make'); module.exports = { make: make };\n", + ) + .expect("write cjs barrel"); + std::fs::write( + cjs.join("make.js"), + "module.exports = function (value) { return 'cjs:' + value; };\n", + ) + .expect("write cjs function"); + std::fs::write( + root.join("main.ts"), + r#" +import * as API from "./vars.js"; +import { Reexported } from "./barrel.js"; +import * as CycleA from "./cycle-a.js"; +import * as CycleB from "./cycle-b.js"; +import * as CJS from "mini-cjs"; + +console.log(API.make("one")); +console.log(API.aliasedMake("two")); +console.log(API.declared("three")); +console.log(API.default("four")); +console.log(Reexported.make("five")); +console.log(CycleA.make("six")); +console.log(CycleB.readReady()); +console.log(CJS.make("seven")); +"#, + ) + .expect("write entry"); + + let output = root.join("main_bin"); + let compile = Command::new(perry_bin()) + .current_dir(root) + .arg("compile") + .arg(root.join("main.ts")) + .arg("-o") + .arg(&output) + .output() + .expect("run perry compile"); + assert!( + compile.status.success(), + "perry compile failed\nstdout:\n{}\nstderr:\n{}", + String::from_utf8_lossy(&compile.stdout), + String::from_utf8_lossy(&compile.stderr) + ); + + let run = Command::new(&output).output().expect("run compiled binary"); + assert!( + run.status.success(), + "compiled binary failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", + run.status, + String::from_utf8_lossy(&run.stdout), + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!( + String::from_utf8_lossy(&run.stdout), + "make:one\nalso:two\ndeclared:three\ndefault:four\nmake:five\nb:six:a\nready\ncjs:seven\n" + ); +} diff --git a/target/deepwiki/arraybuffer-dataview-semantics.md b/target/deepwiki/arraybuffer-dataview-semantics.md deleted file mode 100644 index 085252f877..0000000000 --- a/target/deepwiki/arraybuffer-dataview-semantics.md +++ /dev/null @@ -1,1035 +0,0 @@ -# DeepWiki Response - -Repository: `boa-dev/boa` -Question: For implementing ECMAScript ordinary ArrayBuffer and DataView semantics, summarize the ArrayBuffer constructor/prototype/static surface, ArrayBuffer.isView behavior, DataView internal slots and subclass recognition, ArrayBuffer.prototype.byteLength getter descriptors and brand checks, ArrayBuffer.prototype.slice bounds/defaults, and where to avoid scope creep into SharedArrayBuffer, resizable/growable buffers, transfer semantics, or TypedArray transform methods. Focus on implementation-relevant details and tests. -Search: https://deepwiki.com/search/for-implementing-ecmascript-or_a7d60b37-9322-46df-a259-cc0dbd9f8cd4 - -**Question**: For implementing ECMAScript ordinary ArrayBuffer and DataView semantics, summarize the ArrayBuffer constructor/prototype/static surface, ArrayBuffer.isView behavior, DataView internal slots and subclass recognition, ArrayBuffer.prototype.byteLength getter descriptors and brand checks, ArrayBuffer.prototype.slice bounds/defaults, and where to avoid scope creep into SharedArrayBuffer, resizable/growable buffers, transfer semantics, or TypedArray transform methods. Focus on implementation-relevant details and tests. - -This response summarizes the implementation of ECMAScript `ArrayBuffer` and `DataView` semantics in the Boa engine, focusing on the constructor, prototype, static methods, internal slots, and brand checks, while also highlighting areas to avoid scope creep. - -## ArrayBuffer Constructor, Prototype, and Static Surface - -The `ArrayBuffer` object in Boa is represented by the `ArrayBuffer` struct [1](#ref-q1-1) . Its constructor, `ArrayBuffer::constructor`, handles the creation of new `ArrayBuffer` instances [2](#ref-q1-2) . It takes a `byteLength` and an optional `maxByteLength` to allocate the buffer [3](#ref-q1-3) . The `ArrayBuffer::allocate` function performs the actual memory allocation and initialization of internal slots like `[[ArrayBufferData]]`, `[[ArrayBufferByteLength]]`, and `[[ArrayBufferMaxByteLength]]` [4](#ref-q1-4) . - -The `ArrayBuffer` prototype defines several accessors and methods: -* `ArrayBuffer.prototype.byteLength`: A getter that returns the length of the `ArrayBuffer` in bytes [5](#ref-q1-5) . -* `ArrayBuffer.prototype.resizable`: A getter that indicates if the `ArrayBuffer` can be resized [6](#ref-q1-6) . -* `ArrayBuffer.prototype.maxByteLength`: A getter that returns the maximum byte length of a resizable `ArrayBuffer` [7](#ref-q1-7) . -* `ArrayBuffer.prototype.slice`: Creates a new `ArrayBuffer` that contains a copy of the bytes from the original `ArrayBuffer` within the specified range [8](#ref-q1-8) . -* `ArrayBuffer[@@species]`: A static accessor that returns the `this` value, allowing subclasses to define their own constructor for `slice` operations [9](#ref-q1-9) . - -## ArrayBuffer.isView Behavior - -The static method `ArrayBuffer.isView` determines if an argument is an `ArrayBuffer` view (either a `TypedArray` or a `DataView`) [10](#ref-q1-10) . It checks if the argument is an object and if it has a `[[ViewedArrayBuffer]]` internal slot, which is present in both `TypedArray` and `DataView` objects [11](#ref-q1-11) . - -## DataView Internal Slots and Subclass Recognition - -The `DataView` object is represented by the `DataView` struct [12](#ref-q1-12) . It has the following internal slots [13](#ref-q1-13) : -* `[[ViewedArrayBuffer]]`: A reference to the underlying `ArrayBuffer` or `SharedArrayBuffer` [14](#ref-q1-14) . -* `[[ByteLength]]`: The length of the `DataView` in bytes [15](#ref-q1-15) . -* `[[ByteOffset]]`: The offset in bytes from the start of the `[[ViewedArrayBuffer]]` [16](#ref-q1-16) . - -The `DataView` constructor, `DataView::constructor`, validates the `buffer`, `byteOffset`, and `byteLength` arguments and initializes these internal slots [17](#ref-q1-17) . Subclass recognition is handled by `OrdinaryCreateFromConstructor`, which uses the `new_target` to determine the prototype chain [18](#ref-q1-18) . - -## ArrayBuffer.prototype.byteLength Getter Descriptors and Brand Checks - -The `ArrayBuffer.prototype.byteLength` getter performs a brand check to ensure that `this` value is an `ArrayBuffer` instance [5](#ref-q1-5) . If the `this` value is not an `ArrayBuffer` object, it throws a `TypeError` [19](#ref-q1-19) . It then checks if the buffer is detached; if so, it returns `+0𝔽` [20](#ref-q1-20) . The property is configured with `Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE` [21](#ref-q1-21) . - -## ArrayBuffer.prototype.slice Bounds and Defaults - -The `ArrayBuffer.prototype.slice` method extracts a portion of the `ArrayBuffer` [22](#ref-q1-22) . It performs several checks: -* **Brand Check**: Ensures `this` is an `ArrayBuffer` [23](#ref-q1-23) . -* **Detached Buffer Check**: Throws a `TypeError` if the buffer is detached [24](#ref-q1-24) . -* **Bounds Calculation**: `start` and `end` arguments are converted to `relativeStart` and `relativeEnd` using `ToIndex` and clamped to the buffer's length [25](#ref-q1-25) . If `end` is `undefined`, it defaults to the buffer's length [26](#ref-q1-26) . -* **Species Constructor**: Uses `SpeciesConstructor` to create a new `ArrayBuffer` instance, allowing subclasses to control the type of the returned buffer [27](#ref-q1-27) . -* **Copying Data**: The relevant bytes are copied from the original buffer to the new buffer [28](#ref-q1-28) . - -## Avoiding Scope Creep - -The implementation explicitly avoids scope creep into `SharedArrayBuffer`, resizable/growable buffers, transfer semantics, or `TypedArray` transform methods by: -* **SharedArrayBuffer**: `ArrayBuffer` methods like `byteLength` and `slice` explicitly check if the buffer is a `SharedArrayBuffer` and throw a `TypeError` if it is [29](#ref-q1-29) [30](#ref-q1-30) . The `SharedArrayBuffer` is a distinct type [31](#ref-q1-31) . -* **Resizable/Growable Buffers**: While `ArrayBuffer` supports `max_byte_len` for resizable buffers [32](#ref-q1-32) , the `resize` method explicitly checks if the buffer is fixed-length before allowing resizing [33](#ref-q1-33) . -* **Transfer Semantics**: Transfer methods (`transfer`, `transferToFixedLength`) are guarded by an experimental feature flag [34](#ref-q1-34) , indicating they are not part of the ordinary `ArrayBuffer` semantics by default. -* **TypedArray Transform Methods**: The prompt does not mention `TypedArray` transform methods, and the provided context for `ArrayBuffer` and `DataView` does not show any direct integration or scope creep into these methods. `TypedArray` objects are views over `ArrayBuffer`s, but their specific transform methods are handled within the `TypedArray` implementation [35](#ref-q1-35) . - -## Implementation-Relevant Details and Tests - -The `ArrayBuffer` and `DataView` implementations are built upon the `JsData` trait [36](#ref-q1-36) [37](#ref-q1-37) , and their methods are registered using `BuiltInBuilder` [38](#ref-q1-38) [39](#ref-q1-39) . - -Testing for `ArrayBuffer` includes checks for `detach` functionality and conversion to `Vec` [40](#ref-q1-40) . The `js262` test harness also provides a `$262.detachArrayBuffer()` function to test the detachment behavior [41](#ref-q1-41) . - -## Notes - -The `AlignedVec` type is used for the underlying data storage of `ArrayBuffer` [42](#ref-q1-42) , which ensures proper memory alignment for efficient access. The `DataView` implementation includes methods for getting and setting various numeric types (`getBigInt64`, `setFloat32`, etc.) [43](#ref-q1-43) , which involve endianness handling [44](#ref-q1-44) [45](#ref-q1-45) . - -Wiki pages you might want to explore: -- [Standard Builtins (boa-dev/boa)](/wiki/boa-dev/boa#3.1) -- [Glossary (boa-dev/boa)](/wiki/boa-dev/boa#7) - -View this search on DeepWiki: https://deepwiki.com/search/for-implementing-ecmascript-or_a7d60b37-9322-46df-a259-cc0dbd9f8cd4 - -## References - - -### [1] `core/engine/src/builtins/array_buffer/mod.rs:200-202` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L200-L202) - -```rust -/// The internal representation of an `ArrayBuffer` object. -#[derive(Debug, Clone, Trace, Finalize, JsData)] -pub struct ArrayBuffer { -``` - - -### [2] `core/engine/src/builtins/array_buffer/mod.rs:429-451` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L429-L451) - -```rust - fn constructor( - new_target: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { - // 1. If NewTarget is undefined, throw a TypeError exception. - if new_target.is_undefined() { - return Err(JsNativeError::typ() - .with_message("ArrayBuffer.constructor called with undefined new target") - .into()); - } - - // 2. Let byteLength be ? ToIndex(length). - let byte_len = args.get_or_undefined(0).to_index(context)?; - - // 3. Let requestedMaxByteLength be ? GetArrayBufferMaxByteLengthOption(options). - let max_byte_len = get_max_byte_len(args.get_or_undefined(1), context)?; - - // 4. Return ? AllocateArrayBuffer(NewTarget, byteLength, requestedMaxByteLength). - Ok(Self::allocate(new_target, byte_len, max_byte_len, context)? - .upcast() - .into()) - } -``` - - -### [3] `core/engine/src/builtins/array_buffer/mod.rs:442-445` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L442-L445) - -```rust - let byte_len = args.get_or_undefined(0).to_index(context)?; - - // 3. Let requestedMaxByteLength be ? GetArrayBufferMaxByteLengthOption(options). - let max_byte_len = get_max_byte_len(args.get_or_undefined(1), context)?; -``` - - -### [4] `core/engine/src/builtins/array_buffer/mod.rs:868-917` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L868-L917) - -```rust - pub(crate) fn allocate( - constructor: &JsValue, - byte_len: u64, - max_byte_len: Option, - context: &mut Context, - ) -> JsResult> { - // 1. Let slots be « [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferDetachKey]] ». - // 2. If maxByteLength is present and maxByteLength is not empty, let allocatingResizableBuffer be true; otherwise let allocatingResizableBuffer be false. - // 3. If allocatingResizableBuffer is true, then - // a. If byteLength > maxByteLength, throw a RangeError exception. - // b. Append [[ArrayBufferMaxByteLength]] to slots. - if let Some(max_byte_len) = max_byte_len - && byte_len > max_byte_len - { - return Err(JsNativeError::range() - .with_message("`length` cannot be bigger than `maxByteLength`") - .into()); - } - - // 4. Let obj be ? OrdinaryCreateFromConstructor(constructor, "%ArrayBuffer.prototype%", slots). - let prototype = get_prototype_from_constructor( - constructor, - StandardConstructors::array_buffer, - context, - )?; - - // 5. Let block be ? CreateByteDataBlock(byteLength). - // Preemptively allocate for `max_byte_len` if possible. - // a. If it is not possible to create a Data Block block consisting of maxByteLength bytes, throw a RangeError exception. - // b. NOTE: Resizable ArrayBuffers are designed to be implementable with in-place growth. Implementations may - // throw if, for example, virtual memory cannot be reserved up front. - let block = create_byte_data_block(byte_len, max_byte_len, context)?; - - let obj = JsObject::new( - context.root_shape(), - prototype, - Self { - // 6. Set obj.[[ArrayBufferData]] to block. - // 7. Set obj.[[ArrayBufferByteLength]] to byteLength. - data: Some(block), - // 8. If allocatingResizableBuffer is true, then - // c. Set obj.[[ArrayBufferMaxByteLength]] to maxByteLength. - max_byte_len, - detach_key: JsValue::undefined(), - }, - ); - - // 9. Return obj. - Ok(obj) - } -``` - - -### [5] `core/engine/src/builtins/array_buffer/mod.rs:491-512` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L491-L512) - -```rust - pub(crate) fn get_byte_length( - this: &JsValue, - _args: &[JsValue], - _: &mut Context, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). - // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. - let object = this.as_object(); - let buf = object - .as_ref() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("get ArrayBuffer.prototype.byteLength called with invalid `this`") - })?; - - // 4. If IsDetachedBuffer(O) is true, return +0𝔽. - // 5. Let length be O.[[ArrayBufferByteLength]]. - // 6. Return 𝔽(length). - Ok(buf.len().into()) - } -``` - - -### [6] `core/engine/src/builtins/array_buffer/mod.rs:548-570` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L548-L570) - -```rust - /// [`get ArrayBuffer.prototype.resizable`][spec]. - /// - /// [spec]: https://tc39.es/ecma262/#sec-get-arraybuffer.prototype.resizable - pub(crate) fn get_resizable( - this: &JsValue, - _args: &[JsValue], - _context: &mut Context, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). - // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. - let object = this.as_object(); - let buf = object - .as_ref() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("get ArrayBuffer.prototype.resizable called with invalid `this`") - })?; - - // 4. If IsFixedLengthArrayBuffer(O) is false, return true; otherwise return false. - Ok(JsValue::from(!buf.is_fixed_len())) - } -``` - - -### [7] `core/engine/src/builtins/array_buffer/mod.rs:517-545` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L517-L545) - -```rust - pub(crate) fn get_max_byte_length( - this: &JsValue, - _args: &[JsValue], - _context: &mut Context, - ) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). - // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. - let object = this.as_object(); - let buf = object - .as_ref() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ().with_message( - "get ArrayBuffer.prototype.maxByteLength called with invalid `this`", - ) - })?; - - // 4. If IsDetachedBuffer(O) is true, return +0𝔽. - let Some(data) = buf.bytes() else { - return Ok(JsValue::from(0)); - }; - - // 5. If IsFixedLengthArrayBuffer(O) is true, then - // a. Let length be O.[[ArrayBufferByteLength]]. - // 6. Else, - // a. Let length be O.[[ArrayBufferMaxByteLength]]. - // 7. Return 𝔽(length). - Ok(buf.max_byte_len.unwrap_or(data.len() as u64).into()) -``` - - -### [8] `core/engine/src/builtins/array_buffer/mod.rs:648-746` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L648-L746) - -```rust - fn slice(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - // 1. Let O be the this value. - // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). - // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. - let buf = this - .as_object() - .and_then(|o| o.clone().downcast::().ok()) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("ArrayBuffer.slice called with invalid `this` value") - })?; - - let len = { - let buf = buf.borrow(); - // 4. If IsDetachedBuffer(O) is true, throw a TypeError exception. - if buf.data().is_detached() { - return Err(JsNativeError::typ() - .with_message("ArrayBuffer.slice called with detached buffer") - .into()); - } - // 5. Let len be O.[[ArrayBufferByteLength]]. - buf.data().len() as u64 - }; - - // 6. Let relativeStart be ? ToIntegerOrInfinity(start). - // 7. If relativeStart = -∞, let first be 0. - // 8. Else if relativeStart < 0, let first be max(len + relativeStart, 0). - // 9. Else, let first be min(relativeStart, len). - let first = Array::get_relative_start(context, args.get_or_undefined(0), len)?; - - // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). - // 11. If relativeEnd = -∞, let final be 0. - // 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). - // 13. Else, let final be min(relativeEnd, len). - let final_ = Array::get_relative_end(context, args.get_or_undefined(1), len)?; - - // 14. Let newLen be max(final - first, 0). - let new_len = final_.saturating_sub(first); - - // 15. Let ctor be ? SpeciesConstructor(O, %ArrayBuffer%). - let ctor = buf - .clone() - .upcast() - .species_constructor(StandardConstructors::array_buffer, context)?; - - // 16. Let new be ? Construct(ctor, « 𝔽(newLen) »). - let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?; - - // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). - // 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception. - let Ok(new) = new.downcast::() else { - return Err(JsNativeError::typ() - .with_message("ArrayBuffer constructor returned invalid object") - .into()); - }; - - // 20. If SameValue(new, O) is true, throw a TypeError exception. - if JsObject::equals(&buf, &new) { - return Err(JsNativeError::typ() - .with_message("new ArrayBuffer is the same as this ArrayBuffer") - .into()); - } - - { - // 19. If IsDetachedBuffer(new) is true, throw a TypeError exception. - // 25. Let toBuf be new.[[ArrayBufferData]]. - let mut new = new.borrow_mut(); - let Some(to_buf) = new.data_mut().bytes_mut() else { - return Err(JsNativeError::typ() - .with_message("ArrayBuffer constructor returned detached ArrayBuffer") - .into()); - }; - - // 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception. - if (to_buf.len() as u64) < new_len { - return Err(JsNativeError::typ() - .with_message("new ArrayBuffer length too small") - .into()); - } - - // 22. NOTE: Side-effects of the above steps may have detached O. - // 23. If IsDetachedBuffer(O) is true, throw a TypeError exception. - // 24. Let fromBuf be O.[[ArrayBufferData]]. - let buf = buf.borrow(); - let Some(from_buf) = buf.data().bytes() else { - return Err(JsNativeError::typ() - .with_message("ArrayBuffer detached while ArrayBuffer.slice was running") - .into()); - }; - - // 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). - let first = first as usize; - let new_len = new_len as usize; - to_buf[..new_len].copy_from_slice(&from_buf[first..first + new_len]); - } - - // 27. Return new. - Ok(new.upcast().into()) - } -``` - - -### [9] `core/engine/src/builtins/array_buffer/mod.rs:479-483` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L479-L483) - -```rust - #[allow(clippy::unnecessary_wraps)] - fn get_species(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - // 1. Return the this value. - Ok(this.clone()) - } -``` - - -### [10] `core/engine/src/builtins/array_buffer/mod.rs:455-471` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L455-L471) - -```rust - /// `ArrayBuffer.isView ( arg )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.isview - #[allow(clippy::unnecessary_wraps)] - fn is_view(_: &JsValue, args: &[JsValue], _context: &mut Context) -> JsResult { - // 1. If Type(arg) is not Object, return false. - // 2. If arg has a [[ViewedArrayBuffer]] internal slot, return true. - // 3. Return false. - Ok(args - .get_or_undefined(0) - .as_object() - .is_some_and(|obj| obj.is::() || obj.is::()) - .into()) - } -``` - - -### [11] `core/engine/src/builtins/array_buffer/mod.rs:463-470` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L463-L470) - -```rust - // 1. If Type(arg) is not Object, return false. - // 2. If arg has a [[ViewedArrayBuffer]] internal slot, return true. - // 3. Return false. - Ok(args - .get_or_undefined(0) - .as_object() - .is_some_and(|obj| obj.is::() || obj.is::()) - .into()) -``` - - -### [12] `core/engine/src/builtins/dataview/mod.rs:37-39` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/dataview/mod.rs#L37-L39) - -```rust -/// The internal representation of a `DataView` object. -#[derive(Debug, Clone, Trace, Finalize, JsData)] -pub struct DataView { -``` - - -### [13] `core/engine/src/builtins/dataview/mod.rs:40-42` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/dataview/mod.rs#L40-L42) - -```rust - pub(crate) viewed_array_buffer: BufferObject, - pub(crate) byte_length: Option, - pub(crate) byte_offset: u64, -``` - - -### [14] `core/engine/src/builtins/dataview/mod.rs:40` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/dataview/mod.rs#L40) - -```rust - pub(crate) viewed_array_buffer: BufferObject, -``` - - -### [15] `core/engine/src/builtins/dataview/mod.rs:41` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/dataview/mod.rs#L41) - -```rust - pub(crate) byte_length: Option, -``` - - -### [16] `core/engine/src/builtins/dataview/mod.rs:42` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/dataview/mod.rs#L42) - -```rust - pub(crate) byte_offset: u64, -``` - - -### [17] `core/engine/src/builtins/dataview/mod.rs:193-312` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/dataview/mod.rs#L193-L312) - -```rust - fn constructor( - new_target: &JsValue, - args: &[JsValue], - context: &mut Context, - ) -> JsResult { - // 1. If NewTarget is undefined, throw a TypeError exception. - if new_target.is_undefined() { - return Err(JsNativeError::typ() - .with_message("cannot call `DataView` constructor without `new`") - .into()); - } - let byte_len = args.get_or_undefined(2); - - // 2. Perform ? RequireInternalSlot(buffer, [[ArrayBufferData]]). - let buffer = args - .get_or_undefined(0) - .as_object() - .and_then(|o| o.clone().into_buffer_object().ok()) - .ok_or_else(|| JsNativeError::typ().with_message("buffer must be an ArrayBuffer"))?; - - // 3. Let offset be ? ToIndex(byteOffset). - let offset = args.get_or_undefined(1).to_index(context)?; - - let (buf_byte_len, is_fixed_len) = { - let buffer = buffer.as_buffer(); - - // 4. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - let Some(slice) = buffer.bytes(Ordering::SeqCst) else { - return Err(JsNativeError::typ() - .with_message("ArrayBuffer is detached") - .into()); - }; - - // 5. Let bufferByteLength be ArrayBufferByteLength(buffer, seq-cst). - let buf_len = slice.len() as u64; - - // 6. If offset > bufferByteLength, throw a RangeError exception. - if offset > buf_len { - return Err(JsNativeError::range() - .with_message("Start offset is outside the bounds of the buffer") - .into()); - } - - // 7. Let bufferIsFixedLength be IsFixedLengthArrayBuffer(buffer). - - (buf_len, buffer.is_fixed_len()) - }; - - // 8. If byteLength is undefined, then - let view_byte_len = if byte_len.is_undefined() { - // a. If bufferIsFixedLength is true, then - // i. Let viewByteLength be bufferByteLength - offset. - // b. Else, - // i. Let viewByteLength be auto. - is_fixed_len.then_some(buf_byte_len - offset) - } else { - // 9. Else, - // a. Let viewByteLength be ? ToIndex(byteLength). - let byte_len = byte_len.to_index(context)?; - - // b. If offset + viewByteLength > bufferByteLength, throw a RangeError exception. - if offset + byte_len > buf_byte_len { - return Err(JsNativeError::range() - .with_message("Invalid data view length") - .into()); - } - Some(byte_len) - }; - - // 10. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", - // « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »). - let prototype = - get_prototype_from_constructor(new_target, StandardConstructors::data_view, context)?; - - // 11. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. - // 12. Set bufferByteLength to ArrayBufferByteLength(buffer, seq-cst). - let Some(buf_byte_len) = buffer - .as_buffer() - .bytes(Ordering::SeqCst) - .map(|s| s.len() as u64) - else { - return Err(JsNativeError::typ() - .with_message("ArrayBuffer can't be detached") - .into()); - }; - - // 13. If offset > bufferByteLength, throw a RangeError exception. - if offset > buf_byte_len { - return Err(JsNativeError::range() - .with_message("DataView offset outside of buffer array bounds") - .into()); - } - - // 14. If byteLength is not undefined, then - // a. If offset + viewByteLength > bufferByteLength, throw a RangeError exception. - if !byte_len.is_undefined() - && let Some(view_byte_len) = view_byte_len - && offset + view_byte_len > buf_byte_len - { - return Err(JsNativeError::range() - .with_message("DataView offset outside of buffer array bounds") - .into()); - } - - let obj = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - Self { - // 15. Set O.[[ViewedArrayBuffer]] to buffer. - viewed_array_buffer: buffer, - // 16. Set O.[[ByteLength]] to viewByteLength. - byte_length: view_byte_len, - // 17. Set O.[[ByteOffset]] to offset. - byte_offset: offset, - }, - ); - - // 18. Return O. - Ok(obj.into()) - } -``` - - -### [18] `core/engine/src/builtins/dataview/mod.rs:262-266` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/dataview/mod.rs#L262-L266) - -```rust - // 10. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", - // « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »). - let prototype = - get_prototype_from_constructor(new_target, StandardConstructors::data_view, context)?; -``` - - -### [19] `core/engine/src/builtins/array_buffer/mod.rs:499-506` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L499-L506) - -```rust - let object = this.as_object(); - let buf = object - .as_ref() - .and_then(JsObject::downcast_ref::) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("get ArrayBuffer.prototype.byteLength called with invalid `this`") - })?; -``` - - -### [20] `core/engine/src/builtins/array_buffer/mod.rs:508` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L508) - -```rust - // 4. If IsDetachedBuffer(O) is true, return +0𝔽. -``` - - -### [21] `core/engine/src/builtins/array_buffer/mod.rs:363-367` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L363-L367) - -```rust - js_string!("byteLength"), - Some(get_byte_length), - None, - flag_attributes, - ) -``` - - -### [22] `core/engine/src/builtins/array_buffer/mod.rs:642-647` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L642-L647) - -```rust - /// `ArrayBuffer.prototype.slice ( start, end )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-arraybuffer.prototype.slice -``` - - -### [23] `core/engine/src/builtins/array_buffer/mod.rs:652-658` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L652-L658) - -```rust - let buf = this - .as_object() - .and_then(|o| o.clone().downcast::().ok()) - .ok_or_else(|| { - JsNativeError::typ() - .with_message("ArrayBuffer.slice called with invalid `this` value") - })?; -``` - - -### [24] `core/engine/src/builtins/array_buffer/mod.rs:663-666` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L663-L666) - -```rust - if buf.data().is_detached() { - return Err(JsNativeError::typ() - .with_message("ArrayBuffer.slice called with detached buffer") - .into()); -``` - - -### [25] `core/engine/src/builtins/array_buffer/mod.rs:672-682` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L672-L682) - -```rust - // 6. Let relativeStart be ? ToIntegerOrInfinity(start). - // 7. If relativeStart = -∞, let first be 0. - // 8. Else if relativeStart < 0, let first be max(len + relativeStart, 0). - // 9. Else, let first be min(relativeStart, len). - let first = Array::get_relative_start(context, args.get_or_undefined(0), len)?; - - // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). - // 11. If relativeEnd = -∞, let final be 0. - // 12. Else if relativeEnd < 0, let final be max(len + relativeEnd, 0). - // 13. Else, let final be min(relativeEnd, len). - let final_ = Array::get_relative_end(context, args.get_or_undefined(1), len)?; -``` - - -### [26] `core/engine/src/builtins/array_buffer/mod.rs:678` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L678) - -```rust - // 10. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). -``` - - -### [27] `core/engine/src/builtins/array_buffer/mod.rs:687-691` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L687-L691) - -```rust - // 15. Let ctor be ? SpeciesConstructor(O, %ArrayBuffer%). - let ctor = buf - .clone() - .upcast() - .species_constructor(StandardConstructors::array_buffer, context)?; -``` - - -### [28] `core/engine/src/builtins/array_buffer/mod.rs:738-741` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L738-L741) - -```rust - // 26. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). - let first = first as usize; - let new_len = new_len as usize; - to_buf[..new_len].copy_from_slice(&from_buf[first..first + new_len]); -``` - - -### [29] `core/engine/src/builtins/array_buffer/mod.rs:498` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L498) - -```rust - // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. -``` - - -### [30] `core/engine/src/builtins/array_buffer/mod.rs:651` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L651) - -```rust - // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. -``` - - -### [31] `core/engine/src/builtins/array_buffer/mod.rs:22` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L22) - -```rust -pub use shared::SharedArrayBuffer; -``` - - -### [32] `core/engine/src/builtins/array_buffer/mod.rs:207` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L207) - -```rust - /// The `[[ArrayBufferMaxByteLength]]` internal slot. -``` - - -### [33] `core/engine/src/builtins/array_buffer/mod.rs:271-275` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L271-L275) - -```rust - pub fn resize(&mut self, new_byte_length: u64) -> JsResult<()> { - let Some(max_byte_len) = self.max_byte_len else { - return Err(JsNativeError::typ() - .with_message("ArrayBuffer.resize: cannot resize a fixed-length buffer") - .into()); -``` - - -### [34] `core/engine/src/builtins/array_buffer/mod.rs:396-401` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L396-L401) - -```rust - .method(Self::transfer::, js_string!("transfer"), 0) - .method( - Self::transfer::, - js_string!("transferToFixedLength"), - 0, - ); -``` - - -### [35] `core/engine/src/builtins/typed_array/object.rs:31-37` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/typed_array/object.rs#L31-L37) - - -### [36] `core/engine/src/builtins/array_buffer/mod.rs:201` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L201) - -```rust -#[derive(Debug, Clone, Trace, Finalize, JsData)] -``` - - -### [37] `core/engine/src/builtins/dataview/mod.rs:38` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/dataview/mod.rs#L38) - -```rust -#[derive(Debug, Clone, Trace, Finalize, JsData)] -``` - - -### [38] `core/engine/src/builtins/array_buffer/mod.rs:354-403` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L354-L403) - -```rust - let builder = BuiltInBuilder::from_standard_constructor::(realm) - .static_accessor( - JsSymbol::species(), - Some(get_species), - None, - Attribute::CONFIGURABLE, - ) - .static_method(Self::is_view, js_string!("isView"), 1) - .accessor( - js_string!("byteLength"), - Some(get_byte_length), - None, - flag_attributes, - ) - .accessor( - js_string!("resizable"), - Some(get_resizable), - None, - flag_attributes, - ) - .accessor( - js_string!("maxByteLength"), - Some(get_max_byte_length), - None, - flag_attributes, - ) - .method(Self::js_resize, js_string!("resize"), 1) - .method(Self::slice, js_string!("slice"), 2) - .property( - JsSymbol::to_string_tag(), - Self::NAME, - Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ); - - #[cfg(feature = "experimental")] - let builder = builder - .accessor( - js_string!("detached"), - Some(get_detached), - None, - flag_attributes, - ) - .method(Self::transfer::, js_string!("transfer"), 0) - .method( - Self::transfer::, - js_string!("transferToFixedLength"), - 0, - ); - - builder.build(); -``` - - -### [39] `core/engine/src/builtins/dataview/mod.rs:112-162` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/dataview/mod.rs#L112-L162) - -```rust - let builder = BuiltInBuilder::from_standard_constructor::(realm) - .accessor( - js_string!("buffer"), - Some(get_buffer), - None, - flag_attributes, - ) - .accessor( - js_string!("byteLength"), - Some(get_byte_length), - None, - flag_attributes, - ) - .accessor( - js_string!("byteOffset"), - Some(get_byte_offset), - None, - flag_attributes, - ) - .method(Self::get_big_int64, js_string!("getBigInt64"), 1) - .method(Self::get_big_uint64, js_string!("getBigUint64"), 1) - .method(Self::get_float32, js_string!("getFloat32"), 1) - .method(Self::get_float64, js_string!("getFloat64"), 1) - .method(Self::get_int8, js_string!("getInt8"), 1) - .method(Self::get_int16, js_string!("getInt16"), 1) - .method(Self::get_int32, js_string!("getInt32"), 1) - .method(Self::get_uint8, js_string!("getUint8"), 1) - .method(Self::get_uint16, js_string!("getUint16"), 1) - .method(Self::get_uint32, js_string!("getUint32"), 1) - .method(Self::set_big_int64, js_string!("setBigInt64"), 2) - .method(Self::set_big_uint64, js_string!("setBigUint64"), 2) - .method(Self::set_float32, js_string!("setFloat32"), 2) - .method(Self::set_float64, js_string!("setFloat64"), 2) - .method(Self::set_int8, js_string!("setInt8"), 2) - .method(Self::set_int16, js_string!("setInt16"), 2) - .method(Self::set_int32, js_string!("setInt32"), 2) - .method(Self::set_uint8, js_string!("setUint8"), 2) - .method(Self::set_uint16, js_string!("setUint16"), 2) - .method(Self::set_uint32, js_string!("setUint32"), 2) - .property( - JsSymbol::to_string_tag(), - Self::NAME, - Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, - ); - - #[cfg(feature = "float16")] - let builder = builder - .method(Self::get_float16, js_string!("getFloat16"), 1) - .method(Self::set_float16, js_string!("setFloat16"), 2); - - builder.build(); -``` - - -### [40] `core/engine/src/object/builtins/jsarraybuffer.rs:365-374` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/object/builtins/jsarraybuffer.rs#L365-L374) - -```rust - let context = &mut Context::default(); - - let data = AlignedVec::from_iter(0, [1u8, 2, 3, 4, 5]); - let buffer = JsArrayBuffer::from_byte_block(data, context).unwrap(); - - assert_eq!(buffer.to_vec(), Some(vec![1u8, 2, 3, 4, 5])); - - buffer.detach(&JsValue::undefined()).unwrap(); - assert_eq!(buffer.to_vec(), None); - } -``` - - -### [41] `tests/tester/src/exec/js262.rs:147-172` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/tests/tester/src/exec/js262.rs#L147-L172) - -```rust -/// The `$262.detachArrayBuffer()` function. -/// -/// Implements the `DetachArrayBuffer` abstract operation. -fn detach_array_buffer(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { - fn type_err() -> JsNativeError { - JsNativeError::typ().with_message("The provided object was not an ArrayBuffer") - } - - // 1. Assert: IsSharedArrayBuffer(arrayBuffer) is false. - let object = args.first().and_then(JsValue::as_object); - let mut array_buffer = object - .as_ref() - .and_then(|o| o.downcast_mut::()) - .ok_or_else(type_err)?; - - // 2. If key is not present, set key to undefined. - let key = args.get_or_undefined(1); - - // 3. If SameValue(arrayBuffer.[[ArrayBufferDetachKey]], key) is false, throw a TypeError exception. - // 4. Set arrayBuffer.[[ArrayBufferData]] to null. - // 5. Set arrayBuffer.[[ArrayBufferByteLength]] to 0. - array_buffer.detach(key)?; - - // 6. Return NormalCompletion(null). - Ok(JsValue::null()) -} -``` - - -### [42] `core/engine/src/builtins/array_buffer/mod.rs:205` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/array_buffer/mod.rs#L205) - -```rust - data: Option>, -``` - - -### [43] `core/engine/src/builtins/dataview/mod.rs:131-150` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/dataview/mod.rs#L131-L150) - -```rust - .method(Self::get_big_int64, js_string!("getBigInt64"), 1) - .method(Self::get_big_uint64, js_string!("getBigUint64"), 1) - .method(Self::get_float32, js_string!("getFloat32"), 1) - .method(Self::get_float64, js_string!("getFloat64"), 1) - .method(Self::get_int8, js_string!("getInt8"), 1) - .method(Self::get_int16, js_string!("getInt16"), 1) - .method(Self::get_int32, js_string!("getInt32"), 1) - .method(Self::get_uint8, js_string!("getUint8"), 1) - .method(Self::get_uint16, js_string!("getUint16"), 1) - .method(Self::get_uint32, js_string!("getUint32"), 1) - .method(Self::set_big_int64, js_string!("setBigInt64"), 2) - .method(Self::set_big_uint64, js_string!("setBigUint64"), 2) - .method(Self::set_float32, js_string!("setFloat32"), 2) - .method(Self::set_float64, js_string!("setFloat64"), 2) - .method(Self::set_int8, js_string!("setInt8"), 2) - .method(Self::set_int16, js_string!("setInt16"), 2) - .method(Self::set_int32, js_string!("setInt32"), 2) - .method(Self::set_uint8, js_string!("setUint8"), 2) - .method(Self::set_uint16, js_string!("setUint16"), 2) - .method(Self::set_uint32, js_string!("setUint32"), 2) -``` - - -### [44] `core/engine/src/builtins/dataview/mod.rs:509-512` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/dataview/mod.rs#L509-L512) - -```rust - if is_little_endian { - value.to_little_endian() - } else { - value.to_big_endian() -``` - - -### [45] `core/engine/src/builtins/dataview/mod.rs:849-852` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/dataview/mod.rs#L849-L852) - -```rust - let value = if is_little_endian { - value.to_little_endian() - } else { - value.to_big_endian() -``` diff --git a/target/deepwiki/object-literal-semantics.md b/target/deepwiki/object-literal-semantics.md deleted file mode 100644 index 4689e27e75..0000000000 --- a/target/deepwiki/object-literal-semantics.md +++ /dev/null @@ -1,380 +0,0 @@ -# DeepWiki Response - -Repository: `boa-dev/boa` -Question: For ECMAScript object literal implementation, explain object literal allocation prototype defaults, special __proto__ semantics including null/object/non-object and duplicate/computed cases, computed property key ToPropertyKey evaluation ordering before value evaluation and abrupt completion, computed accessor key handling, escaped reserved-word property names, and how parser versus lowering/runtime responsibilities are separated. Please point to Boa implementation details where possible. -Search: https://deepwiki.com/search/for-ecmascript-object-literal_3076013f-f447-4ceb-bea7-4aa42dfcbcd3 - -**Question**: For ECMAScript object literal implementation, explain object literal allocation prototype defaults, special __proto__ semantics including null/object/non-object and duplicate/computed cases, computed property key ToPropertyKey evaluation ordering before value evaluation and abrupt completion, computed accessor key handling, escaped reserved-word property names, and how parser versus lowering/runtime responsibilities are separated. Please point to Boa implementation details where possible. - -Boa's implementation of ECMAScript object literals involves specific handling for prototype defaults, `__proto__` semantics, computed property keys, and the separation of parser and runtime responsibilities. - -## Object Literal Allocation and Prototype Defaults - -When an object literal is created, its default prototype is `Object.prototype` [1](#ref-q1-1) . This is consistent with the ECMAScript specification for object creation. The `get_prototype_from_constructor` function is responsible for determining the prototype of an object based on its constructor [2](#ref-q1-2) . If a constructor's `prototype` property is not an object, it defaults to the intrinsic prototype of the constructor's realm [3](#ref-q1-3) . - -## Special `__proto__` Semantics - -Boa handles the special `__proto__` property in object literals, including cases for `null`, `object`, and non-object values, as well as duplicate and computed property names [4](#ref-q1-4) . - -### `__proto__` Getter and Setter - -The `Object.prototype.__proto__` getter (`legacy_proto_getter`) exposes the internal `[[Prototype]]` of an object [5](#ref-q1-5) . The setter (`legacy_proto_setter`) allows mutation of an object's `[[Prototype]]` [6](#ref-q1-6) . - -The `legacy_proto_setter` handles different types for the `proto` argument: -* If `proto` is an `Object` or `Null`, it attempts to set the prototype [7](#ref-q1-7) . -* If `proto` is neither an `Object` nor `Null`, the setter returns `undefined` [8](#ref-q1-8) . -* If the `this` value is not an `Object`, it also returns `undefined` [9](#ref-q1-9) . -* If the `[[SetPrototypeOf]]` internal method returns `false`, a `TypeError` is thrown [10](#ref-q1-10) . - -## Computed Property Keys - -Computed property keys are evaluated before their corresponding property values. The `ByteCompiler` is responsible for compiling the expression that defines the computed property name [11](#ref-q1-11) . This expression's result is then converted to a `PropertyKey` using the `emit_to_property_key` instruction [12](#ref-q1-12) . This ensures that the key is resolved before the property's value or accessor is processed. The `to_property_key` method on `JsValue` handles the conversion of various JavaScript types into a `PropertyKey` [13](#ref-q1-13) . This conversion can lead to an abrupt completion if, for example, `to_primitive` throws an error [14](#ref-q1-14) . - -### Computed Accessor Key Handling - -For computed accessor keys (getters and setters), the `ByteCompiler` compiles the key expression and converts it to a `PropertyKey` [15](#ref-q1-15) . Then, specific bytecode instructions like `emit_define_class_static_getter_by_value` or `emit_define_class_getter_by_value` are used to define the accessor with the computed key [16](#ref-q1-16) . - -## Escaped Reserved-Word Property Names - -Boa handles escaped identifiers, including those that might be reserved words, during parsing to ensure correct property name interpretation [17](#ref-q1-17) . The `PropertyName::Literal` and `PropertyName::Computed` variants in the AST represent property names, with `Literal` handling direct identifiers and `Computed` handling expressions that evaluate to a property key [18](#ref-q1-18) . - -## Parser vs. Lowering/Runtime Responsibilities - -The architecture of Boa separates responsibilities into distinct stages [19](#ref-q1-19) : -1. **Parser**: The `boa_parser` crate is responsible for lexing the source code into tokens and then parsing these tokens into an Abstract Syntax Tree (AST) [20](#ref-q1-20) . This stage handles the syntactic validation of object literals, including property names and structure. -2. **ByteCompiler (Lowering)**: The `ByteCompiler` in `boa_engine::bytecompiler` takes the AST and transforms it into bytecode instructions [21](#ref-q1-21) . This is where computed property keys are evaluated and converted to `PropertyKey`s [15](#ref-q1-15) . -3. **VM (Runtime)**: The Virtual Machine (`boa_engine::vm::Vm`) executes the generated bytecode [22](#ref-q1-22) . During runtime, operations like property access (`__get__`) [23](#ref-q1-23) and prototype manipulation (`__set_prototype_of__`) [24](#ref-q1-24) occur. The `JsObject` and its internal methods handle the actual object model and property storage [25](#ref-q1-25) . - -## Notes - -The provided context highlights the implementation of object literals and related features within the Boa engine. The `CHANGELOG.md` entry for "Handle `__proto__` fields in object literals" [4](#ref-q1-4) indicates a specific focus on these semantics. The `Object Model & Shapes` wiki page provides a high-level overview of how objects are structured and optimized in Boa [26](#ref-q1-26) . - -Wiki pages you might want to explore: -- [Architecture (boa-dev/boa)](/wiki/boa-dev/boa#1.1) -- [Object Model & Shapes (boa-dev/boa)](/wiki/boa-dev/boa#2.6) - -View this search on DeepWiki: https://deepwiki.com/search/for-ecmascript-object-literal_3076013f-f447-4ceb-bea7-4aa42dfcbcd3 - -## References - - -### [1] `core/engine/src/builtins/object/mod.rs:1-7` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/object/mod.rs#L1-L7) - -```rust -//! Boa's implementation of ECMAScript's global `Object` object. -//! -//! The `Object` class represents one of ECMAScript's data types. -//! -//! It is used to store various keyed collections and more complex entities. -//! Objects can be created using the `Object()` constructor or the -//! object initializer / literal syntax. -``` - - -### [2] `core/engine/src/object/internal_methods/mod.rs:1171-1175` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/object/internal_methods/mod.rs#L1171-L1175) - -```rust -pub(crate) fn get_prototype_from_constructor( - constructor: &JsValue, - default: F, - context: &mut Context, -) -> JsResult -``` - - -### [3] `core/engine/src/object/internal_methods/mod.rs:1188-1195` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/object/internal_methods/mod.rs#L1188-L1195) - -```rust - // 3. If Type(proto) is not Object, then - // a. Let realm be ? GetFunctionRealm(constructor). - constructor.get_function_realm(context)? - } else { - context.realm().clone() - }; - // b. Set proto to realm's intrinsic object named intrinsicDefaultProto. - Ok(default(realm.intrinsics().constructors()).prototype()) -``` - - -### [4] `CHANGELOG.md:818` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/CHANGELOG.md#L818) - -```markdown -- Handle `__proto__` fields in object literals by @raskad in [#2423](https://github.com/boa-dev/boa/pull/2423) -``` - - -### [5] `core/engine/src/builtins/object/mod.rs:195-217` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/object/mod.rs#L195-L217) - -```rust - /// `get Object.prototype.__proto__` - /// - /// The `__proto__` getter function exposes the value of the - /// internal `[[Prototype]]` of an object. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#sec-get-object.prototype.__proto__ - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto - pub fn legacy_proto_getter( - this: &JsValue, - _: &[JsValue], - context: &mut Context, - ) -> JsResult { - // 1. Let O be ? ToObject(this value). - let obj = this.to_object(context)?; - - // 2. Return ? O.[[GetPrototypeOf]](). - let proto = obj.__get_prototype_of__(&mut InternalMethodPropertyContext::new(context))?; - - Ok(proto.map_or(JsValue::null(), JsValue::new)) -``` - - -### [6] `core/engine/src/builtins/object/mod.rs:220-231` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/object/mod.rs#L220-L231) - -```rust - /// `set Object.prototype.__proto__` - /// - /// The `__proto__` setter allows the `[[Prototype]]` of - /// an object to be mutated. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - [MDN documentation][mdn] - /// - /// [spec]: https://tc39.es/ecma262/#sec-set-object.prototype.__proto__ - /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto - pub fn legacy_proto_setter( -``` - - -### [7] `core/engine/src/builtins/object/mod.rs:240-242` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/object/mod.rs#L240-L242) - -```rust - let proto = match args.get_or_undefined(0).variant() { - JsVariant::Object(proto) => Some(proto.clone()), - JsVariant::Null => None, -``` - - -### [8] `core/engine/src/builtins/object/mod.rs:243-244` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/object/mod.rs#L243-L244) - -```rust - _ => return Ok(JsValue::undefined()), - }; -``` - - -### [9] `core/engine/src/builtins/object/mod.rs:247-249` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/object/mod.rs#L247-L249) - -```rust - let JsVariant::Object(object) = this.variant() else { - return Ok(JsValue::undefined()); - }; -``` - - -### [10] `core/engine/src/builtins/object/mod.rs:254-258` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/object/mod.rs#L254-L258) - -```rust - - // 5. If status is false, throw a TypeError exception. - if !status { - return Err(js_error!(TypeError: "__proto__ called on null or undefined")); - } -``` - - -### [11] `core/engine/src/bytecompiler/class.rs:280-282` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/bytecompiler/class.rs#L280-L282) - -```rust - ClassElementName::PropertyName(PropertyName::Computed(name)) => { - let key = self.register_allocator.alloc(); - self.compile_expr(name, &key); -``` - - -### [12] `core/engine/src/bytecompiler/class.rs:283-284` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/bytecompiler/class.rs#L283-L284) - -```rust - self.bytecode - .emit_to_property_key(key.variable(), key.variable()); -``` - - -### [13] `core/engine/src/value/mod.rs:1057-1060` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/value/mod.rs#L1057-L1060) - -```rust - /// Converts the value to a `PropertyKey`, that can be used as a key for properties. - /// - /// See - pub fn to_property_key(&self, context: &mut Context) -> JsResult { -``` - - -### [14] `core/engine/src/value/mod.rs:1083-1085` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/value/mod.rs#L1083-L1085) - -```rust - JsVariant::Object(o) => o - .to_primitive(context, PreferredType::String)? - .to_property_key(context), -``` - - -### [15] `core/engine/src/bytecompiler/class.rs:280-284` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/bytecompiler/class.rs#L280-L284) - -```rust - ClassElementName::PropertyName(PropertyName::Computed(name)) => { - let key = self.register_allocator.alloc(); - self.compile_expr(name, &key); - self.bytecode - .emit_to_property_key(key.variable(), key.variable()); -``` - - -### [16] `core/engine/src/bytecompiler/class.rs:294-325` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/bytecompiler/class.rs#L294-L325) - -```rust - self.bytecode.emit_define_class_static_getter_by_value( - method.variable(), - key.variable(), - object_register.variable(), - ); - } - (true, MethodDefinitionKind::Set) => { - self.bytecode.emit_define_class_static_setter_by_value( - method.variable(), - key.variable(), - object_register.variable(), - ); - } - (true, _) => { - self.bytecode.emit_define_class_static_method_by_value( - method.variable(), - key.variable(), - object_register.variable(), - ); - } - (false, MethodDefinitionKind::Get) => { - self.bytecode.emit_define_class_getter_by_value( - method.variable(), - key.variable(), - object_register.variable(), - ); - } - (false, MethodDefinitionKind::Set) => { - self.bytecode.emit_define_class_setter_by_value( - method.variable(), - key.variable(), - object_register.variable(), -``` - - -### [17] `CHANGELOG.md:838` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/CHANGELOG.md#L838) - -```markdown -- Add early errors for escaped identifiers by @raskad in [#2546](https://github.com/boa-dev/boa/pull/2546) -``` - - -### [18] `core/engine/src/bytecompiler/declaration/declaration_pattern.rs:42-76` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/bytecompiler/declaration/declaration_pattern.rs#L42-L76) - -```rust - PropertyName::Literal(ident) => { - self.emit_get_property_by_name(&dst, None, object, ident.sym()); - let key = self.register_allocator.alloc(); - self.emit_store_literal( - Literal::String( - self.interner() - .resolve_expect(ident.sym()) - .into_common(false), - ), - &key, - ); - excluded_keys_registers.push(key); - } - PropertyName::Computed(node) => { - let key = self.register_allocator.alloc(); - self.compile_expr(node, &key); - if rest_exits { - self.bytecode.emit_get_property_by_value_push( - dst.variable(), - key.variable(), - object.variable(), - object.variable(), - ); - excluded_keys_registers.push(key); - } else { - self.bytecode.emit_get_property_by_value( - dst.variable(), - key.variable(), - object.variable(), - object.variable(), - ); - self.register_allocator.dealloc(key); - } - } - } -``` - - -### [19] `wiki/Architecture:20` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/wiki/Architecture#L20) - - -### [20] `wiki/Architecture:50-53` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/wiki/Architecture#L50-L53) - - -### [21] `wiki/Architecture:70-71` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/wiki/Architecture#L70-L71) - - -### [22] `wiki/Architecture:72-73` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/wiki/Architecture#L72-L73) - - -### [23] `core/engine/src/vm/opcode/get/property.rs:68` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/vm/opcode/get/property.rs#L68) - -```rust - let result = object.__get__(&key, receiver.clone(), context)?; -``` - - -### [24] `core/engine/src/builtins/object/mod.rs:251-253` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/core/engine/src/builtins/object/mod.rs#L251-L253) - -```rust - // 4. Let status be ? O.[[SetPrototypeOf]](proto). - let status = - object.__set_prototype_of__(proto, &mut InternalMethodPropertyContext::new(context))?; -``` - - -### [25] `wiki/Object Model & Shapes:20` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/wiki/Object Model & Shapes#L20) - - -### [26] `wiki/Object Model & Shapes:1` -Source: [boa-dev/boa @ f5e88de5](https://github.com/boa-dev/boa/blob/f5e88de5/wiki/Object Model & Shapes#L1) diff --git a/test-files/issue_321_named_namespace_reexport/main.ts b/test-files/issue_321_named_namespace_reexport/main.ts index d3e44dfd72..573dc3f612 100644 --- a/test-files/issue_321_named_namespace_reexport/main.ts +++ b/test-files/issue_321_named_namespace_reexport/main.ts @@ -15,9 +15,8 @@ // `runSync(program)` then read `program._tag` off a closure pointer and // the runtime threw `Cannot read properties of undefined (reading '_tag')`. // -// POST-FIX: the consumer's namespace local is tagged into a new -// `namespace_reexport_named_imports` set so the StaticMethodCall arm -// fetches the closure via the zero-arg getter and dispatches through +// POST-FIX: the StaticMethodCall arm classifies the member as variable-shaped, +// fetches its closure via the zero-arg getter, and dispatches through // `js_closure_callN` with the user's args. `Effect.succeed(42)` now // returns the EffectInst, `_tag === "Commit"`, and `runSync` returns 42. //