package com.oracle.truffle.js.nodes.access;
import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.Property;
import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.api.utilities.NeverValidAssumption;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
import com.oracle.truffle.js.nodes.JavaScriptBaseNode;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.objects.Dead;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
import com.oracle.truffle.js.runtime.objects.JSProperty;
import com.oracle.truffle.js.runtime.objects.JSShape;
public abstract class GlobalScopeLookupNode extends JavaScriptBaseNode {
final String varName;
final boolean write;
GlobalScopeLookupNode(String varName, boolean write) {
this.varName = varName;
this.write = write;
}
public static GlobalScopeLookupNode create(String varName, boolean write) {
return GlobalScopeLookupNodeGen.create(varName, write);
}
public abstract boolean execute(Object scope);
@SuppressWarnings("unused")
@Specialization(assumptions = {"assumption"})
static boolean doAbsent(DynamicObject scope,
@Cached("getAbsentPropertyAssumption(scope.getShape())") Assumption assumption) {
return false;
}
@SuppressWarnings("unused")
@Specialization(guards = {"scope.getShape() == cachedShape"}, assumptions = {"cachedShape.getValidAssumption()"}, limit = "cacheLimit", replaces = "doAbsent")
final boolean doCached(DynamicObject scope,
@Cached("scope.getShape()") Shape cachedShape,
@Cached("cachedShape.hasProperty(varName)") boolean exists,
@Cached("isDead(cachedShape)") boolean dead,
@Cached("isConstAssignment(cachedShape)") boolean constAssignment,
@Cached("getPropertyCacheLimit()") int cacheLimit) {
assert !exists || dead == (JSDynamicObject.getOrNull(scope, varName) == Dead.instance());
if (dead) {
throw Errors.createReferenceErrorNotDefined(JavaScriptLanguage.getCurrentJSRealm().getContext(), varName, this);
}
if (constAssignment) {
throw Errors.createTypeErrorConstReassignment(varName, scope, this);
}
return exists;
}
protected int getPropertyCacheLimit() {
return JavaScriptLanguage.getCurrentJSRealm().getContext().getPropertyCacheLimit();
}
@Specialization(replaces = "doCached")
final boolean doUncached(DynamicObject scope,
@Cached("create()") BranchProfile errorBranch) {
Property property = scope.getShape().getProperty(varName);
if (property != null) {
if (JSDynamicObject.getOrNull(scope, varName) == Dead.instance()) {
errorBranch.enter();
throw Errors.createReferenceErrorNotDefined(JavaScriptLanguage.getCurrentJSRealm().getContext(), varName, this);
} else if (write && JSProperty.isConst(property)) {
errorBranch.enter();
throw Errors.createTypeErrorConstReassignment(varName, scope, this);
}
return true;
}
return false;
}
final boolean isDead(Shape shape) {
Property property = shape.getProperty(varName);
return property != null && property.getLocation().isDeclared();
}
final boolean isConstAssignment(Shape shape) {
if (write) {
Property property = shape.getProperty(varName);
return property != null && JSProperty.isConst(property);
}
return false;
}
final Assumption getAbsentPropertyAssumption(Shape shape) {
Property property = shape.getProperty(varName);
if (property == null) {
return JSShape.getPropertyAssumption(shape, varName);
}
return NeverValidAssumption.INSTANCE;
}
}