package com.oracle.truffle.js.nodes.promise;
import java.util.Set;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.Tag;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.js.nodes.JavaScriptNode;
import com.oracle.truffle.js.nodes.arguments.AccessIndexedArgumentNode;
import com.oracle.truffle.js.nodes.cast.JSToStringNode;
import com.oracle.truffle.js.nodes.control.TryCatchNode;
import com.oracle.truffle.js.nodes.function.JSFunctionCallNode;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSException;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.JavaScriptRootNode;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSFunctionData;
import com.oracle.truffle.js.runtime.builtins.JSPromise;
import com.oracle.truffle.js.runtime.objects.JSModuleRecord;
import com.oracle.truffle.js.runtime.objects.PromiseCapabilityRecord;
import com.oracle.truffle.js.runtime.objects.PromiseReactionRecord;
import com.oracle.truffle.js.runtime.objects.ScriptOrModule;
import com.oracle.truffle.js.runtime.objects.Undefined;
import com.oracle.truffle.js.runtime.util.Pair;
import com.oracle.truffle.js.runtime.util.Triple;
public class ImportCallNode extends JavaScriptNode {
@Child private JavaScriptNode argRefNode;
@Child private JavaScriptNode activeScriptOrModuleNode;
@Child private NewPromiseCapabilityNode newPromiseCapabilityNode;
@Child private JSToStringNode toStringNode;
@Child private PromiseReactionJobNode promiseReactionJobNode;
@Child private JSFunctionCallNode callRejectNode;
@Child private TryCatchNode.GetErrorObjectNode getErrorObjectNode;
@Child private InteropLibrary exceptions;
private final JSContext context;
protected ImportCallNode(JSContext context, JavaScriptNode argRefNode, JavaScriptNode activeScriptOrModuleNode) {
this.context = context;
this.argRefNode = argRefNode;
this.activeScriptOrModuleNode = activeScriptOrModuleNode;
this.newPromiseCapabilityNode = NewPromiseCapabilityNode.create(context);
this.toStringNode = JSToStringNode.create();
this.promiseReactionJobNode = PromiseReactionJobNode.create(context);
}
public static ImportCallNode create(JSContext context, JavaScriptNode argRefNode, JavaScriptNode activeScriptOrModuleNode) {
return new ImportCallNode(context, argRefNode, activeScriptOrModuleNode);
}
@Override
public Object execute(VirtualFrame frame) {
Object referencingScriptOrModule = getActiveScriptOrModule(frame);
Object specifier = argRefNode.execute(frame);
String specifierString;
try {
specifierString = toStringNode.executeString(specifier);
} catch (Throwable ex) {
if (TryCatchNode.shouldCatch(ex, exceptions())) {
return newRejectedPromiseFromException(ex);
} else {
throw ex;
}
}
return hostImportModuleDynamically(referencingScriptOrModule, specifierString);
}
private Object getActiveScriptOrModule(VirtualFrame frame) {
if (activeScriptOrModuleNode != null) {
return activeScriptOrModuleNode.execute(frame);
} else {
return new ScriptOrModule(context, getEncapsulatingSourceSection().getSource());
}
}
private DynamicObject hostImportModuleDynamically(Object referencingScriptOrModule, String specifier) {
JSRealm realm = context.getRealm();
if (context.hasImportModuleDynamicallyCallbackBeenSet()) {
DynamicObject promise = context.hostImportModuleDynamically(realm, (ScriptOrModule) referencingScriptOrModule, specifier);
if (promise == null) {
return newRejectedPromiseFromException(createTypeErrorCannotImport(specifier));
}
assert JSPromise.isJSPromise(promise);
return promise;
} else {
PromiseCapabilityRecord promiseCapability = newPromiseCapability();
context.promiseEnqueueJob(realm, createImportModuleDynamicallyJob((ScriptOrModule) referencingScriptOrModule, specifier, promiseCapability));
return promiseCapability.getPromise();
}
}
private PromiseCapabilityRecord newPromiseCapability() {
if (newPromiseCapabilityNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
newPromiseCapabilityNode = insert(NewPromiseCapabilityNode.create(context));
}
return newPromiseCapabilityNode.executeDefault();
}
private DynamicObject newRejectedPromiseFromException(Throwable ex) {
if (callRejectNode == null || getErrorObjectNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
callRejectNode = insert(JSFunctionCallNode.createCall());
getErrorObjectNode = insert(TryCatchNode.GetErrorObjectNode.create(context));
}
PromiseCapabilityRecord promiseCapability = newPromiseCapability();
callRejectNode.executeCall(JSArguments.createOneArg(Undefined.instance, promiseCapability.getReject(), getErrorObjectNode.execute(ex)));
return promiseCapability.getPromise();
}
private InteropLibrary exceptions() {
InteropLibrary e = exceptions;
if (e == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
exceptions = e = insert(InteropLibrary.getFactory().createDispatched(JSConfig.InteropLibraryLimit));
}
return e;
}
@TruffleBoundary
private static JSException createTypeErrorCannotImport(String specifier) {
return Errors.createError("Cannot dynamically import module: " + specifier);
}
public DynamicObject createImportModuleDynamicallyJob(ScriptOrModule referencingScriptOrModule, String specifier, PromiseCapabilityRecord promiseCapability) {
if (context.isOptionTopLevelAwait()) {
Triple<ScriptOrModule, String, PromiseCapabilityRecord> request = new Triple<>(referencingScriptOrModule, specifier, promiseCapability);
PromiseCapabilityRecord startModuleLoadCapability = newPromiseCapability();
PromiseReactionRecord startModuleLoad = PromiseReactionRecord.create(startModuleLoadCapability, createImportModuleDynamicallyHandler(), true);
return promiseReactionJobNode.execute(startModuleLoad, request);
} else {
Pair<ScriptOrModule, String> request = new Pair<>(referencingScriptOrModule, specifier);
return promiseReactionJobNode.execute(PromiseReactionRecord.create(promiseCapability, createImportModuleDynamicallyHandler(), true), request);
}
}
private DynamicObject createImportModuleDynamicallyHandler() {
JSFunctionData functionData = context.getOrCreateBuiltinFunctionData(JSContext.BuiltinFunctionKey.ImportModuleDynamically, (c) -> createImportModuleDynamicallyHandlerImpl(c));
return JSFunction.create(context.getRealm(), functionData);
}
private static JSFunctionData createImportModuleDynamicallyHandlerImpl(JSContext context) {
class ImportModuleDynamicallyRootNode extends JavaScriptRootNode {
@Child protected JavaScriptNode argumentNode = AccessIndexedArgumentNode.create(0);
@SuppressWarnings("unchecked")
@Override
public Object execute(VirtualFrame frame) {
Pair<ScriptOrModule, String> request = (Pair<ScriptOrModule, String>) argumentNode.execute(frame);
ScriptOrModule referencingScriptOrModule = request.getFirst();
String specifier = request.getSecond();
JSModuleRecord moduleRecord = context.getEvaluator().hostResolveImportedModule(context, referencingScriptOrModule, specifier);
return finishDynamicImport(context.getRealm(), moduleRecord, referencingScriptOrModule, specifier);
}
protected Object finishDynamicImport(JSRealm realm, JSModuleRecord moduleRecord, ScriptOrModule referencingScriptOrModule, String specifier) {
context.getEvaluator().moduleInstantiation(realm, moduleRecord);
context.getEvaluator().moduleEvaluation(realm, moduleRecord);
if (moduleRecord.getEvaluationError() != null) {
throw JSRuntime.rethrow(moduleRecord.getEvaluationError());
}
assert moduleRecord == context.getEvaluator().hostResolveImportedModule(context, referencingScriptOrModule, specifier);
assert moduleRecord.isEvaluated();
return context.getEvaluator().getModuleNamespace(moduleRecord);
}
}
class TopLevelAwaitImportModuleDynamicallyRootNode extends ImportModuleDynamicallyRootNode {
@Child private PerformPromiseThenNode promiseThenNode = PerformPromiseThenNode.create(context);
@Child private JSFunctionCallNode callPromiseReaction = JSFunctionCallNode.createCall();
@Child private TryCatchNode.GetErrorObjectNode getErrorObjectNode;
@Child private InteropLibrary exceptions;
@SuppressWarnings("unchecked")
@Override
public Object execute(VirtualFrame frame) {
Triple<ScriptOrModule, String, PromiseCapabilityRecord> request = (Triple<ScriptOrModule, String, PromiseCapabilityRecord>) argumentNode.execute(frame);
ScriptOrModule referencingScriptOrModule = request.getFirst();
String specifier = request.getSecond();
PromiseCapabilityRecord moduleLoadedCapability = request.getThird();
try {
JSModuleRecord moduleRecord = context.getEvaluator().hostResolveImportedModule(context, referencingScriptOrModule, specifier);
JSRealm realm = context.getRealm();
if (moduleRecord.isTopLevelAsync()) {
context.getEvaluator().moduleInstantiation(realm, moduleRecord);
Object moduleLoadedStartPromise = context.getEvaluator().moduleEvaluation(realm, moduleRecord);
assert JSPromise.isJSPromise(moduleLoadedStartPromise);
promiseThenNode.execute((DynamicObject) moduleLoadedStartPromise, moduleLoadedCapability.getResolve(), moduleLoadedCapability.getReject(), moduleLoadedCapability);
} else {
Object result = finishDynamicImport(realm, moduleRecord, referencingScriptOrModule, specifier);
if (moduleRecord.isAsyncEvaluating()) {
PromiseCapabilityRecord topLevelCapability = moduleRecord.getTopLevelCapability();
promiseThenNode.execute(topLevelCapability.getPromise(), moduleLoadedCapability.getResolve(), moduleLoadedCapability.getReject(), null);
} else {
callPromiseReaction.executeCall(JSArguments.create(Undefined.instance, moduleLoadedCapability.getResolve(), result));
}
}
} catch (Throwable t) {
if (shouldCatch(t)) {
Object errorObject = getErrorObjectNode.execute(t);
callPromiseReaction.executeCall(JSArguments.create(Undefined.instance, moduleLoadedCapability.getReject(), errorObject));
} else {
throw t;
}
}
return Undefined.instance;
}
private boolean shouldCatch(Throwable exception) {
if (getErrorObjectNode == null || exceptions == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
getErrorObjectNode = insert(TryCatchNode.GetErrorObjectNode.create(context));
exceptions = insert(InteropLibrary.getFactory().createDispatched(JSConfig.InteropLibraryLimit));
}
return TryCatchNode.shouldCatch(exception, exceptions);
}
}
JavaScriptRootNode root = context.isOptionTopLevelAwait() ? new TopLevelAwaitImportModuleDynamicallyRootNode() : new ImportModuleDynamicallyRootNode();
CallTarget callTarget = Truffle.getRuntime().createCallTarget(root);
return JSFunctionData.createCallOnly(context, callTarget, 0, "");
}
@Override
protected JavaScriptNode copyUninitialized(Set<Class<? extends Tag>> materializedTags) {
return ImportCallNode.create(context, cloneUninitialized(argRefNode, materializedTags), cloneUninitialized(activeScriptOrModuleNode, materializedTags));
}
}