diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java
index 6b92ed012bc8d64b274e128c4d31bc06a2a734bd..711f5ae303de668a2623bf03ef81ce4163385146 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java
@@ -509,6 +509,7 @@ public class BasePackage extends RBuiltinPackage {
         add(UpdateLevels.class, UpdateLevelsNodeGen::create);
         add(UpdateNames.class, UpdateNamesNodeGen::create);
         add(UpdateOldClass.class, UpdateOldClassNodeGen::create);
+        add(UpdateSlot.class, UpdateSlotNodeGen::create);
         add(UpdateStorageMode.class, UpdateStorageModeNodeGen::create);
         add(UpdateSubstr.class, UpdateSubstrNodeGen::create);
         add(UpperTri.class, UpperTriNodeGen::create);
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Slot.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Slot.java
index 019d0c5541331f5cb48de4bfd9dfcefc63bb3e5a..9498cd4c134972b677f26757f08241c46ae41c22 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Slot.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Slot.java
@@ -13,34 +13,21 @@
 
 package com.oracle.truffle.r.nodes.builtin.base;
 
-import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.dsl.*;
+import com.oracle.truffle.r.nodes.access.AccessSlotNode;
+import com.oracle.truffle.r.nodes.access.AccessSlotNodeGen;
 import com.oracle.truffle.r.nodes.access.ConstantNode;
 import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
-import com.oracle.truffle.r.nodes.attributes.AttributeAccess;
-import com.oracle.truffle.r.nodes.attributes.AttributeAccessNodeGen;
 import com.oracle.truffle.r.nodes.builtin.*;
-import com.oracle.truffle.r.nodes.function.ClassHierarchyNode;
-import com.oracle.truffle.r.nodes.function.ClassHierarchyNodeGen;
 import com.oracle.truffle.r.nodes.function.WrapArgumentNode;
-import com.oracle.truffle.r.nodes.unary.TypeofNode;
-import com.oracle.truffle.r.nodes.unary.TypeofNodeGen;
 import com.oracle.truffle.r.runtime.*;
-import com.oracle.truffle.r.runtime.context.RContext;
-import com.oracle.truffle.r.runtime.data.*;
-import com.oracle.truffle.r.runtime.data.model.*;
-import com.oracle.truffle.r.runtime.env.REnvironment;
+import com.oracle.truffle.r.runtime.data.RPromise;
+import com.oracle.truffle.r.runtime.data.RSymbol;
 
 @RBuiltin(name = "@", kind = RBuiltinKind.PRIMITIVE, parameterNames = {"", ""}, nonEvalArgs = 1)
 public abstract class Slot extends RBuiltinNode {
 
-    private final RAttributeProfiles attrProfiles = RAttributeProfiles.create();
-    @Child private ClassHierarchyNode classHierarchy;
-    @Child private TypeofNode typeofNode;
-
-    protected AttributeAccess createAttrAccess(String name) {
-        return AttributeAccessNodeGen.create(name);
-    }
+    @Child AccessSlotNode accessSlotNode = AccessSlotNodeGen.create(null, null);
 
     protected String getName(Object nameObj) {
         if (nameObj instanceof RPromise) {
@@ -63,103 +50,9 @@ public abstract class Slot extends RBuiltinNode {
         throw RError.error(this, RError.Message.GENERIC, "invalid type or length for slot name");
     }
 
-    private Object getSlotS4Internal(RS4Object object, String name, Object value) {
-        if (value == null) {
-            if (name == RRuntime.DOT_S3_CLASS) {
-                // TODO: this will not work if `@` function is called directly, as in:
-                // `@`(x, ".S3Class")
-                // in general, treatment of the name parameter has to be finessed to be
-                // fully compatible with GNU R on direct calls to `@` function
-                if (classHierarchy == null) {
-                    CompilerDirectives.transferToInterpreterAndInvalidate();
-                    classHierarchy = insert(ClassHierarchyNodeGen.create(true));
-                }
-                return classHierarchy.execute(object);
-            } else if (name == RRuntime.DOT_DATA) {
-                return getDataPart(object);
-            } else if (name == RRuntime.NAMES_ATTR_KEY && object instanceof RAbstractVector) {
-                assert false; // RS4Object can never be a vector?
-                return RNull.instance;
-            }
-
-            RStringVector classAttr = object.getClassAttr(attrProfiles);
-            if (classAttr == null) {
-                if (typeofNode == null) {
-                    CompilerDirectives.transferToInterpreterAndInvalidate();
-                    typeofNode = insert(TypeofNodeGen.create());
-                }
-                throw RError.error(this, RError.Message.SLOT_CANNOT_GET, name, typeofNode.execute(object).getName());
-            } else {
-                throw RError.error(this, RError.Message.SLOT_NONE, name, classAttr.getLength() == 0 ? RRuntime.STRING_NA : classAttr.getDataAt(0));
-            }
-        }
-        if (value == RRuntime.NULL_STR_VECTOR) {
-            return RNull.instance;
-        } else {
-            return value;
-        }
-    }
-
-    @Specialization(guards = "getName(nameObj) == cachedName")
-    protected Object getSlotS4Cached(RS4Object object, @SuppressWarnings("unused") Object nameObj, @Cached("getName(nameObj)") String cachedName,
-                    @Cached("createAttrAccess(cachedName)") AttributeAccess attrAccess) {
-        Object value = attrAccess.execute(object.getAttributes());
-        return getSlotS4Internal(object, cachedName, value);
-    }
-
-    @Specialization(contains = "getSlotS4Cached")
-    protected Object getSlotS4(RS4Object object, Object nameObj) {
-        String name = getName(nameObj);
-        Object value = object.getAttr(attrProfiles, name.intern());
-        return getSlotS4Internal(object, name, value);
-    }
-
-    protected RFunction getDataPartFunction(REnvironment methodsNamespace) {
-        Object f = methodsNamespace.findFunction("getDataPart");
-        return (RFunction) RContext.getRRuntimeASTAccess().forcePromise(f);
-    }
-
-    protected REnvironment getMethodsNamespace() {
-        return REnvironment.getRegisteredNamespace("methods");
-    }
-
-    private Object getDataPart(Object object) {
-        // TODO: any way to cache it or use a mechanism similar to overrides?
-        REnvironment methodsNamespace = REnvironment.getRegisteredNamespace("methods");
-        RFunction dataPart = getDataPartFunction(methodsNamespace);
-        return RContext.getEngine().evalFunction(dataPart, methodsNamespace.getFrame(), object);
-    }
-
-    @SuppressWarnings("unused")
-    @Specialization(guards = "isDotData(getName(nameObj))")
-    protected Object getSlotNonS4(RAbstractContainer object, Object nameObj) {
-        return getDataPart(object);
+    @Specialization
+    protected Object getSlot(Object object, Object nameObj) {
+        return accessSlotNode.executeAccess(object, getName(nameObj));
     }
 
-    // this is really a fallback specialization but @Fallback does not work here (because of the
-    // type of "object"?)
-    @Specialization(guards = "!isDotData(getName(nameObj))")
-    protected Object getSlot(RAbstractContainer object, Object nameObj) {
-        // first argument is wrong
-        String name = getName(nameObj);
-
-        RStringVector classAttr = object.getClassAttr(attrProfiles);
-        if (classAttr == null) {
-            RStringVector implicitClassVec = object.getImplicitClass();
-            assert implicitClassVec.getLength() > 0;
-            throw RError.error(this, RError.Message.SLOT_BASIC_CLASS, name, implicitClassVec.getDataAt(0));
-        } else {
-            assert classAttr.getLength() > 0;
-            throw RError.error(this, RError.Message.SLOT_NON_S4, name, classAttr.getDataAt(0));
-        }
-    }
-
-    protected boolean isS4(Object o) {
-        return o instanceof RS4Object;
-    }
-
-    protected boolean isDotData(String name) {
-        // see comment on usinq object equality in getSlotS4()
-        return name == RRuntime.DOT_DATA;
-    }
 }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateSlot.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateSlot.java
new file mode 100644
index 0000000000000000000000000000000000000000..e852ca0caec51da35ceaec92dbfc26080d393fd0
--- /dev/null
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateSlot.java
@@ -0,0 +1,102 @@
+/*
+ * This material is distributed under the GNU General Public License
+ * Version 2. You may review the terms of this license at
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * Copyright (c) 1995, 1996, 1997  Robert Gentleman and Ross Ihaka
+ * Copyright (c) 1995-2014, The R Core Team
+ * Copyright (c) 2002-2008, The R Foundation
+ * Copyright (c) 2015, Oracle and/or its affiliates
+ *
+ * All rights reserved.
+ */
+
+package com.oracle.truffle.r.nodes.builtin.base;
+
+import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
+import com.oracle.truffle.api.dsl.*;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.r.nodes.access.ConstantNode;
+import com.oracle.truffle.r.nodes.access.UpdateSlotNode;
+import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
+import com.oracle.truffle.r.nodes.builtin.*;
+import com.oracle.truffle.r.nodes.function.ClassHierarchyNode;
+import com.oracle.truffle.r.nodes.function.ClassHierarchyNodeGen;
+import com.oracle.truffle.r.nodes.function.RCallNode;
+import com.oracle.truffle.r.nodes.function.WrapArgumentNode;
+import com.oracle.truffle.r.runtime.*;
+import com.oracle.truffle.r.runtime.context.RContext;
+import com.oracle.truffle.r.runtime.data.RAttributable;
+import com.oracle.truffle.r.runtime.data.RFunction;
+import com.oracle.truffle.r.runtime.data.RPromise;
+import com.oracle.truffle.r.runtime.data.RS4Object;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+import com.oracle.truffle.r.runtime.data.RSymbol;
+import com.oracle.truffle.r.runtime.data.model.RAbstractContainer;
+import com.oracle.truffle.r.runtime.env.REnvironment;
+
+@RBuiltin(name = "@<-", kind = RBuiltinKind.PRIMITIVE, parameterNames = {"", "", ""}, nonEvalArgs = 1)
+public abstract class UpdateSlot extends RBuiltinNode {
+
+    @CompilationFinal RFunction checkSlotAssign;
+    @Child private ClassHierarchyNode objClassHierarchy;
+    @Child private ClassHierarchyNode valClassHierarchy;
+    @Child UpdateSlotNode updateSlotNode = com.oracle.truffle.r.nodes.access.UpdateSlotNodeGen.create(null, null, null);
+
+    protected String getName(Object nameObj) {
+        if (nameObj instanceof RPromise) {
+            Object rep = ((RPromise) nameObj).getRep();
+            if (rep instanceof WrapArgumentNode) {
+                rep = ((WrapArgumentNode) rep).getOperand();
+            }
+            if (rep instanceof ConstantNode) {
+                Object val = ((ConstantNode) rep).getValue();
+                if (val instanceof String) {
+                    return (String) val;
+                }
+                if (val instanceof RSymbol) {
+                    return ((RSymbol) val).getName();
+                }
+            } else if (rep instanceof ReadVariableNode) {
+                return ((ReadVariableNode) rep).getIdentifier();
+            } else if (rep instanceof RCallNode) {
+                throw RError.error(this, RError.Message.SLOT_INVALID_TYPE, "language");
+            }
+        }
+        // TODO: this is not quite correct, but I wonder if we even reach here (can also be
+        // augmented on demand)
+        throw RError.error(this, RError.Message.SLOT_INVALID_TYPE, nameObj.getClass().toString());
+    }
+
+    private void checkSlotAssign(VirtualFrame frame, RAttributable object, String name, Object value) {
+        // TODO: optimize using a mechanism similar to overrides?
+        if (checkSlotAssign == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            REnvironment methodsNamespace = REnvironment.getRegisteredNamespace("methods");
+            Object f = methodsNamespace.findFunction("checkAtAssignment");
+            checkSlotAssign = (RFunction) RContext.getRRuntimeASTAccess().forcePromise(f);
+            assert objClassHierarchy == null && valClassHierarchy == null;
+            objClassHierarchy = insert(ClassHierarchyNodeGen.create(true));
+            valClassHierarchy = insert(ClassHierarchyNodeGen.create(true));
+
+        }
+        RStringVector objClass = objClassHierarchy.execute(object);
+        RStringVector valClass = objClassHierarchy.execute(value);
+        RContext.getEngine().evalFunction(checkSlotAssign, frame.materialize(), objClass, name, valClass);
+    }
+
+    @Specialization
+    protected Object updateSlot(VirtualFrame frame, RS4Object object, Object nameObj, Object value) {
+        String name = getName(nameObj);
+        checkSlotAssign(frame, object, name, value);
+        return updateSlotNode.executeUpdate(object, name, value);
+    }
+
+    @Specialization
+    protected Object updateSlot(VirtualFrame frame, RAbstractContainer object, Object nameObj, Object value) {
+        String name = getName(nameObj);
+        checkSlotAssign(frame, object, name, value);
+        return updateSlotNode.executeUpdate(object, name, value);
+    }
+}
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RTruffleVisitor.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RTruffleVisitor.java
index 7ac82e548d2df902165d96e1a778d1581416aa34..cdbb0cdd86aede92e42822eab018c540faf3f5e3 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RTruffleVisitor.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RTruffleVisitor.java
@@ -423,7 +423,7 @@ public final class RTruffleVisitor extends BasicVisitor<RSyntaxNode> {
             replacementArg = callArgAst.accept(this);
             RCallNode replacementCall = prepareReplacementCall(fAst, args, tmpSymbol, rhsSymbol, false);
             assignFromTemp = doReplacementLeftHandSide(callArgAst.getLhs(), true, replacementCall, replacement.isSuper(), replacement.getSource(), (receiver, rhsAccess) -> {
-                return createFieldUpdate(null, receiver, rhsAccess, callArgAst.getFieldName());
+                return createFieldUpdate(null, receiver, rhsAccess, callArgAst.getFieldName(), callArgAst.isAt());
             }).asRNode();
         }
         RSyntaxNode result = constructReplacementSuffix(rhs, replacementArg, true, assignFromTemp, tmpSymbol, rhsSymbol, replacement.getSource());
@@ -509,8 +509,8 @@ public final class RTruffleVisitor extends BasicVisitor<RSyntaxNode> {
         return RCallNode.createCall(callSource, function, ArgumentsSignature.empty(2), lhs, ConstantNode.create(callSource, access.getFieldName()));
     }
 
-    private static RCallNode createFieldUpdate(SourceSection source, RSyntaxNode receiver, RSyntaxNode rhs, String fieldName) {
-        ReadVariableNode function = ReadVariableNode.createForced(source, "$<-", RType.Function);
+    private static RCallNode createFieldUpdate(SourceSection source, RSyntaxNode receiver, RSyntaxNode rhs, String fieldName, boolean at) {
+        ReadVariableNode function = ReadVariableNode.createForced(source, at ? "@<-" : "$<-", RType.Function);
         return RCallNode.createCall(source, function, ArgumentsSignature.empty(3), receiver, ConstantNode.create(source, fieldName), rhs);
     }
 
@@ -564,7 +564,7 @@ public final class RTruffleVisitor extends BasicVisitor<RSyntaxNode> {
                 RCallNode updateOp = updateFunction.apply(accessAST.accept(this), rhs);
                 checkAssignSourceSection(updateOp, source);
                 result = doReplacementLeftHandSide(accessAST.getLhs(), false, updateOp, isSuper, source, (receiver1, rhsAccess1) -> {
-                    return createFieldUpdate(null, receiver1, rhsAccess1, accessAST.getFieldName());
+                    return createFieldUpdate(null, receiver1, rhsAccess1, accessAST.getFieldName(), accessAST.isAt());
                 });
             } else {
                 throw RInternalError.unimplemented();
@@ -594,7 +594,7 @@ public final class RTruffleVisitor extends BasicVisitor<RSyntaxNode> {
         FieldAccess a = u.getVector();
         RSyntaxNode rhs = u.getRHS().accept(this);
         return doReplacementLeftHandSide(a.getLhs(), true, rhs, u.isSuper(), u.getSource(), (receiver, rhsAccess) -> {
-            return createFieldUpdate(u.getSource(), receiver, rhsAccess, a.getFieldName());
+            return createFieldUpdate(u.getSource(), receiver, rhsAccess, a.getFieldName(), a.isAt());
         });
     }
 
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/AccessSlotNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/AccessSlotNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..ea32f12a3f15bbfb46bc3c91a2cf3ca794517854
--- /dev/null
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/AccessSlotNode.java
@@ -0,0 +1,159 @@
+/*
+ * This material is distributed under the GNU General Public License
+ * Version 2. You may review the terms of this license at
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * Copyright (c) 1995, 1996, 1997  Robert Gentleman and Ross Ihaka
+ * Copyright (c) 1995-2014, The R Core Team
+ * Copyright (c) 2002-2008, The R Foundation
+ * Copyright (c) 2015, Oracle and/or its affiliates
+ *
+ * All rights reserved.
+ */
+package com.oracle.truffle.r.nodes.access;
+
+import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.dsl.*;
+import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
+import com.oracle.truffle.r.nodes.attributes.AttributeAccess;
+import com.oracle.truffle.r.nodes.attributes.AttributeAccessNodeGen;
+import com.oracle.truffle.r.nodes.function.ClassHierarchyNode;
+import com.oracle.truffle.r.nodes.function.ClassHierarchyNodeGen;
+import com.oracle.truffle.r.nodes.function.WrapArgumentNode;
+import com.oracle.truffle.r.nodes.unary.TypeofNode;
+import com.oracle.truffle.r.nodes.unary.TypeofNodeGen;
+import com.oracle.truffle.r.runtime.*;
+import com.oracle.truffle.r.runtime.context.*;
+import com.oracle.truffle.r.runtime.data.*;
+import com.oracle.truffle.r.runtime.data.model.*;
+import com.oracle.truffle.r.runtime.env.*;
+import com.oracle.truffle.r.runtime.nodes.*;
+
+/**
+ * Perform a slot access. This node represents the {@code @} operator in R.
+ */
+@NodeChildren({@NodeChild(value = "object", type = RNode.class), @NodeChild(value = "name", type = RNode.class)})
+public abstract class AccessSlotNode extends RNode {
+
+    public abstract Object executeAccess(Object o, String name);
+
+    private final RAttributeProfiles attrProfiles = RAttributeProfiles.create();
+    @Child private ClassHierarchyNode classHierarchy;
+    @Child private TypeofNode typeofNode;
+
+    protected AttributeAccess createAttrAccess(String name) {
+        return AttributeAccessNodeGen.create(name);
+    }
+
+    protected String getName(Object nameObj) {
+        if (nameObj instanceof RPromise) {
+            Object rep = ((RPromise) nameObj).getRep();
+            if (rep instanceof WrapArgumentNode) {
+                rep = ((WrapArgumentNode) rep).getOperand();
+            }
+            if (rep instanceof ConstantNode) {
+                Object val = ((ConstantNode) rep).getValue();
+                if (val instanceof String) {
+                    return (String) val;
+                }
+                if (val instanceof RSymbol) {
+                    return ((RSymbol) val).getName();
+                }
+            } else if (rep instanceof ReadVariableNode) {
+                return ((ReadVariableNode) rep).getIdentifier();
+            }
+        }
+        throw RError.error(this, RError.Message.GENERIC, "invalid type or length for slot name");
+    }
+
+    private Object getSlotS4Internal(RS4Object object, String name, Object value) {
+        if (value == null) {
+            if (name == RRuntime.DOT_S3_CLASS) {
+                // TODO: this will not work if `@` function is called directly, as in:
+                // `@`(x, ".S3Class")
+                // in general, treatment of the name parameter has to be finessed to be
+                // fully compatible with GNU R on direct calls to `@` function
+                if (classHierarchy == null) {
+                    CompilerDirectives.transferToInterpreterAndInvalidate();
+                    classHierarchy = insert(ClassHierarchyNodeGen.create(true));
+                }
+                return classHierarchy.execute(object);
+            } else if (name == RRuntime.DOT_DATA) {
+                return getDataPart(object);
+            } else if (name == RRuntime.NAMES_ATTR_KEY && object instanceof RAbstractVector) {
+                assert false; // RS4Object can never be a vector?
+                return RNull.instance;
+            }
+
+            RStringVector classAttr = object.getClassAttr(attrProfiles);
+            if (classAttr == null) {
+                if (typeofNode == null) {
+                    CompilerDirectives.transferToInterpreterAndInvalidate();
+                    typeofNode = insert(TypeofNodeGen.create());
+                }
+                throw RError.error(this, RError.Message.SLOT_CANNOT_GET, name, typeofNode.execute(object).getName());
+            } else {
+                throw RError.error(this, RError.Message.SLOT_NONE, name, classAttr.getLength() == 0 ? RRuntime.STRING_NA : classAttr.getDataAt(0));
+            }
+        }
+        if (value == RRuntime.NULL_STR_VECTOR) {
+            return RNull.instance;
+        } else {
+            return value;
+        }
+    }
+
+    @Specialization(guards = "name == cachedName")
+    protected Object getSlotS4Cached(RS4Object object, @SuppressWarnings("unused") String name, @Cached("name") String cachedName, @Cached("createAttrAccess(cachedName)") AttributeAccess attrAccess) {
+        Object value = attrAccess.execute(object.getAttributes());
+        return getSlotS4Internal(object, cachedName, value);
+    }
+
+    @Specialization(contains = "getSlotS4Cached")
+    protected Object getSlotS4(RS4Object object, String name) {
+        Object value = object.getAttr(attrProfiles, name.intern());
+        return getSlotS4Internal(object, name, value);
+    }
+
+    protected RFunction getDataPartFunction(REnvironment methodsNamespace) {
+        Object f = methodsNamespace.findFunction("getDataPart");
+        return (RFunction) RContext.getRRuntimeASTAccess().forcePromise(f);
+    }
+
+    protected REnvironment getMethodsNamespace() {
+        return REnvironment.getRegisteredNamespace("methods");
+    }
+
+    private Object getDataPart(Object object) {
+        // TODO: any way to cache it or use a mechanism similar to overrides?
+        REnvironment methodsNamespace = REnvironment.getRegisteredNamespace("methods");
+        RFunction dataPart = getDataPartFunction(methodsNamespace);
+        return RContext.getEngine().evalFunction(dataPart, methodsNamespace.getFrame(), object);
+    }
+
+    @SuppressWarnings("unused")
+    @Specialization(guards = "isDotData(name)")
+    protected Object getSlotNonS4(RAbstractContainer object, String name) {
+        return getDataPart(object);
+    }
+
+    // this is really a fallback specialization but @Fallback does not work here (because of the
+    // type of "object"?)
+    @Specialization(guards = "!isDotData(name)")
+    protected Object getSlot(RAbstractContainer object, String name) {
+        RStringVector classAttr = object.getClassAttr(attrProfiles);
+        if (classAttr == null) {
+            RStringVector implicitClassVec = object.getImplicitClass();
+            assert implicitClassVec.getLength() > 0;
+            throw RError.error(this, RError.Message.SLOT_BASIC_CLASS, name, implicitClassVec.getDataAt(0));
+        } else {
+            assert classAttr.getLength() > 0;
+            throw RError.error(this, RError.Message.SLOT_NON_S4, name, classAttr.getDataAt(0));
+        }
+    }
+
+    protected boolean isDotData(String name) {
+        // see comment on usinq object equality in getSlotS4()
+        return name == RRuntime.DOT_DATA;
+    }
+}
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/UpdateSlotNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/UpdateSlotNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..c5c76344168ed2056b8d601a9a1d985816611dfa
--- /dev/null
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/UpdateSlotNode.java
@@ -0,0 +1,76 @@
+/*
+ * This material is distributed under the GNU General Public License
+ * Version 2. You may review the terms of this license at
+ * http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * Copyright (c) 1995, 1996, 1997  Robert Gentleman and Ross Ihaka
+ * Copyright (c) 1995-2014, The R Core Team
+ * Copyright (c) 2002-2008, The R Foundation
+ * Copyright (c) 2015, Oracle and/or its affiliates
+ *
+ * All rights reserved.
+ */
+package com.oracle.truffle.r.nodes.access;
+
+import com.oracle.truffle.api.CompilerDirectives.CompilationFinal;
+import com.oracle.truffle.api.dsl.*;
+import com.oracle.truffle.r.nodes.attributes.PutAttributeNode;
+import com.oracle.truffle.r.nodes.attributes.PutAttributeNodeGen;
+import com.oracle.truffle.r.nodes.function.ClassHierarchyNode;
+import com.oracle.truffle.r.runtime.*;
+import com.oracle.truffle.r.runtime.data.*;
+import com.oracle.truffle.r.runtime.data.model.*;
+import com.oracle.truffle.r.runtime.nodes.*;
+
+@NodeChildren({@NodeChild(value = "object", type = RNode.class), @NodeChild(value = "name", type = RNode.class), @NodeChild(value = "value", type = RNode.class)})
+public abstract class UpdateSlotNode extends RNode {
+
+    public abstract Object executeUpdate(Object object, String name, Object value);
+
+    @CompilationFinal RFunction checkSlotAssign;
+    @Child private ClassHierarchyNode objClassHierarchy;
+    @Child private ClassHierarchyNode valClassHierarchy;
+
+    protected PutAttributeNode createAttrUpdate(String name) {
+        return PutAttributeNodeGen.create(name);
+    }
+
+    private static Object getActualValue(Object value) {
+        if (value == RNull.instance) {
+            return RRuntime.NULL_STR_VECTOR;
+        } else {
+            return value;
+        }
+    }
+
+    @SuppressWarnings("unused")
+    @Specialization(guards = "name == cachedName")
+    protected Object updateSlotS4Cached(RS4Object object, String name, Object value, @Cached("name") String cachedName, @Cached("createAttrUpdate(cachedName)") PutAttributeNode attributeUpdate) {
+        Object actualValue = getActualValue(value);
+        attributeUpdate.execute(object.getAttributes(), actualValue);
+        return object;
+    }
+
+    @Specialization(contains = "updateSlotS4Cached")
+    protected Object updateSlotS4(RS4Object object, String name, Object value) {
+        Object actualValue = getActualValue(value);
+        object.setAttr(name.intern(), actualValue);
+        return object;
+    }
+
+    @SuppressWarnings("unused")
+    @Specialization(guards = "name == cachedName")
+    protected Object updateSlotCached(RAbstractContainer object, String name, Object value, @Cached("name") String cachedName, @Cached("createAttrUpdate(cachedName)") PutAttributeNode attributeUpdate) {
+        Object actualValue = getActualValue(value);
+        attributeUpdate.execute(object.getAttributes(), actualValue);
+        return object;
+    }
+
+    @Specialization(contains = "updateSlotCached")
+    protected Object updateSlot(RAbstractContainer object, String name, Object value) {
+        Object actualValue = getActualValue(value);
+        object.setAttr(name.intern(), actualValue);
+        return object;
+    }
+
+}
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
index 2bb431e255a98124e0f2b9943128e03dba715043..2d10890fec592c02e08704c68b344f40a63498c3 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
@@ -636,7 +636,8 @@ public final class RError extends RuntimeException {
         SLOT_BASIC_CLASS("trying to get slot \"%s\" from an object of a basic class (\"%s\") with no slots"),
         SLOT_NON_S4("trying to get slot \"%s\" from an object (class \"%s\") that is not an S4 object "),
         SLOT_CANNOT_GET("cannot get a slot (\"%s\") from an object of type \"%s\""),
-        SLOT_NONE("no slot of name \"%s\" for this object of class \"%s\"");
+        SLOT_NONE("no slot of name \"%s\" for this object of class \"%s\""),
+        SLOT_INVALID_TYPE("invalid type '%s' for slot name");
 
         public final String message;
         final boolean hasArgs;
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java
index 40080dfa9b45280f60b77130bd34851032bf07ce..f155e4810d97a6e8284d8c0a53697f43b5fe4eac 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java
@@ -41,6 +41,10 @@ public class TestS4 extends TestBase {
         assertEval(Output.ContainsError, "{ getClass(\"ClassUnionRepresentation\")@foo }");
         assertEval(Output.ContainsError, "{ c(42)@foo }");
         assertEval(Output.ContainsError, "{ x<-c(42); class(x)<-\"bar\"; x@foo }");
+    }
 
+    @Test
+    public void testSlotUpdate() {
+        assertEval("{ x<-getClass(\"ClassUnionRepresentation\"); x@virtual<-TRUE; x@virtual }");
     }
 }
diff --git a/mx.fastr/copyrights/overrides b/mx.fastr/copyrights/overrides
index 728d6ff93332e1bbc1b71c51631dc3c2c8e1b02a..ac032cb5d484de91ebaee7d460faf21483696a61 100644
--- a/mx.fastr/copyrights/overrides
+++ b/mx.fastr/copyrights/overrides
@@ -136,7 +136,10 @@ com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/U
 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Unlist.java,gnu_r.copyright
 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateClass.java,purdue.copyright
 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateLevels.java,purdue.copyright
+com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateSlot.java,gnu_r_gentleman_ihaka.copyright
 com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateStorageMode.java,purdue.copyright
+com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/AccessSlotNode.java,gnu_r_gentleman_ihaka.copyright
+com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/UpdateSlotNode.java,gnu_r_gentleman_ihaka.copyright
 com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/binary/CastTypeNode.java,purdue.copyright
 com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/CallMatcherNode.java,purdue.copyright
 com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/GroupDispatchNode.java,purdue.copyright