Skip to content

Commit 33e2055

Browse files
ssrliveCopilot
andcommitted
Implement import.source() dynamic expression for source phase imports
Add parsing, compilation, and runtime support for import.source(specifier) which returns a Promise that resolves with the module namespace object. This follows the same architecture as import() and import.defer(): parser recognizes the syntax, compiler emits a helper call, and the VM dispatches to host_source_import_promise at runtime. - Add Expr::SourceImport AST node and parser handling - Add INTERNAL_SOURCE_IMPORT_HELPER compiler constant - Add host_source_import_promise VM method (loads module, resolves with namespace) - Reject new import.source(...) with SyntaxError - Update feature probes to verify module loading works - Remove dead code: parse_simple_module_namespace, resolve_import_specifier_path Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d936092 commit 33e2055

8 files changed

Lines changed: 152 additions & 137 deletions

File tree

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,15 @@
11
// module
2-
2+
// Probe: source-phase-imports-module-source
3+
// Verify import.source(specifier) loads the target module and reads its exports.
34
try {
4-
var emitted = false;
5-
function emitOK() {
6-
if (!emitted) {
7-
emitted = true;
8-
console.log("OK");
9-
}
10-
}
11-
12-
// Synchronous auxiliary check for call-shape support.
135
var result = import.source("./source-phase-imports-target.js");
14-
if (result && (typeof result === "object" || typeof result === "function")) {
15-
emitOK();
16-
}
17-
18-
// Keep asynchronous validation: resolved value should be object-like.
196
if (result && typeof result.then === "function") {
20-
result.then(function(modSource) {
21-
if (modSource && typeof modSource === "object") {
22-
emitOK();
7+
result.then(function(mod) {
8+
if (mod && mod.ok === true) {
9+
console.log("OK");
2310
}
24-
}).catch(function() {
25-
// suppress
26-
});
11+
}).catch(function() {});
2712
}
2813
} catch (e) {
29-
// suppress
14+
// syntax not supported
3015
}
Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,15 @@
1+
// module
2+
// Probe: source-phase-imports
3+
// Verify import.source(specifier) loads the target module and reads its exports.
14
try {
2-
var emitted = false;
3-
function emitOK() {
4-
if (!emitted) {
5-
emitted = true;
6-
console.log("OK");
7-
}
8-
}
9-
105
var result = import.source("./source-phase-imports-target.js");
11-
12-
// Synchronous auxiliary check: if the call shape is accepted
13-
// (returns object/function), emit an immediately observable signal for the runner.
14-
if (result && (typeof result === "object" || typeof result === "function")) {
15-
emitOK();
16-
}
17-
18-
// Keep the original asynchronous probe: validate final namespace content.
196
if (result && typeof result.then === "function") {
20-
result
21-
.then(function(mod) {
22-
if (mod && mod.ok === true) {
23-
emitOK();
24-
}
25-
})
26-
.catch(function() {
27-
// suppress
28-
});
7+
result.then(function(mod) {
8+
if (mod && mod.ok === true) {
9+
console.log("OK");
10+
}
11+
}).catch(function() {});
2912
}
3013
} catch (e) {
31-
// suppress
14+
// syntax not supported
3215
}

src/core/compiler.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub(crate) const INTERNAL_FOROF_HELPER: &str = "__forOfValues internal";
1212
pub(crate) const INTERNAL_GETITER_HELPER: &str = "__getIterator internal";
1313
pub(crate) const INTERNAL_DYNAMIC_IMPORT_HELPER: &str = "__dynamicImport internal";
1414
pub(crate) const INTERNAL_DEFERRED_IMPORT_HELPER: &str = "__deferredImport internal";
15+
pub(crate) const INTERNAL_SOURCE_IMPORT_HELPER: &str = "__sourceImport internal";
1516

1617
#[derive(Default)]
1718
pub struct Compiler<'gc> {
@@ -5122,6 +5123,11 @@ impl<'gc> Compiler<'gc> {
51225123
self.compile_expr(module_expr)?;
51235124
self.emit_call_opcode(1, 0);
51245125
}
5126+
Expr::SourceImport(module_expr) => {
5127+
self.emit_helper_get(INTERNAL_SOURCE_IMPORT_HELPER);
5128+
self.compile_expr(module_expr)?;
5129+
self.emit_call_opcode(1, 0);
5130+
}
51255131
Expr::This => {
51265132
self.chunk.write_opcode(Opcode::GetThis);
51275133
}
@@ -9684,7 +9690,7 @@ impl<'gc> Compiler<'gc> {
96849690
Self::expr_references_any_identifier(spec, names)
96859691
|| attrs.as_ref().is_some_and(|a| Self::expr_references_any_identifier(a, names))
96869692
}
9687-
Expr::DeferredImport(spec) => Self::expr_references_any_identifier(spec, names),
9693+
Expr::DeferredImport(spec) | Expr::SourceImport(spec) => Self::expr_references_any_identifier(spec, names),
96889694
Expr::Function(..)
96899695
| Expr::GeneratorFunction(..)
96909696
| Expr::AsyncFunction(..)

src/core/gc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub fn trace_expr<'gc, T: GcTrace<'gc>>(context: &mut T, expr: &Expr) {
5656
trace_expr(context, b);
5757
}
5858
}
59-
Expr::DeferredImport(a) => {
59+
Expr::DeferredImport(a) | Expr::SourceImport(a) => {
6060
trace_expr(context, a);
6161
}
6262
Expr::Function(_, _, body, _) => {

src/core/mod.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ fn expr_contains_await(expr: &Expr) -> bool {
500500
Expr::DynamicImport(specifier, options) => {
501501
expr_contains_await(specifier) || options.as_ref().map(|expr| expr_contains_await(expr)).unwrap_or(false)
502502
}
503-
Expr::DeferredImport(specifier) => expr_contains_await(specifier),
503+
Expr::DeferredImport(specifier) | Expr::SourceImport(specifier) => expr_contains_await(specifier),
504504
Expr::ArrowFunction(_, body) | Expr::AsyncArrowFunction(_, body) => body.iter().any(statement_contains_await),
505505
Expr::Function(..)
506506
| Expr::GeneratorFunction(..)
@@ -590,7 +590,7 @@ fn expr_contains_yield(expr: &Expr) -> bool {
590590
Expr::DynamicImport(specifier, options) => {
591591
expr_contains_yield(specifier) || options.as_ref().map(|expr| expr_contains_yield(expr)).unwrap_or(false)
592592
}
593-
Expr::DeferredImport(specifier) => expr_contains_yield(specifier),
593+
Expr::DeferredImport(specifier) | Expr::SourceImport(specifier) => expr_contains_yield(specifier),
594594
Expr::ArrowFunction(_, body) | Expr::AsyncArrowFunction(_, body) => body.iter().any(statement_contains_yield),
595595
Expr::Function(..)
596596
| Expr::GeneratorFunction(..)
@@ -913,7 +913,7 @@ fn expr_uses_identifier(expr: &Expr, ident: &str) -> bool {
913913
Expr::DynamicImport(specifier, options) => {
914914
expr_uses_identifier(specifier, ident) || options.as_ref().map(|expr| expr_uses_identifier(expr, ident)).unwrap_or(false)
915915
}
916-
Expr::DeferredImport(specifier) => expr_uses_identifier(specifier, ident),
916+
Expr::DeferredImport(specifier) | Expr::SourceImport(specifier) => expr_uses_identifier(specifier, ident),
917917
Expr::ArrowFunction(params, body) | Expr::AsyncArrowFunction(params, body) => {
918918
params_use_identifier(params, ident) || statement_list_uses_identifier(body, ident)
919919
}
@@ -1159,7 +1159,7 @@ fn expr_contains_arrow_params_with_await(expr: &Expr) -> bool {
11591159
.map(|expr| expr_contains_arrow_params_with_await(expr))
11601160
.unwrap_or(false)
11611161
}
1162-
Expr::DeferredImport(specifier) => expr_contains_arrow_params_with_await(specifier),
1162+
Expr::DeferredImport(specifier) | Expr::SourceImport(specifier) => expr_contains_arrow_params_with_await(specifier),
11631163
Expr::Function(..)
11641164
| Expr::GeneratorFunction(..)
11651165
| Expr::AsyncFunction(..)
@@ -1430,7 +1430,7 @@ fn field_initializer_has_direct_super_call(expr: &Expr) -> bool {
14301430
Expr::DynamicImport(e, opts) => {
14311431
field_initializer_has_direct_super_call(e) || opts.as_ref().is_some_and(|o| field_initializer_has_direct_super_call(o))
14321432
}
1433-
Expr::DeferredImport(e) => field_initializer_has_direct_super_call(e),
1433+
Expr::DeferredImport(e) | Expr::SourceImport(e) => field_initializer_has_direct_super_call(e),
14341434
Expr::Yield(Some(e)) => field_initializer_has_direct_super_call(e),
14351435
Expr::Call(callee, args) | Expr::OptionalCall(callee, args) | Expr::New(callee, args) => {
14361436
field_initializer_has_direct_super_call(callee) || args.iter().any(field_initializer_has_direct_super_call)
@@ -1893,7 +1893,7 @@ fn validate_expression(expr: &Expr) -> Result<(), JSError> {
18931893
validate_expression(options)?;
18941894
}
18951895
}
1896-
Expr::DeferredImport(specifier) => {
1896+
Expr::DeferredImport(specifier) | Expr::SourceImport(specifier) => {
18971897
validate_expression(specifier)?;
18981898
}
18991899
Expr::Class(class_def) => {
@@ -2700,7 +2700,7 @@ pub(crate) fn module_has_top_level_await(statements: &[Statement]) -> bool {
27002700
Expr::DynamicImport(spec, attrs) => {
27012701
expr_has_top_level_await(spec) || attrs.as_ref().is_some_and(|attrs| expr_has_top_level_await(attrs))
27022702
}
2703-
Expr::DeferredImport(spec) => expr_has_top_level_await(spec),
2703+
Expr::DeferredImport(spec) | Expr::SourceImport(spec) => expr_has_top_level_await(spec),
27042704
Expr::Class(class_def) => {
27052705
class_def.extends.as_ref().is_some_and(expr_has_top_level_await)
27062706
|| class_def.members.iter().any(|member| match member {

src/core/parser.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5445,7 +5445,12 @@ fn parse_primary(tokens: &[TokenData], index: &mut usize, allow_call: bool) -> R
54455445
// `new import(...)` is a SyntaxError, but `new (import(...))` is valid
54465446
let bare_import = *index < tokens.len() && matches!(tokens[*index].token, Token::Import);
54475447
let constructor = parse_primary(tokens, index, false)?;
5448-
if bare_import && matches!(constructor, Expr::DynamicImport(..) | Expr::DeferredImport(..)) {
5448+
if bare_import
5449+
&& matches!(
5450+
constructor,
5451+
Expr::DynamicImport(..) | Expr::DeferredImport(..) | Expr::SourceImport(..)
5452+
)
5453+
{
54495454
return Err(raise_parse_error!("Cannot use 'new' with import()"));
54505455
}
54515456
let args = if *index < tokens.len() && matches!(tokens[*index].token, Token::LParen) {
@@ -5674,9 +5679,33 @@ fn parse_primary(tokens: &[TokenData], index: &mut usize, allow_call: bool) -> R
56745679
}
56755680
*index += 1;
56765681
Expr::DeferredImport(Box::new(arg))
5682+
} else if *index < tokens.len()
5683+
&& let Token::Identifier(id) = &tokens[*index].token
5684+
&& id == "source"
5685+
{
5686+
*index += 1;
5687+
if *index >= tokens.len() || !matches!(tokens[*index].token, Token::LParen) {
5688+
return Err(raise_parse_error!("Expected '(' after 'import.source'"));
5689+
}
5690+
*index += 1;
5691+
if *index < tokens.len() && matches!(tokens[*index].token, Token::RParen) {
5692+
return Err(raise_parse_error!("import.source() requires a specifier argument"));
5693+
}
5694+
if *index < tokens.len() && matches!(tokens[*index].token, Token::Spread) {
5695+
return Err(raise_parse_error!("import.source() does not accept a rest parameter"));
5696+
}
5697+
let arg = with_allowed_in(|| parse_assignment(tokens, index))?;
5698+
if *index < tokens.len() && matches!(tokens[*index].token, Token::Comma) {
5699+
*index += 1;
5700+
}
5701+
if *index >= tokens.len() || !matches!(tokens[*index].token, Token::RParen) {
5702+
return Err(raise_parse_error!("Expected ')' after import.source(...)"));
5703+
}
5704+
*index += 1;
5705+
Expr::SourceImport(Box::new(arg))
56775706
} else {
56785707
return Err(raise_parse_error!(
5679-
"Only 'import.meta' and 'import.defer' are valid after 'import.'"
5708+
"Only 'import.meta', 'import.defer' and 'import.source' are valid after 'import.'"
56805709
));
56815710
}
56825711
} else {

src/core/statement.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ pub enum Expr {
213213
Call(Box<Expr>, Vec<Expr>),
214214
DynamicImport(Box<Expr>, Option<Box<Expr>>),
215215
DeferredImport(Box<Expr>),
216+
SourceImport(Box<Expr>),
216217
ValuePlaceholder,
217218
}
218219

@@ -694,6 +695,7 @@ fn scan_expr(expr: &Expr, mask: u8, found: &mut u8) {
694695
| Expr::Setter(e)
695696
| Expr::YieldStar(e)
696697
| Expr::DeferredImport(e)
698+
| Expr::SourceImport(e)
697699
| Expr::DynamicImport(e, None) => {
698700
scan_expr(e, mask, found);
699701
}

0 commit comments

Comments
 (0)