diff --git a/cli/src/main.rs b/cli/src/main.rs index d5c50f9b488..52a193dac8c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -11,12 +11,14 @@ mod debug; mod helper; use boa_engine::context::time::JsInstant; +use boa_engine::interop::JsThis; use boa_engine::job::{GenericJob, TimeoutJob}; use boa_engine::{ - Context, JsError, JsResult, Source, + Context, Finalize, JsData, JsError, JsObject, JsResult, JsValue, Source, Trace, boa_class, builtins::promise::PromiseState, context::ContextBuilder, job::{Job, JobExecutor, NativeAsyncJob, PromiseJob}, + js_string, module::{Module, SimpleModuleLoader}, optimizer::OptimizerOptions, script::Script, @@ -30,6 +32,13 @@ use color_eyre::{ }; use colored::Colorize; use debug::init_boa_debug_object; +#[cfg(all( + target_arch = "x86_64", + target_os = "linux", + target_env = "gnu", + feature = "dhat" +))] +use jemallocator as _; use rustyline::{EditMode, Editor, config::Config, error::ReadlineError}; use std::collections::BTreeMap; use std::{ @@ -43,14 +52,6 @@ use std::{ rc::Rc, }; -#[cfg(all( - target_arch = "x86_64", - target_os = "linux", - target_env = "gnu", - feature = "dhat" -))] -use jemallocator as _; - #[cfg(all( target_arch = "x86_64", target_os = "linux", @@ -381,6 +382,66 @@ fn evaluate_files(args: &Opt, context: &mut Context, loader: &SimpleModuleLoader Ok(()) } +fn init_debug_stuff_do_not_merge(context: &mut Context) { + #[derive(Debug, Trace, Finalize, JsData)] + struct X; + + #[boa_class(extends = "Base")] + impl X { + #[boa(constructor)] + #[boa(length = 0)] + fn new(this: JsThis, context: &mut Context) -> Self { + Self + } + + fn foo(JsThis(this): JsThis, context: &mut Context) -> u32 { + eprintln!("this: {}", JsValue::new(this.clone()).display()); + + eprintln!( + "zthis.foo: {}", + boa_engine::JsValue::from(this.get(js_string!("foo"), context).unwrap()) + .display() + .to_string() + ); + eprintln!( + "zthis.baseFoo: {}", + boa_engine::JsValue::from(this.get(js_string!("baseFoo"), context).unwrap()) + .display() + .to_string() + ); + eprintln!( + "zthis.proto: {}", + boa_engine::JsValue::from(this.prototype().unwrap()).display_obj(true) + ); + + this.get(js_string!("baseFoo"), context) + .unwrap() + .as_callable() + .expect("as callable") + .call(&this.clone().into(), &[JsValue::from(1)], context) + .expect("baseFoo() call") + .to_u32(context) + .expect("to_u32") + + 1 + } + } + + context + .eval(Source::from_bytes( + r" + class Base { + static baseStatic() { return 'hello'; } + baseFoo(a) { return a + 1 } + } + ", + )) + .expect("eval failed"); + + context + .register_global_class::() + .expect("global_class registration"); +} + fn main() -> Result<()> { color_eyre::config::HookBuilder::default() .display_location_section(false) @@ -413,6 +474,8 @@ fn main() -> Result<()> { init_boa_debug_object(&mut context); } + init_debug_stuff_do_not_merge(&mut context); + // Configure optimizer options let mut optimizer_options = OptimizerOptions::empty(); optimizer_options.set(OptimizerOptions::STATISTICS, args.optimizer_statistics); diff --git a/core/engine/src/builtins/function/mod.rs b/core/engine/src/builtins/function/mod.rs index a46c75e45a2..ad6727d268c 100644 --- a/core/engine/src/builtins/function/mod.rs +++ b/core/engine/src/builtins/function/mod.rs @@ -1080,6 +1080,15 @@ fn function_construct( argument_count: usize, context: &mut InternalMethodCallContext<'_>, ) -> JsResult { + eprintln!( + "function_construct {} {}", + JsValue::from(this_function_object.clone()).display(), + argument_count + ); + eprintln!( + "function_construct proto: {}", + JsValue::from(this_function_object.prototype().unwrap()).display() + ); context.check_runtime_limits()?; let function = this_function_object @@ -1100,6 +1109,11 @@ fn function_construct( let env_fp = environments.len() as u32; let new_target = context.vm.stack.pop(); + eprintln!( + "function_construct new_target: {} {}", + new_target.display_obj(false), + argument_count + ); let this = if code.is_derived_constructor() { None @@ -1110,6 +1124,10 @@ fn function_construct( // see let prototype = get_prototype_from_constructor(&new_target, StandardConstructors::object, context)?; + eprintln!( + "function_construct proto: {}", + JsValue::from(prototype.clone()).display() + ); let this = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, diff --git a/core/engine/src/class.rs b/core/engine/src/class.rs index 525edd202ec..506c5453468 100644 --- a/core/engine/src/class.rs +++ b/core/engine/src/class.rs @@ -111,6 +111,8 @@ use crate::{ object::{ConstructorBuilder, FunctionBinding, JsFunction, JsObject, NativeObject, PROTOTYPE}, property::{Attribute, PropertyDescriptor, PropertyKey}, }; +use boa_engine::object::JsPrototype; +use boa_parser::Source; /// Native class. /// @@ -161,11 +163,16 @@ pub trait Class: NativeObject + Sized { /// could lead to weird errors like missing inherited methods or incorrect internal data. /// fn construct( - new_target: &JsValue, + this_function_object: &JsValue, args: &[JsValue], context: &mut Context, ) -> JsResult { - if new_target.is_undefined() { + eprintln!( + "Class::construct {} {}", + Self::NAME, + this_function_object.display() + ); + if this_function_object.is_undefined() { return Err(JsNativeError::typ() .with_message(format!( "cannot call constructor of native class `{}` without new", @@ -175,10 +182,14 @@ pub trait Class: NativeObject + Sized { } let prototype = 'proto: { - let realm = if let Some(constructor) = new_target.as_object() { - if let Some(proto) = constructor.get(PROTOTYPE, context)?.as_object() { - break 'proto proto.clone(); - } + let realm = if let Some(constructor) = this_function_object.as_object() { + // if let Some(proto) = constructor.get(PROTOTYPE, context)?.as_object() { + // eprintln!( + // "construct: {}", + // JsValue::from(proto.clone()).display().to_string() + // ); + // break 'proto proto.clone(); + // } constructor.get_function_realm(context)? } else { context.realm().clone() @@ -193,11 +204,20 @@ pub trait Class: NativeObject + Sized { })? .prototype() }; + let r = context.realm(); - let data = Self::data_constructor(new_target, args, context)?; + let data = Self::data_constructor(this_function_object, args, context)?; - let object = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, data); + eprintln!( + "Class::construct proto: {}", + JsValue::from(prototype.clone()).display() + ); + let object = JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + prototype.clone(), + data, + ); + object.set_prototype(Some(prototype)); Self::object_constructor(&object, args, context)?; @@ -264,6 +284,20 @@ impl<'ctx> ClassBuilder<'ctx> { self.builder.build() } + /// Set the prototype of the class's constructor, making it a subclass. + pub fn extends(&mut self, value: T) -> &mut Self + where + T: Into, + { + let p = value.into(); + eprintln!("ClassBuilder::extends {:?}", p); + self.builder.inherit(p.clone()); + if let Some(p) = p { + self.builder.custom_prototype(p.prototype()); + } + self + } + /// Add a method to the class. /// /// It is added to `prototype`. diff --git a/core/engine/src/context/intrinsics.rs b/core/engine/src/context/intrinsics.rs index fc4e64291cf..1994f9ebbd7 100644 --- a/core/engine/src/context/intrinsics.rs +++ b/core/engine/src/context/intrinsics.rs @@ -3,7 +3,7 @@ use boa_gc::{Finalize, Trace}; use crate::{ - JsSymbol, + JsSymbol, JsValue, builtins::{Array, OrdinaryObject, iterable::IteratorPrototypes, uri::UriFunctions}, js_string, object::{ @@ -88,6 +88,11 @@ impl Default for StandardConstructor { impl StandardConstructor { /// Creates a new `StandardConstructor` from the constructor and the prototype. pub(crate) fn new(constructor: JsFunction, prototype: JsObject) -> Self { + eprintln!( + "StandardConstructor::new\n ctor: {constructor:?}\n ctor.proto: {}\n prototype: {}", + JsValue::from(constructor.prototype().clone().unwrap()).display(), + JsValue::from(prototype.clone()).display(), + ); Self { constructor, prototype, diff --git a/core/engine/src/interop/into_js_arguments.rs b/core/engine/src/interop/into_js_arguments.rs index 843d126925e..ee5394e6478 100644 --- a/core/engine/src/interop/into_js_arguments.rs +++ b/core/engine/src/interop/into_js_arguments.rs @@ -1,3 +1,5 @@ +use crate::builtins::TypeError; +use crate::js_error; use boa_engine::object::Object; use boa_engine::value::TryFromJs; use boa_engine::{Context, JsNativeError, JsObject, JsResult, JsValue, NativeObject}; @@ -205,6 +207,33 @@ impl<'a, T: TryFromJs> TryFromJsArgument<'a> for JsAll { } } +/// Captures the `super` keyword in a JS class. This will only work on +/// classes. It can be typed or not. +#[derive(Debug, Clone)] +pub struct JsSuper(JsObject); + +impl JsSuper { + /// Equivalent of calling `super(...)` in the constructor. It is undefined + /// behaviour to call this in + pub fn call(&self, args: &[JsValue], context: &mut Context) -> JsResult<()> { + self.0.construct(args, Some(&self.0), context)?; + Ok(()) + } +} + +impl<'a> TryFromJsArgument<'a> for JsSuper { + fn try_from_js_argument( + this: &'a JsValue, + rest: &'a [JsValue], + _context: &mut Context, + ) -> JsResult<(Self, &'a [JsValue])> { + let o = this + .as_object() + .ok_or_else(|| js_error!(TypeError: "this must be an object"))?; + Ok((Self(o), rest)) + } +} + /// Captures the `this` value in a JS function. Although this can be /// specified multiple times as argument, it will always be filled /// with clone of the same value. diff --git a/core/engine/src/native_function/mod.rs b/core/engine/src/native_function/mod.rs index d8b9e916295..5574ca34679 100644 --- a/core/engine/src/native_function/mod.rs +++ b/core/engine/src/native_function/mod.rs @@ -386,21 +386,35 @@ pub(crate) fn native_function_call( /// Panics if the object is currently mutably borrowed. // fn native_function_construct( - obj: &JsObject, + this_function_object: &JsObject, argument_count: usize, context: &mut InternalMethodCallContext<'_>, ) -> JsResult { + eprintln!( + "native_function_construct backtrace: {:?}\n{}", + this_function_object, + std::backtrace::Backtrace::capture() + ); + eprintln!( + "native_function_construct {} {}", + JsValue::from(this_function_object.clone()).display(), + argument_count + ); + eprintln!( + "native_function_construct proto: {}", + JsValue::from(this_function_object.prototype().unwrap()).display() + ); + // We technically don't need this since native functions don't push any new frames to the // vm, but we'll eventually have to combine the native stack with the vm stack. context.check_runtime_limits()?; - let this_function_object = obj.clone(); let NativeFunctionObject { f: function, name, constructor, realm, - } = obj + } = this_function_object .downcast_ref::() .expect("the object should be a native function object") .clone(); @@ -415,21 +429,37 @@ fn native_function_construct( let mut realm = realm.unwrap_or_else(|| context.realm().clone()); context.swap_realm(&mut realm); - context.vm.native_active_function = Some(this_function_object); + context.vm.native_active_function = Some(this_function_object.clone()); + eprintln!( + "native_function_construct stack:\n{:?}\n-------------", + context.vm.stack + ); let new_target = context.vm.stack.pop(); + eprintln!( + "native_function_construct new_target: {} {}", + new_target.display_obj(false), + argument_count + ); + let args = context .vm .stack .calling_convention_pop_arguments(argument_count); let _func = context.vm.stack.pop(); - let _this = context.vm.stack.pop(); + let this = context.vm.stack.pop(); let result = function .call(&new_target, &args, context) .map_err(|err| err.inject_realm(context.realm().clone())) .and_then(|v| match v.variant() { - JsVariant::Object(o) => Ok(o.clone()), + JsVariant::Object(o) => { + eprintln!( + "native_function_construct construct object: {}", + JsValue::from(this_function_object.clone()).display() + ); + Ok(o.clone()) + } val => { if constructor.expect("must be a constructor").is_base() || val.is_undefined() { let prototype = get_prototype_from_constructor( @@ -437,6 +467,10 @@ fn native_function_construct( StandardConstructors::object, context, )?; + eprintln!( + "native_function_construct proto: {}", + JsValue::from(prototype.clone()).display() + ); Ok(JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, diff --git a/core/engine/src/object/mod.rs b/core/engine/src/object/mod.rs index 63895196ab9..59e65076ff2 100644 --- a/core/engine/src/object/mod.rs +++ b/core/engine/src/object/mod.rs @@ -834,7 +834,16 @@ impl<'ctx> ConstructorBuilder<'ctx> { /// /// Default is `Object.prototype` pub fn inherit>(&mut self, prototype: O) -> &mut Self { + eprintln!("ConstructorBuilder::inherit"); + self.kind = Some(ConstructorKind::Derived); self.inherit = Some(prototype.into()); + + let v = self + .inherit + .as_ref() + .and_then(|o| o.clone()) + .map(|o| JsValue::new(o.clone())); + eprintln!("inherit: {:?}", v.map(|v| v.display().to_string())); self } @@ -864,6 +873,10 @@ impl<'ctx> ConstructorBuilder<'ctx> { /// Build the constructor function object. #[must_use] pub fn build(mut self) -> StandardConstructor { + eprintln!( + "ConstructorBuilder::build {}", + self.name.to_std_string_escaped() + ); let length = PropertyDescriptor::builder() .value(self.length) .writable(false) @@ -876,8 +889,10 @@ impl<'ctx> ConstructorBuilder<'ctx> { .configurable(true); let prototype = { - if let Some(proto) = self.inherit.take() { + let internal_methods = if let Some(proto) = self.inherit.take() { + let v = proto.internal_methods(); self.prototype.set_prototype(proto); + v } else { self.prototype.set_prototype( self.context @@ -886,10 +901,12 @@ impl<'ctx> ConstructorBuilder<'ctx> { .object() .prototype(), ); - } + &ORDINARY_INTERNAL_METHODS + }; - JsObject::from_object_and_vtable(self.prototype, &ORDINARY_INTERNAL_METHODS) + JsObject::from_object_and_vtable(self.prototype, internal_methods) }; + eprintln!("build: {}", JsValue::new(prototype.clone()).display()); let constructor = { let data = NativeFunctionObject { @@ -922,6 +939,9 @@ impl<'ctx> ConstructorBuilder<'ctx> { } if self.has_prototype_property { + eprintln!("ConstructorBuilder::build has_prototype_property"); + eprintln!("prototype: {}", JsValue::from(prototype.clone()).display()); + let before = constructor.prototype(); constructor.insert( PROTOTYPE, PropertyDescriptor::builder() @@ -930,6 +950,11 @@ impl<'ctx> ConstructorBuilder<'ctx> { .enumerable(false) .configurable(false), ); + let after = constructor.prototype(); + eprintln!( + "before: {before:?}, after: {after:?} (== {})", + before == after + ); } JsObject::from_object_and_vtable(constructor, internal_methods) @@ -947,6 +972,16 @@ impl<'ctx> ConstructorBuilder<'ctx> { ); } + eprintln!( + "constructor: {:?} prototype: {}", + constructor, + JsValue::from(prototype.clone()).display() + ); + eprintln!( + "contructor.prototype: {}", + JsValue::from(constructor.prototype().unwrap()).display() + ); + StandardConstructor::new(JsFunction::from_object_unchecked(constructor), prototype) } } diff --git a/core/macros/src/class.rs b/core/macros/src/class.rs index 59d7e89b69b..a2ab345f8cb 100644 --- a/core/macros/src/class.rs +++ b/core/macros/src/class.rs @@ -12,10 +12,45 @@ use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::visit_mut::VisitMut; use syn::{ - Attribute, ConstParam, Expr, FnArg, GenericParam, Ident, ImplItemFn, ItemImpl, LifetimeParam, - Lit, Meta, MetaNameValue, PatType, Receiver, ReturnType, Signature, Token, Type, TypeParam, + Attribute, ConstParam, Expr, ExprLit, FnArg, GenericParam, Ident, ImplItemFn, ItemImpl, + LifetimeParam, Lit, Meta, MetaNameValue, PatType, Receiver, ReturnType, Signature, Token, Type, + TypeParam, }; +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum FunctionArgumentType { + Context, + This, + Any, +} + +impl FunctionArgumentType { + fn from_pat_type(pat_type: &mut PatType) -> Self { + let ty = pat_type.ty.as_ref(); + + if take_path_attr(&mut pat_type.attrs, "context") { + return FunctionArgumentType::Context; + } + + if let Type::Reference(syn::TypeReference { + elem, mutability, .. + }) = ty + { + if let Type::Path(syn::TypePath { qself: _, path }) = elem.as_ref() { + if let Some(maybe_ctx) = path.segments.last() { + match maybe_ctx.ident.to_string().as_str() { + "Context" if mutability.is_some() => return Self::Context, + "JsThis" => return Self::This, + _ => {} + } + } + } + } + + FunctionArgumentType::Any + } +} + /// A function representation. Takes a function from the AST and remember its name, length and /// how its body should be in the output AST. /// There are three types of functions: Constructors, Methods and Accessors (setter/getter). @@ -79,40 +114,29 @@ impl Function { fn arg_from_pat_type( pat_type: &mut PatType, i: usize, + allow_this: bool, ) -> SpannedResult<(bool, TokenStream2, TokenStream2)> { - let ty = pat_type.ty.as_ref(); let ident = Ident::new(&format!("boa_arg_{i}"), Span2::call_site()); - // Find out if it's a boa context. - let is_context = match ty { - Type::Reference(syn::TypeReference { - elem, - mutability: Some(_), - .. - }) => match elem.as_ref() { - Type::Path(syn::TypePath { qself: _, path }) => { - if let Some(maybe_ctx) = path.segments.last() { - maybe_ctx.ident == "Context" - } else { - false - } - } - _ => take_path_attr(&mut pat_type.attrs, "context"), - }, - _ => false, - }; - - if is_context { - Ok((true, quote! {}, quote! { context })) - } else { - Ok(( - false, - quote! { - let (#ident, rest): (#ty, &[boa_engine::JsValue]) = - boa_engine::interop::TryFromJsArgument::try_from_js_argument( this, rest, context )?; - }, - quote! { #ident }, - )) + let arg_type = FunctionArgumentType::from_pat_type(pat_type); + + match arg_type { + FunctionArgumentType::Context => Ok((true, quote! {}, quote! { context })), + FunctionArgumentType::This if !allow_this => error( + pat_type, + "Cannot use `this` arguments in the data constructor.", + ), + _ => { + let ty = pat_type.ty.as_ref(); + Ok(( + false, + quote! { + let (#ident, rest): (#ty, &[boa_engine::JsValue]) = + boa_engine::interop::TryFromJsArgument::try_from_js_argument( this, rest, context )?; + }, + quote! { #ident }, + )) + } } } @@ -141,7 +165,7 @@ impl Function { } } FnArg::Typed(ty) => { - let (incr, decl, call) = Self::arg_from_pat_type(ty, i)?; + let (incr, decl, call) = Self::arg_from_pat_type(ty, i, true)?; if incr { not_param_count += 1; } @@ -255,7 +279,7 @@ impl Function { .map(|(i, a)| match a { FnArg::Receiver(receiver) => error(receiver, "Constructors cannot use 'self'"), FnArg::Typed(ty) => { - let (_, decl, call) = Self::arg_from_pat_type(ty, i)?; + let (_, decl, call) = Self::arg_from_pat_type(ty, i, false)?; Ok((decl, call)) } }) @@ -508,9 +532,31 @@ impl ClassVisitor { } /// Serialize the `boa_engine::Class` implementation into a token stream. - fn serialize_class_impl(&self, class_ty: &Type, class_name: &str) -> TokenStream2 { + fn serialize_class_impl( + &self, + class_ty: &Type, + class_name: &str, + extends: Option<&Extends>, + ) -> TokenStream2 { let arg_count = self.constructor.as_ref().map_or(0, |c| c.length); + let extends = extends.map(|e| match e { + Extends::JsValue(js) => quote! { + { + let proto = builder + .context() + .eval(Source::from_bytes( #js ))?; + builder.extends( + proto.as_object() + .ok_or_else(|| boa_engine::js_error!(TypeError: "invalid extends prototype"))? + ); + } + }, + Extends::RustPath(_) => { + todo!() + } + }); + let accessors = self.accessors.values().map(Accessor::body); let builder_methods = self.methods.iter().map(|m| { @@ -548,7 +594,7 @@ impl ClassVisitor { let constructor_body = self.constructor.as_ref().map_or_else( || { quote! { - Ok(Default::default()) + Err(boa_engine::JsNativeError::typ().with_message("illegal constructor").into()) } }, |c| c.body.clone(), @@ -568,6 +614,9 @@ impl ClassVisitor { } fn init(builder: &mut boa_engine::class::ClassBuilder) -> boa_engine::JsResult<()> { + // Add prototype. + #extends + // Add all statics. #(#builder_statics)* @@ -621,15 +670,24 @@ impl VisitMut for ClassVisitor { } } +#[derive(Debug)] +enum Extends { + JsValue(String), + // RustPath(ExprPath), + RustPath(()), +} + #[derive(Debug)] struct ClassArguments { name: Option, + extends: Option, } impl Parse for ClassArguments { fn parse(input: ParseStream<'_>) -> syn::Result { let args: Punctuated = Punctuated::parse_terminated(input)?; let mut name = None; + let mut extends = None; for arg in &args { match arg { @@ -643,11 +701,23 @@ impl Parse for ClassArguments { _ => Err(syn::Error::new(lit.span(), "Expected a string literal")), }?); } + Meta::NameValue(MetaNameValue { path, value, .. }) if path.is_ident("extends") => { + extends = Some(match value { + Expr::Lit(ExprLit { + lit: Lit::Str(s), .. + }) => Ok(Extends::JsValue(s.value())), + Expr::Path(_path) => Ok(Extends::RustPath(() /* path.clone() */)), + _ => Err(syn::Error::new( + value.span(), + "Expected a string literal or type identifier", + )), + }?); + } _ => return Err(syn::Error::new(arg.span(), "Unrecognize argument.")), } } - Ok(Self { name }) + Ok(Self { name, extends }) } } @@ -688,7 +758,8 @@ pub(crate) fn class_impl(attr: TokenStream, input: TokenStream) -> TokenStream { .into(); }; - let class_impl = visitor.serialize_class_impl(&impl_.self_ty, &name.to_string()); + let class_impl = + visitor.serialize_class_impl(&impl_.self_ty, &name.to_string(), args.extends.as_ref()); let debug = take_path_attr(&mut impl_.attrs, "debug"); diff --git a/tests/macros/tests/extends.rs b/tests/macros/tests/extends.rs new file mode 100644 index 00000000000..2786e6779b7 --- /dev/null +++ b/tests/macros/tests/extends.rs @@ -0,0 +1,93 @@ +//! Test for inheritance. +use boa_engine::interop::JsThis; +use boa_engine::{ + Context, Finalize, JsData, JsObject, JsValue, Source, Trace, boa_class, js_string, +}; + +#[test] +fn extends_js() { + #[derive(Debug, Trace, Finalize, JsData)] + struct X; + + #[boa_class(extends = "Base")] + impl X { + #[boa(constructor)] + #[boa(length = 0)] + fn new(_this: JsThis, _context: &mut Context) -> Self { + Self + } + + fn foo(JsThis(_this): JsThis, _context: &mut Context) -> u32 { + 0 + } + } + + let context = &mut Context::default(); + context + .eval(Source::from_bytes( + r" + class Base { + static baseStatic() { return 'hello'; } + baseFoo(a) { return a + 1 } + } + ", + )) + .expect("eval failed"); + + context + .register_global_class::() + .expect("global_class registration"); + + let x = context + .eval(Source::from_bytes( + r#" + new Uint8Array(); + (new X) + "#, + )) + .expect("eval 2 failed"); + + eprintln!( + "x.foo = {}", + x.as_object() + .unwrap() + .get(js_string!("foo"), context) + .unwrap() + .display() + ); + eprintln!( + "x.baseFoo = {}", + x.as_object() + .unwrap() + .get(js_string!("baseFoo"), context) + .unwrap() + .display() + ); +} + +#[test] +fn do_the_thing() { + let context = &mut Context::default(); + let x = context + .eval(Source::from_bytes( + r" + class Base { + baseFoo(a) { return a + 1 } + } + + class X extends Base { + foo() { return this.baseFoo(1) + 1 } + } + + new X + ", + )) + .expect("eval failed"); + + let obj = x.as_object().expect("x as object"); + eprintln!( + "baseFoo: {:?}", + obj.get(js_string!("baseFoo"), context) + .expect("get baseFoo") + ); +}