-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dynamic Module Import #2913
Dynamic Module Import #2913
Conversation
@Microsoft/chakra-es @Microsoft/chakra-developers, please help code review. Thanks! |
lib/Jsrt/ChakraCore.h
Outdated
@@ -70,6 +71,21 @@ typedef JsErrorCode(CHAKRA_CALLBACK * FetchImportedModuleCallBack)(_In_ JsModule | |||
/// <returns> | |||
/// true if the operation succeeded, false otherwise. | |||
/// </returns> | |||
typedef JsErrorCode(CHAKRA_CALLBACK * FetchImportedModuleFromScriptCallBack)(_In_ DWORD_PTR dwReferencingSourceContext, _In_ JsValueRef specifier, _Outptr_result_maybenull_ JsModuleRecord* dependentModuleRecord); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DWORD_PTR [](start = 82, length = 9)
DWORD_PTR [](start = 82, length = 9)
Use JsSourceContext instead of DWORD_PTR #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
`testDynamicImport( | ||
()=>{ | ||
var getName = ()=>{ return 'ModuleSimpleExport'; }; | ||
return import( getName() + '.js'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import [](start = 31, length = 6)
What if a non-string is passed to import? like import() or import({}) etc. Can we add test cases covering error scenarios? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added new test dynamic-module-import-specifier.js.
More tests to come.
In reply to: 114840008 [](ancestors = 114840008)
{ | ||
Js::JavascriptError * error = scriptContext->GetLibrary()->CreateURIError(); | ||
Var value = JavascriptString::NewCopySz(moduleName, scriptContext); | ||
JavascriptOperators::OP_SetProperty(error, PropertyIds::message, value, scriptContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OP_SetProperty [](start = 37, length = 14)
Should you use JavascriptError::SetErrorMessageProperties instead? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or JavascriptError::SetErrorMessage
#Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
else | ||
{ | ||
Assert(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert [](start = 12, length = 6)
nit: Using AssertMsg with a message would be more useful #WontFix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. This line is removed along with stack walker call though.
In reply to: 114845379 [](ancestors = 114845379)
this->codeAddr = this->GetReturnAddress(); | ||
this->frame = (void **)this->frame[0]; | ||
if (frame != nullptr) | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which situation needs this and why only x86? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only with nested enter_script, which I have removed.
In reply to: 114913205 [](ancestors = 114913205)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks generally good. Can you look into storing the promise object itself in the module record instead of the promise capability? I think this makes sense to do and is simpler and more efficient.
@@ -58,7 +58,8 @@ void ExceptionCheck::SetHandledExceptionType(ExceptionType e) | |||
#if DBG | |||
if(!(e == ExceptionType_None || | |||
e == ExceptionType_DisableCheck || | |||
!JsUtil::ExternalApi::IsScriptActiveOnCurrentThreadContext() || | |||
(e & ExceptionType_OutOfMemory) == ExceptionType_OutOfMemory || // InitializeModuleRecord handles OOM during dynamic import | |||
!JsUtil::ExternalApi::IsScriptActiveOnCurrentThreadContext() || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: one space missing here. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/Parser/Parse.cpp
Outdated
{ | ||
Assert(m_scriptContext->GetConfig()->IsES6ModuleEnabled()); | ||
Assert(m_token.tk == tkIMPORT); | ||
|
||
m_pscan->Scan(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we peek at the next token instead of scanning here? We don't know if this is a dynamic import or a static import at this point. If we scan and find that this is a static import at an invalid place (inside a function or outside of module code etc), won't we error on the wrong token? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{ | ||
Js::JavascriptError * error = scriptContext->GetLibrary()->CreateURIError(); | ||
Var value = JavascriptString::NewCopySz(moduleName, scriptContext); | ||
JavascriptOperators::OP_SetProperty(error, PropertyIds::message, value, scriptContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or JavascriptError::SetErrorMessage
#Resolved
@@ -61,7 +61,7 @@ namespace Js | |||
// in practice the modulerecord lifetime should be the same as the scriptcontext so it could be retrieved for the same | |||
// site. Host might hold a reference to the module as well after initializing the module. | |||
// In our implementation, we'll use the moduleId in bytecode to identify the module. | |||
childModuleRecord->moduleId = scriptContext->GetLibrary()->EnsureModuleRecordList()->Add(childModuleRecord); | |||
childModuleRecord->moduleId = scriptContext->GetLibrary()->EnsureModuleRecordList()->Add(childModuleRecord) + 1; // 0 == kmodGlobal |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you move the comment about kmodGlobal
into the comment above the call? Maybe expand it a little to explain that module record moduleId should be > 0. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found a better way to check module code so this shift is no longer needed.
In reply to: 114905856 [](ancestors = 114905856)
@@ -230,15 +259,15 @@ namespace Js | |||
OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("\t>NotifyParentsAsNeeded\n")); | |||
NotifyParentsAsNeeded(); | |||
|
|||
if (!WasDeclarationInitialized() && isRootModule) | |||
if (this->promiseCapability != nullptr || (!WasDeclarationInitialized() && isRootModule)) | |||
{ | |||
// TODO: move this as a promise call? if parser is called from a different thread |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this PR address the TODO? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR doesn't enable the case where parser is called from a different thread. Therefore I leave this TODO here for future enhancement.
In reply to: 114906209 [](ancestors = 114906209)
promiseCapability = JavascriptPromise::NewPromiseCapability(scriptContext->GetLibrary()->GetPromiseConstructor(), scriptContext); | ||
} | ||
|
||
Var resolveOrRejectVar = toResolve ? promiseCapability->GetResolve() : promiseCapability->GetReject(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So toResolve
indicates the promise should be resolved instead of rejected? Nitpicky, but I think I would prefer this to be named isResolve
, isReject
, success
, or something along those lines. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (promiseCapability == nullptr) | ||
{ | ||
BEGIN_TRANSLATE_TO_HRESULT(static_cast<ExceptionType>(ExceptionType_OutOfMemory | ExceptionType_StackOverflow)); | ||
promiseCapability = JavascriptPromise::NewPromiseCapability(scriptContext->GetLibrary()->GetPromiseConstructor(), scriptContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the spec call-out that we should be storing the PromiseCapabilityRecord
here or is this more of an implementation detail? I would suspect it should be simpler to store a JavascriptPromise*
here and call JavascriptPromise::Resolve()
or JavascriptPromise::Reject()
on it when it's time to resolve/reject it.
Looks like we're storing promiseCapability
here to avoid casting the promise into a JavascriptPromise*
because JavascriptPromiseCapability::promise
is a Var
? I think that cast should be safe to do since we pass %Promise%
to JavascriptPromise::NewPromiseCapability
which is guaranteed to return something that's a JavascriptPromise*
because the return object is constructed via JavascriptLibrary::CreatePromise
and we aren't doing a super call. Actually might be even simpler than that since we don't even need to call an executor function for this promise, right? So we could just call library->CreatePromise()
and pass the result to JavascriptPromise::InitializePromise
.
Might make sense to add a helper to JavascriptPromise
itself
JavascriptPromise* JavascriptPromise::CreateEnginePromise(ScriptContext* scriptContext)
{
// Unused
JavascriptPromiseResolveOrRejectFunction* resolve;
JavascriptPromiseResolveOrRejectFunction* reject;
JavascriptPromise* promise = library->CreatePromise();
InitializePromise(promise, &resolve, &reject, scriptContext);
return promise;
}
I think it would be simpler to resolve the promise at least if we stored the promise itself instead of the capability record. We would also avoid constructing a bunch of objects and making a bunch of calls by constructing the promise as the above helper. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/Parser/Parse.cpp
Outdated
if (m_token.tk == tkLParen) | ||
{ | ||
ParseNodePtr specifier = ParseTerm<buildAST>(); | ||
return CreateCallNode(knopCall, CreateNodeWithScanner<knopImport>(), specifier); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CreateCallNode [](start = 15, length = 14)
Since we parse this like a call, I would like to make sure we have sufficient coverage for parsing cases where import() could be mistaken for a function call, e.g. with multiple parameters, spread, etc. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good point #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added dynamic-module-import-specifier.js
More to come.
In reply to: 114915006 [](ancestors = 114915006)
else if (pnode->sxCall.pnodeTarget->nop == knopImport) | ||
{ | ||
ParseNodePtr args = pnode->sxCall.pnodeArgs; | ||
Emit(args, byteCodeGenerator, funcInfo, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
args [](start = 17, length = 4)
Since we're piggybacking off a call node, perhaps we want to assert some restrictions e.g. single parameter #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -58,7 +58,8 @@ void ExceptionCheck::SetHandledExceptionType(ExceptionType e) | |||
#if DBG | |||
if(!(e == ExceptionType_None || | |||
e == ExceptionType_DisableCheck || | |||
!JsUtil::ExternalApi::IsScriptActiveOnCurrentThreadContext() || | |||
(e & ExceptionType_OutOfMemory) == ExceptionType_OutOfMemory || // InitializeModuleRecord handles OOM during dynamic import |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InitializeModuleRecord [](start = 77, length = 22)
Why add this? The point here is that if you are in script, you should be throwing the JS exception object instead of OutOfMemory. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (nullptr == this->childrenModuleSet) | ||
{ | ||
ArenaAllocator* allocator = scriptContext->GeneralAllocator(); | ||
this->childrenModuleSet = (ChildModuleRecordSet*)AllocatorNew(ArenaAllocator, allocator, ChildModuleRecordSet, allocator); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
childrenModuleSet [](start = 18, length = 17)
Use Anew? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SourceTextModuleRecord *moduleRecord = nullptr; | ||
|
||
JavascriptFunction* pfuncCaller; | ||
if (JavascriptStackWalker::GetCaller(&pfuncCaller, scriptContext) && pfuncCaller && pfuncCaller->IsScriptFunction()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to use the stack walker here? Didn't the interpreter just called this OP_ImportCall and know what the function is already? Can't the interpreter pass it in? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated interpreter and lowerer code to pass in function and removed stack walker call.
In reply to: 114930380 [](ancestors = 114930380)
@@ -63,7 +63,7 @@ namespace Js | |||
// not run and we might be in an inconsistent state | |||
|
|||
// Put any code that may raise an exception in OnScriptStart | |||
scriptContext->GetThreadContext()->EnterScriptStart(entryExitRecord, doCleanup); | |||
scriptContext->GetThreadContext()->EnterScriptStart(entryExitRecord, doCleanup, &this->isScriptActive); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are we trying to allow nested EnterScript without LeaveScript? Why? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switched to using fscrIsModuleCode to detect ES6 module in ParseableFuncitonInfo::IsES6ModuleCode(). In reply to: 299373242 [](ancestors = 299373242) Refers to: lib/Runtime/Language/SourceTextModuleRecord.cpp:122 in ea66233. [](commit_id = ea66233, deletion_comment = False) |
@dotnet-bot |
lib/Parser/Parse.cpp
Outdated
ParseNodePtr specifier = ParseExpr<buildAST>(koplCma, nullptr, /* fAllowIn */FALSE, /* fAllowEllipsis */FALSE); | ||
if (m_token.tk != tkRParen) | ||
{ | ||
Error(ERRsyntax); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: proper error would be useful. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// we are not doing any translation, just treat the specifier as fileName. | ||
// While this call will come back directly from runtime script or module code, the additional | ||
// task can be scheduled asynchronously that executed later. | ||
JsErrorCode WScriptJsrt::FetchImportedModuleFromScript(_In_ JsSourceContext dwReferencingSourceContext, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FetchImportedModuleFromScript [](start = 25, length = 29)
wondering if this function can be refactored with above function. It seems most of the code is shared. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/Jsrt/Core/JsrtContextCore.cpp
Outdated
|
||
HRESULT hr = NOERROR; | ||
Js::JavascriptString* specifierVar = nullptr; | ||
specifierVar = Js::JavascriptString::NewCopySz(specifier, GetScriptContext()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: combine these two lines? #WontFix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/Jsrt/Core/JsrtContextCore.cpp
Outdated
Js::JavascriptString* specifierVar = nullptr; | ||
specifierVar = Js::JavascriptString::NewCopySz(specifier, GetScriptContext()); | ||
|
||
if (FAILED(hr)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this redundant check? where the hr is changed in above code? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/Jsrt/Core/JsrtContextCore.cpp
Outdated
return hr; | ||
} | ||
|
||
JsModuleRecord dependentRecord = JS_INVALID_REFERENCE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there is not much difference between this function and above function (FetchImportedModule). Did you consider creating a lambda and just stub relevant part in that? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
body: function () { | ||
let functionBody = | ||
` | ||
assert.doesNotThrow(function () { eval("import(undefined)"); }, "undefined"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aren't these tests repeated from import-specifier.js file? #WontFix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are not exactly the same. Import-specifier.js tests ToString operation applied on specifier. This one tests parser.
In reply to: 115364519 [](ancestors = 115364519)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see this file is added to the rlexe.xml, am I missing something?
In reply to: 115372781 [](ancestors = 115372781,115364519)
return import(${specifier}); | ||
}, | ||
(v)=>{ | ||
assert.isTrue(false, 'Expected: promise rejected; actual: promise resolved: ' + '${message}'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: change this to assert.fail() . #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}, | ||
]; | ||
|
||
testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a test case which covers the nested dynamic imports ? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nest import syntax is test in dynamic-module-import-specifier.js
In reply to: 115367185 [](ancestors = 115367185)
{ | ||
Assert(m_scriptContext->GetConfig()->IsES6ModuleEnabled()); | ||
Assert(m_token.tk == tkIMPORT); | ||
|
||
RestorePoint parsedImport; | ||
m_pscan->Capture(&parsedImport); | ||
m_pscan->Scan(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to do a rewind? We are just going to scan again a few lines down anyway. #WontFix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just to make sure col# reported by error msg is not off due to the premature scan.
In reply to: 115371147 [](ancestors = 115371147)
lib/Jsrt/Core/JsrtContextCore.cpp
Outdated
@@ -110,7 +129,7 @@ HRESULT ChakraCoreHostScriptContext::FetchImportedModule(Js::ModuleRecordBase* r | |||
JsModuleRecord dependentRecord = JS_INVALID_REFERENCE; | |||
{ | |||
AUTO_NO_EXCEPTION_REGION; | |||
JsErrorCode errorCode = fetchImportedModuleCallback(referencingModule, specifierVar, &dependentRecord); | |||
JsErrorCode errorCode = fetch(specifierVar, &dependentRecord);//fetchImportedModuleCallback(referencingModule, specifierVar, &dependentRecord); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick : comments can be removed #Resolved
return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, errorObject, scriptContext); | ||
} | ||
|
||
Throw::InternalError(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what happens if
import( { toString : function() {
throw 1;
}});
#Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the reasonable thing to expect is to reject the promise with 1.
Here spec says:
IfAbruptRejectPromise(specifierString, promiseCapability).
which is questionable because specifierString will be nullptr.
I will seek clarification separately.
In reply to: 115379478 [](ancestors = 115379478)
|
||
// Walk the call stack if caller function is neither module code nor having host source context | ||
DWORD_PTR dwReferencingSourceContext = dwReferencingSourceContext = parentFuncBody->GetFunctionInfo()->GetFunctionProxy()->GetSourceContextInfo()->dwHostSourceContext; | ||
if (!parentFuncBody->IsES6ModuleCode() && dwReferencingSourceContext == Js::Constants::NoHostSourceContext) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if dwReferencingSourceContext == Js::Constants::NoHostSourceContext
could you not use callerUtf8SourceInfo (defined in the utf8sourceinfo)? Confirm with @agarwal.sandeep@microsoft.com that this is populated in the non-debug scenario as well. #WontFix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
callerUtf8SourceInfo is populated in non-debug scenario as well. You will still need to walk the chain to get to first non evaled parent. #ByDesign
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it works for a single eval case, but fails for nested eval.
In reply to: 115380766 [](ancestors = 115380766)
JavascriptError::SetErrorMessageProperties(error, hr, moduleName, scriptContext); | ||
return SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, error, scriptContext); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would be nice if this code moved to SoureTextModuleRecord. I don't think javascriptoperator.cpp needs to know any of this. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (this->promise != nullptr) | ||
{ | ||
SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->GetErrorObject(), this->GetScriptContext(), this); | ||
this->SetPromise(nullptr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SetPromise [](start = 22, length = 10)
would it be wise to reset the promise to null first and do the ResolveOrReject.. ? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact ResolveOrRejectDynamicImportPromise() takes care of this already. Removing this line and in similar cases
In reply to: 115382160 [](ancestors = 115382160)
@@ -175,6 +175,12 @@ namespace Js | |||
} | |||
|
|||
OUTPUT_TRACE_DEBUGONLY(Js::ModulePhase, _u("\t>NotifyParentAsNeeded\n")); | |||
if (this->promise != nullptr) | |||
{ | |||
SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->GetErrorObject(), this->GetScriptContext(), this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this->GetScriptContext() [](start = 107, length = 24)
nitpick : use the local scriptContext (also you can use the errorObject as well) #Resolved
|
||
if (this->errorObject != nullptr) | ||
{ | ||
SourceTextModuleRecord::ResolveOrRejectDynamicImportPromise(false, this->GetErrorObject(), scriptContext, this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scriptContext [](start = 107, length = 13)
nitpick : could you not use the this->scriptContext? I am not sure why we need to have local variable for a scriptcontext. #Resolved
AssertMsg(errorObject != nullptr && Js::JavascriptError::Is(errorObject), "ModuleEvaluation: Unhandled exception thrown by calling root function"); | ||
if (errorObject != nullptr && Js::JavascriptError::Is(errorObject)) | ||
{ | ||
ResolveOrRejectDynamicImportPromise(false, errorObject, scriptContext); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setPromise to null? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding 'this' as last param and SetPromise(nullptr) will be taken care of.
In reply to: 115383242 [](ancestors = 115383242)
Js::JavascriptError * error = scriptContext->GetLibrary()->CreateError(); | ||
JavascriptError::SetErrorMessageProperties(error, E_OUTOFMEMORY, this->GetSpecifierSz(), scriptContext); | ||
ResolveOrRejectDynamicImportPromise(false, error, scriptContext); | ||
return scriptContext->GetLibrary()->GetUndefined(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we are returning undefined in the OOM case? why not re-thorw? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only in the dynamic import case, we reject the promise with error object and return undefined (not supposed to reject and throw)
In the static case, we should throw the error object. Making the change.
In reply to: 115383826 [](ancestors = 115383826)
if (walker.GetCaller(&caller) && caller != nullptr && caller->IsScriptFunction()) | ||
{ | ||
parentFuncBody = caller->GetFunctionBody(); | ||
dwReferencingSourceContext = dwReferencingSourceContext = parentFuncBody->GetFunctionInfo()->GetFunctionProxy()->GetSourceContextInfo()->dwHostSourceContext; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: function body has function GetHostSourceContext(), which will give you the dwHostSOurceContext. #Resolved
@dotnet-bot |
|
||
do | ||
{ | ||
if (walker.GetCaller(&caller) && caller != nullptr && caller->IsScriptFunction()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interesting to see what happens when the import is called in the setTimeout. Will you add a test case just to see that we don't get the fatal error. #WontFix
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't find a scenario which involves the nested imports also dynamic->static->dynamic. sort of?
In reply to: 115561112 [](ancestors = 115561112)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am still working on these test cases. They will come in later.
In reply to: 115577016 [](ancestors = 115577016,115561112)
} | ||
} | ||
|
||
throw(err); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw outside of catch handler. (look at other example in the code base, where they use different function for re-throw). #Resolved
if (this->promise != nullptr) | ||
{ | ||
Var errorObject = err.GetAndClear()->GetThrownObject(scriptContext); | ||
AssertMsg(errorObject != nullptr, "ModuleEvaluation: null error object thrown from root function"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AssertMsg [](start = 16, length = 9)
nit : convert to AssertOrFailFast so that you don't have to have if/else blocks #Resolved
} | ||
|
||
Assert(this->errorObject == nullptr); | ||
Assert(!WasEvaluated()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: SetWasEvaluated has this assert covered. #Resolved
Is import() configurable through config flags or it is enabled by default on? #Resolved |
We are enabling it by default. In reply to: 300271647 [](ancestors = 300271647) |
This PR adds support for import() dynamic module import sematic. Per https://github.com/tc39/proposal-dynamic-import: "A call to import(specifier) returns a promise for the module namespace object of the requested module, which is created after fetching, instantiating, and evaluating all of the module's dependencies, as well as the module itself. "Here specifier will be interpreted the same way as in an import declaration (i.e., the same strings will work in both places). However, while specifier is a string it is not necessarily a string literal; thus code like import(`./language-packs/${navigator.language}.js`) will work—something impossible to accomplish with the usual import declarations. "import() is proposed to work in both scripts and modules. This gives script code an easy asynchronous entry point into the module world, allowing it to start running module code." This PR includes following changes: - Update parser and bytecode generator to support import() sematic in module and in script - Add new bytecode 'ImportCall' - Add runtime function for import() that: ○ Uses caller from stack to look up module record or source context that are associated with the module or script from which 'import()' is called ○ Requests host to load target module source file (gets module record in return) ○ Creates promise unless the module record has one ○ Resolves/rejects promise if appropriates ○ Returns promise - Add new host callback ('FetchImportedModuleFromScript') for fetching imported module from script (accepts source context) - Add 'promiseCapability' field to module record class - Update SourceTextModuleRecord's methods to accept callback from host and to handle dynamically imported module and its promise capability - Update exception checks and assertions to cover new usage scenario of importing and evaluating module code with active script Add unit tests for dynamic import functionality
Merge pull request #2913 from suwc:build/suwc/buddy This PR adds support for `import()` dynamic module import sematic. Per https://github.com/tc39/proposal-dynamic-import: "A call to `import(specifier)` returns a promise for the module namespace object of the requested module, which is created after fetching, instantiating, and evaluating all of the module's dependencies, as well as the module itself. "Here specifier will be interpreted the same way as in an import declaration (i.e., the same strings will work in both places). However, while specifier is a string it is not necessarily a string literal; thus code like import(`./language-packs/${navigator.language}.js`) will work—something impossible to accomplish with the usual import declarations. "`import()` is proposed to work in both scripts and modules. This gives script code an easy asynchronous entry point into the module world, allowing it to start running module code." This PR includes following changes: - Update parser and bytecode generator to support `import()` sematic in module and in script - Add new bytecode `ImportCall` - Add runtime function for `import()` that: ○ Uses caller from stack to look up module record or source context that are associated with the module or script from which `import()` is called ○ Requests host to load target module source file (gets module record in return) ○ Creates promise unless the module record has one ○ Resolves/rejects promise if appropriates ○ Returns promise - Add new host callback (`FetchImportedModuleFromScript`) for fetching imported module from script (accepts source context) - Add `promiseCapability` field to module record class - Update `SourceTextModuleRecord`'s methods to accept callback from host and to handle dynamically imported module and its promise capability - Update exception checks and assertions to cover new usage scenario of importing and evaluating module code with active script Add unit tests for dynamic import functionality
Thanks for the code review. |
This PR adds support for
import()
dynamic module import sematic.Per https://github.com/tc39/proposal-dynamic-import:
"A call to
import(specifier)
returns a promise for the module namespace object of the requested module, which is created after fetching, instantiating, and evaluating all of the module's dependencies, as well as the module itself."Here specifier will be interpreted the same way as in an import declaration (i.e., the same strings will work in both places). However, while specifier is a string it is not necessarily a string literal; thus code like import(
./language-packs/${navigator.language}.js
) will work—something impossible to accomplish with the usual import declarations."
import()
is proposed to work in both scripts and modules. This gives script code an easy asynchronous entry point into the module world, allowing it to start running module code."This PR includes following changes:
- Update parser and bytecode generator to support
import()
sematic in module and in script- Add new bytecode
ImportCall
- Add runtime function for
import()
that:○ Uses caller from stack to look up module record or source context that are associated with the module or script from which
import()
is called○ Requests host to load target module source file (gets module record in return)
○ Creates promise unless the module record has one
○ Resolves/rejects promise if appropriates
○ Returns promise
- Add new host callback (
FetchImportedModuleFromScript
) for fetching imported module from script (accepts source context)- Add
promiseCapability
field to module record class- Update
SourceTextModuleRecord
's methods to accept callback from host and to handle dynamically imported module and its promise capability- Update exception checks and assertions to cover new usage scenario of importing and evaluating module code with active script
Add unit tests for dynamic import functionality