Skip to content

Commit

Permalink
Implement pseudo-property import.meta (#2956)
Browse files Browse the repository at this point in the history
* Implement pseudo-property `import.meta`

* Apply review
  • Loading branch information
jedel1043 authored May 25, 2023
1 parent 9bbe018 commit 7ae8582
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 92 deletions.
9 changes: 6 additions & 3 deletions boa_ast/src/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ pub enum Expression {
/// The `new.target` pseudo-property expression.
NewTarget,

// TODO: import.meta
/// The `import.meta` pseudo-property expression.
ImportMeta,

/// See [`Assign`].
Assign(Assign),

Expand Down Expand Up @@ -197,6 +199,7 @@ impl Expression {
Self::ImportCall(impc) => impc.to_interned_string(interner),
Self::Optional(opt) => opt.to_interned_string(interner),
Self::NewTarget => "new.target".to_owned(),
Self::ImportMeta => "import.meta".to_owned(),
Self::TaggedTemplate(tag) => tag.to_interned_string(interner),
Self::Assign(assign) => assign.to_interned_string(interner),
Self::Unary(unary) => unary.to_interned_string(interner),
Expand Down Expand Up @@ -316,7 +319,7 @@ impl VisitWith for Expression {
Self::Yield(y) => visitor.visit_yield(y),
Self::Parenthesized(e) => visitor.visit_parenthesized(e),
Self::FormalParameterList(fpl) => visitor.visit_formal_parameter_list(fpl),
Self::This | Self::NewTarget => {
Self::This | Self::NewTarget | Self::ImportMeta => {
// do nothing; can be handled as special case by visitor
ControlFlow::Continue(())
}
Expand Down Expand Up @@ -358,7 +361,7 @@ impl VisitWith for Expression {
Self::Yield(y) => visitor.visit_yield_mut(y),
Self::Parenthesized(e) => visitor.visit_parenthesized_mut(e),
Self::FormalParameterList(fpl) => visitor.visit_formal_parameter_list_mut(fpl),
Self::This | Self::NewTarget => {
Self::This | Self::NewTarget | Self::ImportMeta => {
// do nothing; can be handled as special case by visitor
ControlFlow::Continue(())
}
Expand Down
7 changes: 6 additions & 1 deletion boa_engine/src/bytecompiler/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,12 @@ impl ByteCompiler<'_, '_> {
}
Expression::NewTarget => {
if use_expr {
self.emit_opcode(Opcode::PushNewTarget);
self.emit_opcode(Opcode::NewTarget);
}
}
Expression::ImportMeta => {
if use_expr {
self.emit_opcode(Opcode::ImportMeta);
}
}
Expression::Optional(opt) => {
Expand Down
4 changes: 2 additions & 2 deletions boa_engine/src/module/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ pub trait ModuleLoader {
/// [final]: https://tc39.es/ecma262/#sec-hostfinalizeimportmeta
fn init_import_meta(
&self,
_import_meta: JsObject,
_module: Module,
_import_meta: &JsObject,
_module: &Module,
_context: &mut Context<'_>,
) {
}
Expand Down
5 changes: 5 additions & 0 deletions boa_engine/src/module/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1769,6 +1769,11 @@ impl SourceTextModule {
pub(crate) fn loaded_modules(&self) -> &GcRefCell<FxHashMap<Sym, Module>> {
&self.inner.loaded_modules
}

/// Gets the import meta object of this module.
pub(crate) fn import_meta(&self) -> &GcRefCell<Option<JsObject>> {
&self.inner.import_meta
}
}

/// Abstract operation [`AsyncModuleExecutionFulfilled ( module )`][spec].
Expand Down
3 changes: 2 additions & 1 deletion boa_engine/src/vm/code_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,8 @@ impl CodeBlock {
| Opcode::PushClassField
| Opcode::SuperCallDerived
| Opcode::Await
| Opcode::PushNewTarget
| Opcode::NewTarget
| Opcode::ImportMeta
| Opcode::SuperCallPrepare
| Opcode::CallEvalSpread
| Opcode::CallSpread
Expand Down
3 changes: 2 additions & 1 deletion boa_engine/src/vm/flowgraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,8 @@ impl CodeBlock {
| Opcode::PushClassField
| Opcode::SuperCallDerived
| Opcode::Await
| Opcode::PushNewTarget
| Opcode::NewTarget
| Opcode::ImportMeta
| Opcode::CallEvalSpread
| Opcode::CallSpread
| Opcode::NewSpread
Expand Down
95 changes: 95 additions & 0 deletions boa_engine/src/vm/opcode/meta/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use std::unreachable;

use crate::{
module::ModuleKind,
vm::{opcode::Operation, ActiveRunnable, CompletionType},
Context, JsObject, JsResult, JsValue,
};

/// `NewTarget` implements the Opcode Operation for `Opcode::NewTarget`
///
/// Operation:
/// - Push the current new target to the stack.
#[derive(Debug, Clone, Copy)]
pub(crate) struct NewTarget;

impl Operation for NewTarget {
const NAME: &'static str = "NewTarget";
const INSTRUCTION: &'static str = "INST - NewTarget";

fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let new_target = if let Some(new_target) = context
.vm
.environments
.get_this_environment()
.as_function()
.and_then(|env| env.slots().new_target().cloned())
{
new_target.into()
} else {
JsValue::undefined()
};
context.vm.push(new_target);
Ok(CompletionType::Normal)
}
}

/// `ImportMeta` implements the Opcode Operation for `Opcode::ImportMeta`
///
/// Operation:
/// - Push the current `import.meta` to the stack
#[derive(Debug, Clone, Copy)]
pub(crate) struct ImportMeta;

impl Operation for ImportMeta {
const NAME: &'static str = "ImportMeta";
const INSTRUCTION: &'static str = "INST - ImportMeta";

fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
// Meta Properties
//
// ImportMeta : import . meta
//
// https://tc39.es/ecma262/#sec-meta-properties

// 1. Let module be GetActiveScriptOrModule().

let Some(ActiveRunnable::Module(module)) = context.vm.active_runnable.clone() else {
unreachable!("2. Assert: module is a Source Text Module Record.");
};

let ModuleKind::SourceText(src) = module.kind() else {
unreachable!("2. Assert: module is a Source Text Module Record.");
};

// 3. Let importMeta be module.[[ImportMeta]].
// 4. If importMeta is empty, then
// 5. Else,
// a. Assert: importMeta is an Object.
let import_meta = src
.import_meta()
.borrow_mut()
.get_or_insert_with(|| {
// a. Set importMeta to OrdinaryObjectCreate(null).
let import_meta = JsObject::with_null_proto();

// b. Let importMetaValues be HostGetImportMetaProperties(module).
// c. For each Record { [[Key]], [[Value]] } p of importMetaValues, do
// i. Perform ! CreateDataPropertyOrThrow(importMeta, p.[[Key]], p.[[Value]]).
// d. Perform HostFinalizeImportMeta(importMeta, module).
context
.module_loader()
.init_import_meta(&import_meta, &module, context);

// e. Set module.[[ImportMeta]] to importMeta.
import_meta
})
.clone();

// b. Return importMeta.
// f. Return importMeta.
context.vm.push(import_meta);

Ok(CompletionType::Normal)
}
}
14 changes: 12 additions & 2 deletions boa_engine/src/vm/opcode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod generator;
mod get;
mod iteration;
mod jump;
mod meta;
mod new;
mod nop;
mod pop;
Expand Down Expand Up @@ -62,6 +63,8 @@ pub(crate) use iteration::*;
#[doc(inline)]
pub(crate) use jump::*;
#[doc(inline)]
pub(crate) use meta::*;
#[doc(inline)]
pub(crate) use new::*;
#[doc(inline)]
pub(crate) use nop::*;
Expand Down Expand Up @@ -1602,8 +1605,15 @@ generate_impl! {
///
/// Operands:
///
/// Stack: **=>** new_target
PushNewTarget,
/// Stack: **=>** `new.target`
NewTarget,

/// Push the current `import.meta` to the stack.
///
/// Operands:
///
/// Stack: **=>** `import.meta`
ImportMeta,

/// Pushes `true` to the stack if the top stack value is an object, or `false` otherwise.
///
Expand Down
2 changes: 0 additions & 2 deletions boa_engine/src/vm/opcode/push/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ pub(crate) mod array;
pub(crate) mod class;
pub(crate) mod environment;
pub(crate) mod literal;
pub(crate) mod new_target;
pub(crate) mod numbers;
pub(crate) mod object;

pub(crate) use array::*;
pub(crate) use class::*;
pub(crate) use environment::*;
pub(crate) use literal::*;
pub(crate) use new_target::*;
pub(crate) use numbers::*;
pub(crate) use object::*;

Expand Down
32 changes: 0 additions & 32 deletions boa_engine/src/vm/opcode/push/new_target.rs

This file was deleted.

3 changes: 2 additions & 1 deletion boa_interner/src/sym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,6 @@ static_syms! {
"__proto__",
"name",
"await",
("*default*", DEFAULT_EXPORT)
("*default*", DEFAULT_EXPORT),
"meta"
}
42 changes: 41 additions & 1 deletion boa_parser/src/parser/expression/left_hand_side/member.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,53 @@ where
cursor.set_goal(InputElement::RegExp);

let token = cursor.peek(0, interner).or_abrupt()?;
let position = token.span().start();
let mut lhs = match token.kind() {
TokenKind::Keyword((Keyword::New | Keyword::Super, true)) => {
TokenKind::Keyword((Keyword::New | Keyword::Super | Keyword::Import, true)) => {
return Err(Error::general(
"keyword must not contain escaped characters",
token.span().start(),
));
}
TokenKind::Keyword((Keyword::Import, false)) => {
cursor.advance(interner);

cursor.expect(
TokenKind::Punctuator(Punctuator::Dot),
"import.meta",
interner,
)?;

let token = cursor.next(interner).or_abrupt()?;

match token.kind() {
TokenKind::IdentifierName((Sym::META, ContainsEscapeSequence(ces))) => {
if *ces {
return Err(Error::general(
"`import.meta` cannot contain escaped characters",
token.span().start(),
));
}
}
_ => {
return Err(Error::expected(
["property `meta`".into()],
token.to_string(interner),
token.span(),
"import.meta",
));
}
}

if !cursor.module() {
return Err(Error::general(
"invalid `import.meta` expression outside a module",
position,
));
}

ast::Expression::ImportMeta
}
TokenKind::Keyword((Keyword::New, false)) => {
cursor.advance(interner);

Expand Down
Loading

0 comments on commit 7ae8582

Please sign in to comment.