Browse Source

Implement pseudo-property `import.meta` (#2956)

* Implement pseudo-property `import.meta`

* Apply review
pull/2962/head
José Julián Espina 1 year ago committed by GitHub
parent
commit
7ae858204b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      boa_ast/src/expression/mod.rs
  2. 7
      boa_engine/src/bytecompiler/expression/mod.rs
  3. 4
      boa_engine/src/module/mod.rs
  4. 5
      boa_engine/src/module/source.rs
  5. 3
      boa_engine/src/vm/code_block.rs
  6. 3
      boa_engine/src/vm/flowgraph/mod.rs
  7. 95
      boa_engine/src/vm/opcode/meta/mod.rs
  8. 14
      boa_engine/src/vm/opcode/mod.rs
  9. 2
      boa_engine/src/vm/opcode/push/mod.rs
  10. 32
      boa_engine/src/vm/opcode/push/new_target.rs
  11. 3
      boa_interner/src/sym.rs
  12. 42
      boa_parser/src/parser/expression/left_hand_side/member.rs
  13. 92
      boa_parser/src/parser/expression/left_hand_side/mod.rs

9
boa_ast/src/expression/mod.rs

@ -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),
@ -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),
@ -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(())
}
@ -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(())
}

7
boa_engine/src/bytecompiler/expression/mod.rs

@ -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) => {

4
boa_engine/src/module/mod.rs

@ -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<'_>,
) {
}

5
boa_engine/src/module/source.rs

@ -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].

3
boa_engine/src/vm/code_block.rs

@ -500,7 +500,8 @@ impl CodeBlock {
| Opcode::PushClassField
| Opcode::SuperCallDerived
| Opcode::Await
| Opcode::PushNewTarget
| Opcode::NewTarget
| Opcode::ImportMeta
| Opcode::SuperCallPrepare
| Opcode::CallEvalSpread
| Opcode::CallSpread

3
boa_engine/src/vm/flowgraph/mod.rs

@ -584,7 +584,8 @@ impl CodeBlock {
| Opcode::PushClassField
| Opcode::SuperCallDerived
| Opcode::Await
| Opcode::PushNewTarget
| Opcode::NewTarget
| Opcode::ImportMeta
| Opcode::CallEvalSpread
| Opcode::CallSpread
| Opcode::NewSpread

95
boa_engine/src/vm/opcode/meta/mod.rs

@ -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
boa_engine/src/vm/opcode/mod.rs

@ -18,6 +18,7 @@ mod generator;
mod get;
mod iteration;
mod jump;
mod meta;
mod new;
mod nop;
mod pop;
@ -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::*;
@ -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.
///

2
boa_engine/src/vm/opcode/push/mod.rs

@ -7,7 +7,6 @@ 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;
@ -15,7 +14,6 @@ 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::*;

32
boa_engine/src/vm/opcode/push/new_target.rs

@ -1,32 +0,0 @@
use crate::{
vm::{opcode::Operation, CompletionType},
Context, JsResult, JsValue,
};
/// `PushNewTarget` implements the Opcode Operation for `Opcode::PushNewTarget`
///
/// Operation:
/// - Push the current new target to the stack.
#[derive(Debug, Clone, Copy)]
pub(crate) struct PushNewTarget;
impl Operation for PushNewTarget {
const NAME: &'static str = "PushNewTarget";
const INSTRUCTION: &'static str = "INST - PushNewTarget";
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)
}
}

3
boa_interner/src/sym.rs

@ -148,5 +148,6 @@ static_syms! {
"__proto__",
"name",
"await",
("*default*", DEFAULT_EXPORT)
("*default*", DEFAULT_EXPORT),
"meta"
}

42
boa_parser/src/parser/expression/left_hand_side/member.rs

@ -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);

92
boa_parser/src/parser/expression/left_hand_side/mod.rs

@ -28,7 +28,7 @@ use crate::{
},
AssignmentExpression,
},
AllowAwait, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser,
AllowAwait, AllowYield, Cursor, ParseResult, TokenParser,
},
Error,
};
@ -78,24 +78,36 @@ where
type Output = Expression;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
/// Checks if we need to parse a super call expression `super()`.
/// Checks if we need to parse a keyword call expression `keyword()`.
///
/// It first checks if the next token is `super`, and if it is, it checks if the second next
/// It first checks if the next token is `keyword`, and if it is, it checks if the second next
/// token is the open parenthesis (`(`) punctuator.
///
/// This is needed because the `if let` chain is very complex, and putting it inline in the
/// initialization of `lhs` would make it very hard to return an expression over all
/// possible branches of the `if let`s. Instead, we extract the check into its own function,
/// then use it inside the condition of a simple `if ... else` expression.
fn is_super_call<R: Read>(
fn is_keyword_call<R: Read>(
keyword: Keyword,
cursor: &mut Cursor<R>,
interner: &mut Interner,
) -> ParseResult<bool> {
if let Some(next) = cursor.peek(0, interner)? {
if let TokenKind::Keyword((Keyword::Super, _)) = next.kind() {
if let Some(next) = cursor.peek(1, interner)? {
if next.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) {
return Ok(true);
if let TokenKind::Keyword((kw, escaped)) = next.kind() {
if kw == &keyword {
if *escaped {
return Err(Error::general(
format!(
"keyword `{}` cannot contain escaped characters",
kw.as_str().0
),
next.span().start(),
));
}
if let Some(next) = cursor.peek(1, interner)? {
if next.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) {
return Ok(true);
}
}
}
}
@ -107,54 +119,42 @@ where
cursor.set_goal(InputElement::TemplateTail);
let mut lhs = if is_super_call(cursor, interner)? {
let mut lhs = if is_keyword_call(Keyword::Super, cursor, interner)? {
cursor.advance(interner);
let args =
Arguments::new(self.allow_yield, self.allow_await).parse(cursor, interner)?;
SuperCall::new(args).into()
} else {
let next = cursor.peek(0, interner).or_abrupt()?;
if let TokenKind::Keyword((Keyword::Import, escaped)) = next.kind() {
if *escaped {
return Err(Error::general(
"keyword `import` must not contain escaped characters",
next.span().start(),
));
}
cursor.advance(interner);
cursor.expect(
TokenKind::Punctuator(Punctuator::OpenParen),
"import call",
interner,
)?;
} else if is_keyword_call(Keyword::Import, cursor, interner)? {
// `import`
cursor.advance(interner);
// `(`
cursor.advance(interner);
let arg = AssignmentExpression::new(None, true, self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
let arg = AssignmentExpression::new(None, true, self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseParen),
"import call",
interner,
)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseParen),
"import call",
interner,
)?;
CallExpressionTail::new(
self.allow_yield,
self.allow_await,
ImportCall::new(arg).into(),
)
.parse(cursor, interner)?
} else {
let mut member =
MemberExpression::new(self.name, self.allow_yield, self.allow_await)
CallExpressionTail::new(
self.allow_yield,
self.allow_await,
ImportCall::new(arg).into(),
)
.parse(cursor, interner)?
} else {
let mut member = MemberExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner)?;
if let Some(tok) = cursor.peek(0, interner)? {
if tok.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) {
member = CallExpression::new(self.allow_yield, self.allow_await, member)
.parse(cursor, interner)?;
if let Some(tok) = cursor.peek(0, interner)? {
if tok.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) {
member = CallExpression::new(self.allow_yield, self.allow_await, member)
.parse(cursor, interner)?;
}
}
member
}
member
};
if let Some(tok) = cursor.peek(0, interner)? {

Loading…
Cancel
Save