diff --git a/boa_engine/src/vm/opcode/mod.rs b/boa_engine/src/vm/opcode/mod.rs index 6d2a113440..b4bacd3490 100644 --- a/boa_engine/src/vm/opcode/mod.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -605,6 +605,8 @@ generate_opcodes! { /// Get the prototype of a superclass and push it on the stack. /// + /// Additionally this sets the `[[prototype]]` of the class and the `DERIVED` flag. + /// /// Operands: /// /// Stack: class, superclass **=>** class, superclass.prototype diff --git a/boa_engine/src/vm/opcode/push/class/mod.rs b/boa_engine/src/vm/opcode/push/class/mod.rs index 0c5438a1b3..28e7c183b0 100644 --- a/boa_engine/src/vm/opcode/push/class/mod.rs +++ b/boa_engine/src/vm/opcode/push/class/mod.rs @@ -25,42 +25,67 @@ impl Operation for PushClassPrototype { fn execute(context: &mut Context<'_>) -> JsResult { let superclass = context.vm.pop(); + let class = context.vm.pop(); - if let Some(superclass) = superclass.as_constructor() { + // // Taken from `15.7.14 Runtime Semantics: ClassDefinitionEvaluation`: + // + // + // 8. Else + // f. If superclass is null, then + let (proto_parent, constructor_parent) = if superclass.is_null() { + // i. Let protoParent be null. + // ii. Let constructorParent be %Function.prototype%. + // + // NOTE(HalidOdat): We set constructorParent to None, it is resolved in `SetClassPrototype` opcode. + (JsValue::null(), None) + + // h. Else, + } else if let Some(superclass) = superclass.as_constructor() { + // i. Let protoParent be ? Get(superclass, "prototype"). let proto = superclass.get(PROTOTYPE, context)?; + + // ii. If protoParent is not an Object and protoParent is not null, throw a TypeError exception. if !proto.is_object() && !proto.is_null() { return Err(JsNativeError::typ() .with_message("superclass prototype must be an object or null") .into()); } - let class = context.vm.pop(); - { - let class_object = class.as_object().expect("class must be object"); - class_object.set_prototype(Some(superclass.clone())); - - let mut class_object_mut = class_object.borrow_mut(); - let class_function = class_object_mut - .as_function_mut() - .expect("class must be function object"); - if let FunctionKind::Ordinary { - constructor_kind, .. - } = class_function.kind_mut() - { - *constructor_kind = ConstructorKind::Derived; - } - } + // iii. Let constructorParent be superclass. + (proto, Some(superclass.clone())) - context.vm.push(class); - context.vm.push(proto); - Ok(CompletionType::Normal) - } else if superclass.is_null() { - context.vm.push(JsValue::Null); - Ok(CompletionType::Normal) + // g. Else if IsConstructor(superclass) is false, then } else { - Err(JsNativeError::typ() + // i. Throw a TypeError exception. + return Err(JsNativeError::typ() .with_message("superclass must be a constructor") - .into()) + .into()); + }; + + let class_object = class.as_object().expect("class must be object"); + + if let Some(constructor_parent) = constructor_parent { + class_object.set_prototype(Some(constructor_parent)); } + + let mut class_object_mut = class_object.borrow_mut(); + let class_function = class_object_mut + .as_function_mut() + .expect("class must be function object"); + + // 17. If ClassHeritageopt is present, set F.[[ConstructorKind]] to derived. + if let FunctionKind::Ordinary { + constructor_kind, .. + } = class_function.kind_mut() + { + *constructor_kind = ConstructorKind::Derived; + } + + drop(class_object_mut); + + context.vm.push(class); + context.vm.push(proto_parent); + + Ok(CompletionType::Normal) } } diff --git a/boa_engine/src/vm/opcode/set/class_prototype.rs b/boa_engine/src/vm/opcode/set/class_prototype.rs index d647e7e8ae..7c4b316a35 100644 --- a/boa_engine/src/vm/opcode/set/class_prototype.rs +++ b/boa_engine/src/vm/opcode/set/class_prototype.rs @@ -25,6 +25,7 @@ impl Operation for SetClassPrototype { _ => unreachable!(), }; + // 9.Let proto be OrdinaryObjectCreate(protoParent). let proto = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype,