package com.oracle.truffle.js.nodes.function;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.Tag;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.HiddenKey;
import com.oracle.truffle.js.nodes.JavaScriptNode;
import com.oracle.truffle.js.nodes.access.CreateObjectNode;
import com.oracle.truffle.js.nodes.access.InitializeInstanceElementsNode;
import com.oracle.truffle.js.nodes.access.JSWriteFrameSlotNode;
import com.oracle.truffle.js.nodes.access.ObjectLiteralNode.ObjectLiteralMemberNode;
import com.oracle.truffle.js.nodes.access.PropertyGetNode;
import com.oracle.truffle.js.nodes.access.PropertySetNode;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.objects.JSObject;
import com.oracle.truffle.js.runtime.objects.Null;
import java.util.Set;
public final class ClassDefinitionNode extends JavaScriptNode implements FunctionNameHolder {
private final JSContext context;
@Child private JavaScriptNode constructorFunctionNode;
@Child private JavaScriptNode classHeritageNode;
@Children private final ObjectLiteralMemberNode[] memberNodes;
@Child private JSWriteFrameSlotNode writeClassBindingNode;
@Child private PropertyGetNode getPrototypeNode;
@Child private CreateMethodPropertyNode setConstructorNode;
@Child private CreateObjectNode.CreateObjectWithPrototypeNode createPrototypeNode;
@Child private DefineMethodNode defineConstructorMethodNode;
@Child private PropertySetNode setFieldsNode;
@Child private InitializeInstanceElementsNode staticFieldsNode;
@Child private PropertySetNode setPrivateBrandNode;
@Child private SetFunctionNameNode setFunctionName;
private final boolean hasName;
private final int instanceFieldCount;
private final int staticFieldCount;
protected ClassDefinitionNode(JSContext context, JSFunctionExpressionNode constructorFunctionNode, JavaScriptNode classHeritageNode, ObjectLiteralMemberNode[] memberNodes,
JSWriteFrameSlotNode writeClassBindingNode, boolean hasName, int instanceFieldCount, int staticFieldCount, boolean hasPrivateInstanceMethods) {
this.context = context;
this.constructorFunctionNode = constructorFunctionNode;
this.classHeritageNode = classHeritageNode;
this.memberNodes = memberNodes;
this.hasName = hasName;
this.instanceFieldCount = instanceFieldCount;
this.staticFieldCount = staticFieldCount;
this.writeClassBindingNode = writeClassBindingNode;
this.getPrototypeNode = PropertyGetNode.create(JSObject.PROTOTYPE, false, context);
this.setConstructorNode = CreateMethodPropertyNode.create(context, JSObject.CONSTRUCTOR);
this.createPrototypeNode = CreateObjectNode.createOrdinaryWithPrototype(context);
this.defineConstructorMethodNode = DefineMethodNode.create(context, constructorFunctionNode);
this.setFieldsNode = instanceFieldCount != 0 ? PropertySetNode.createSetHidden(JSFunction.CLASS_FIELDS_ID, context) : null;
this.setPrivateBrandNode = hasPrivateInstanceMethods ? PropertySetNode.createSetHidden(JSFunction.PRIVATE_BRAND_ID, context) : null;
this.setFunctionName = hasName ? null : SetFunctionNameNode.create();
}
public static ClassDefinitionNode create(JSContext context, JSFunctionExpressionNode constructorFunction, JavaScriptNode classHeritage, ObjectLiteralMemberNode[] members,
JSWriteFrameSlotNode writeClassBinding, boolean hasName, int instanceFieldCount, int staticFieldCount, boolean hasPrivateInstanceMethods) {
return new ClassDefinitionNode(context, constructorFunction, classHeritage, members, writeClassBinding, hasName, instanceFieldCount, staticFieldCount, hasPrivateInstanceMethods);
}
@Override
public DynamicObject execute(VirtualFrame frame) {
return executeWithClassName(frame, null);
}
public DynamicObject executeWithClassName(VirtualFrame frame, Object className) {
JSRealm realm = context.getRealm();
Object protoParent = realm.getObjectPrototype();
Object constructorParent = realm.getFunctionPrototype();
if (classHeritageNode != null) {
Object superclass = classHeritageNode.execute(frame);
if (superclass == Null.instance) {
protoParent = Null.instance;
} else if (!JSRuntime.isConstructor(superclass)) {
throw Errors.createTypeError("not a constructor", this);
} else if (JSRuntime.isGenerator(superclass)) {
throw Errors.createTypeError("class cannot extend a generator function", this);
} else {
protoParent = getPrototypeNode.getValue(superclass);
if (protoParent != Null.instance && !JSRuntime.isObject(protoParent)) {
throw Errors.createTypeError("protoParent is neither Object nor Null", this);
}
constructorParent = superclass;
}
}
assert protoParent == Null.instance || JSRuntime.isObject(protoParent);
DynamicObject proto = createPrototypeNode.execute(frame, ((DynamicObject) protoParent));
DynamicObject constructor = defineConstructorMethodNode.execute(frame, proto, (DynamicObject) constructorParent);
JSFunction.setClassPrototype(constructor, proto);
if (setFunctionName != null && className != null) {
setFunctionName.execute(constructor, className);
}
setConstructorNode.executeVoid(proto, constructor);
Object[][] instanceFields = instanceFieldCount == 0 ? null : new Object[instanceFieldCount][];
Object[][] staticFields = staticFieldCount == 0 ? null : new Object[staticFieldCount][];
initializeMembers(frame, proto, constructor, instanceFields, staticFields);
if (writeClassBindingNode != null) {
writeClassBindingNode.executeWrite(frame, constructor);
}
if (setFieldsNode != null) {
setFieldsNode.setValue(constructor, instanceFields);
}
if (setPrivateBrandNode != null) {
HiddenKey privateBrand = new HiddenKey("Brand");
setPrivateBrandNode.setValue(constructor, privateBrand);
}
if (staticFieldCount != 0) {
InitializeInstanceElementsNode defineStaticFields = this.staticFieldsNode;
if (defineStaticFields == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
this.staticFieldsNode = defineStaticFields = insert(InitializeInstanceElementsNode.create(context));
}
defineStaticFields.executeStaticFields(constructor, staticFields);
}
return constructor;
}
@ExplodeLoop
private void initializeMembers(VirtualFrame frame, DynamicObject proto, DynamicObject constructor, Object[][] instanceFields, Object[][] staticFields) {
int instanceFieldIndex = 0;
int staticFieldIndex = 0;
for (ObjectLiteralMemberNode memberNode : memberNodes) {
DynamicObject homeObject = memberNode.isStatic() ? constructor : proto;
memberNode.executeVoid(frame, homeObject, context);
if (memberNode.isField()) {
Object key = memberNode.executeKey(frame);
Object value = memberNode.executeValue(frame, homeObject);
Object[] field = new Object[]{key, value, memberNode.isAnonymousFunctionDefinition()};
if (memberNode.isStatic() && staticFields != null) {
staticFields[staticFieldIndex++] = field;
} else if (instanceFields != null) {
instanceFields[instanceFieldIndex++] = field;
} else {
throw Errors.shouldNotReachHere();
}
}
}
assert instanceFieldIndex == instanceFieldCount && staticFieldIndex == staticFieldCount;
}
@Override
public boolean isResultAlwaysOfType(Class<?> clazz) {
return clazz == DynamicObject.class;
}
@Override
public String getFunctionName() {
return hasName ? ((FunctionNameHolder) constructorFunctionNode).getFunctionName() : "";
}
@Override
public void setFunctionName(String name) {
((FunctionNameHolder) constructorFunctionNode).setFunctionName(name);
}
@Override
protected JavaScriptNode copyUninitialized(Set<Class<? extends Tag>> materializedTags) {
return create(context, (JSFunctionExpressionNode) cloneUninitialized(constructorFunctionNode, materializedTags), cloneUninitialized(classHeritageNode, materializedTags),
ObjectLiteralMemberNode.cloneUninitialized(memberNodes, materializedTags),
cloneUninitialized(writeClassBindingNode, materializedTags), hasName, instanceFieldCount, staticFieldCount, setPrivateBrandNode != null);
}
}