diff --git a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/methods/MethodsListDispatch.java b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/methods/MethodsListDispatch.java
index 15024aa6274a18607b024c96010739fb926e2c38..24ccf148b3164dbc2e6d0e8843534e1e951abfd3 100644
--- a/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/methods/MethodsListDispatch.java
+++ b/com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/methods/MethodsListDispatch.java
@@ -44,6 +44,7 @@ import com.oracle.truffle.r.runtime.ArgumentsSignature;
 import com.oracle.truffle.r.runtime.PrimitiveMethodsInfo;
 import com.oracle.truffle.r.runtime.PrimitiveMethodsInfo.MethodCode;
 import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RError.Message;
 import com.oracle.truffle.r.runtime.RInternalError;
 import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.context.RContext;
@@ -189,10 +190,13 @@ public class MethodsListDispatch {
                 return RRuntime.asLogical(prev);
             }
             boolean value = RRuntime.fromLogical(onOff);
-            RContext.getInstance().setMethodTableDispatchOn(value);
-            if (value != prev) {
-                // TODO
+            if (!value) {
+                warning(Message.GENERIC, "FastR does not support R_set_method_dispatch(FALSE) yet. S4 dispatch may not work correctly.");
             }
+            // StandardGeneric, the default one (true case) is currently implemented in FastR,
+            // the other one is in GnuR implemented by R_standardGeneric and is not implemented
+            // in FastR yet.
+            RContext.getInstance().setMethodTableDispatchOn(value);
             return RRuntime.asLogical(prev);
         }
     }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/infix/AccessField.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/infix/AccessField.java
index 1663b1ff5ace4d4cab3bd9c25712f45d953c911d..adc9076fd6b91177070fe04b597ced131219d695 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/infix/AccessField.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/infix/AccessField.java
@@ -27,16 +27,18 @@ import static com.oracle.truffle.r.runtime.RDispatch.INTERNAL_GENERIC;
 import static com.oracle.truffle.r.runtime.builtins.RBehavior.PURE;
 import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE;
 
-import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.dsl.Fallback;
 import com.oracle.truffle.api.dsl.NodeChild;
 import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.nodes.NodeCost;
+import com.oracle.truffle.api.nodes.NodeInfo;
 import com.oracle.truffle.api.profiles.BranchProfile;
 import com.oracle.truffle.api.profiles.ConditionProfile;
 import com.oracle.truffle.r.nodes.access.vector.ElementAccessMode;
-import com.oracle.truffle.r.nodes.access.vector.ExtractListElement;
 import com.oracle.truffle.r.nodes.access.vector.ExtractVectorNode;
 import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
+import com.oracle.truffle.r.nodes.helpers.AccessListField;
 import com.oracle.truffle.r.runtime.ArgumentsSignature;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RError.Message;
@@ -44,39 +46,33 @@ import com.oracle.truffle.r.runtime.RType;
 import com.oracle.truffle.r.runtime.builtins.RBuiltin;
 import com.oracle.truffle.r.runtime.builtins.RSpecialFactory;
 import com.oracle.truffle.r.runtime.data.RList;
-import com.oracle.truffle.r.runtime.data.RStringVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractListVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
 import com.oracle.truffle.r.runtime.nodes.RNode;
 
+@NodeInfo(cost = NodeCost.NONE)
 @NodeChild(value = "arguments", type = RNode[].class)
-abstract class AccessFieldSpecial extends SpecialsUtils.ListFieldSpecialBase {
+abstract class AccessFieldSpecial extends RNode {
 
-    @Child private ExtractListElement extractListElement = ExtractListElement.create();
+    @Child private AccessListField accessListField;
 
-    @Specialization(limit = "2", guards = {"getNamesNode.getNames(list) == cachedNames", "field == cachedField"})
-    public Object doList(RList list, @SuppressWarnings("unused") String field,
-                    @SuppressWarnings("unused") @Cached("list.getNames()") RStringVector cachedNames,
-                    @SuppressWarnings("unused") @Cached("field") String cachedField,
-                    @Cached("getIndex(cachedNames, field)") int index) {
-        if (index == -1) {
-            throw RSpecialFactory.throwFullCallNeeded();
+    @Specialization
+    public Object doList(RList list, String field) {
+        if (accessListField == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            accessListField = insert(AccessListField.create());
         }
-        return extractListElement.execute(list, index);
-    }
-
-    @Specialization(replaces = "doList")
-    public Object doListDynamic(RList list, String field) {
-        int index = getIndex(getNamesNode.getNames(list), field);
-        if (index == -1) {
+        Object result = accessListField.execute(list, field);
+        if (result == null) {
             throw RSpecialFactory.throwFullCallNeeded();
+        } else {
+            return accessListField.execute(list, field);
         }
-        return extractListElement.execute(list, index);
     }
 
     @Fallback
     @SuppressWarnings("unused")
-    public void doFallback(Object container, Object field) {
+    public Object doFallback(Object container, Object field) {
         throw RSpecialFactory.throwFullCallNeeded();
     }
 }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/infix/SpecialsUtils.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/infix/SpecialsUtils.java
index 2d25546585e829459ca483a694e24bd4745cd46c..add7367ea2fa6a0b51217e0b763c8e0559dc305e 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/infix/SpecialsUtils.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/infix/SpecialsUtils.java
@@ -23,7 +23,6 @@
 package com.oracle.truffle.r.nodes.builtin.base.infix;
 
 import com.oracle.truffle.api.CompilerDirectives;
-import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.dsl.Cached;
 import com.oracle.truffle.api.dsl.NodeChild;
 import com.oracle.truffle.api.dsl.Specialization;
@@ -32,7 +31,6 @@ import com.oracle.truffle.api.nodes.NodeCost;
 import com.oracle.truffle.api.nodes.NodeInfo;
 import com.oracle.truffle.r.nodes.attributes.HasAttributesNode;
 import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.GetDimAttributeNode;
-import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.GetNamesAttributeNode;
 import com.oracle.truffle.r.nodes.builtin.base.infix.SpecialsUtilsFactory.ConvertIndexNodeGen;
 import com.oracle.truffle.r.nodes.builtin.base.infix.SpecialsUtilsFactory.ConvertValueNodeGen;
 import com.oracle.truffle.r.nodes.function.ClassHierarchyNode;
@@ -40,7 +38,6 @@ import com.oracle.truffle.r.runtime.ArgumentsSignature;
 import com.oracle.truffle.r.runtime.Utils;
 import com.oracle.truffle.r.runtime.data.RDoubleVector;
 import com.oracle.truffle.r.runtime.data.RIntVector;
-import com.oracle.truffle.r.runtime.data.RStringVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
 import com.oracle.truffle.r.runtime.nodes.RNode;
 import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
@@ -116,37 +113,6 @@ class SpecialsUtils {
         }
     }
 
-    /**
-     * Common code shared between specials accessing/updating fields.
-     */
-    abstract static class ListFieldSpecialBase extends RNode {
-
-        @Child protected GetNamesAttributeNode getNamesNode = GetNamesAttributeNode.create();
-
-        protected static int getIndex(RStringVector names, String field) {
-            if (names != null) {
-                int fieldHash = field.hashCode();
-                for (int i = 0; i < names.getLength(); i++) {
-                    String current = names.getDataAt(i);
-                    if (current == field || hashCodeEquals(current, fieldHash) && contentsEquals(current, field)) {
-                        return i;
-                    }
-                }
-            }
-            return -1;
-        }
-
-        @TruffleBoundary
-        private static boolean contentsEquals(String current, String field) {
-            return field.equals(current);
-        }
-
-        @TruffleBoundary
-        private static boolean hashCodeEquals(String current, int fieldHash) {
-            return current.hashCode() == fieldHash;
-        }
-    }
-
     @NodeInfo(cost = NodeCost.NONE)
     @NodeChild(value = "delegate", type = RNode.class)
     public abstract static class ConvertIndex extends RNode {
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/infix/UpdateField.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/infix/UpdateField.java
index b73fcb9cc98050b48c2d5f5652c1c962323a10b9..c62b319f80d872b316e3686b99da2a6267ad8bf4 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/infix/UpdateField.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/infix/UpdateField.java
@@ -28,16 +28,17 @@ import static com.oracle.truffle.r.runtime.builtins.RBehavior.PURE;
 import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE;
 
 import com.oracle.truffle.api.CompilerDirectives;
-import com.oracle.truffle.api.dsl.Cached;
 import com.oracle.truffle.api.dsl.Fallback;
 import com.oracle.truffle.api.dsl.NodeChild;
 import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.nodes.NodeCost;
+import com.oracle.truffle.api.nodes.NodeInfo;
 import com.oracle.truffle.api.profiles.ConditionProfile;
 import com.oracle.truffle.r.nodes.access.vector.ElementAccessMode;
 import com.oracle.truffle.r.nodes.access.vector.ReplaceVectorNode;
 import com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef;
 import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
-import com.oracle.truffle.r.nodes.function.opt.ShareObjectNode;
+import com.oracle.truffle.r.nodes.helpers.UpdateListField;
 import com.oracle.truffle.r.nodes.unary.CastListNode;
 import com.oracle.truffle.r.nodes.unary.CastListNodeGen;
 import com.oracle.truffle.r.runtime.ArgumentsSignature;
@@ -47,69 +48,43 @@ import com.oracle.truffle.r.runtime.builtins.RBuiltin;
 import com.oracle.truffle.r.runtime.builtins.RSpecialFactory;
 import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.RNull;
-import com.oracle.truffle.r.runtime.data.RStringVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractListVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
 import com.oracle.truffle.r.runtime.nodes.RNode;
 
+@NodeInfo(cost = NodeCost.NONE)
 @NodeChild(value = "arguments", type = RNode[].class)
-abstract class UpdateFieldSpecial extends SpecialsUtils.ListFieldSpecialBase {
+abstract class UpdateFieldSpecial extends RNode {
 
-    @Child private ShareObjectNode shareObject = ShareObjectNode.create();
+    @Child private UpdateListField updateListField;
 
     /**
      * {@link RNull} and lists have special handling when they are RHS of update. Nulls delete the
      * field and lists can cause cycles.
      */
-    protected static boolean isNotRNullRList(Object value) {
+    static boolean isNotRNullRList(Object value) {
         return value != RNull.instance && !(value instanceof RList);
     }
 
-    @Specialization(limit = "2", guards = {"!list.isShared()", "getNamesNode.getNames(list) == cachedNames", "field == cachedField", "isNotRNullRList(value)"})
-    public Object doList(RList list, @SuppressWarnings("unused") String field, Object value,
-                    @SuppressWarnings("unused") @Cached("list.getNames()") RStringVector cachedNames,
-                    @SuppressWarnings("unused") @Cached("field") String cachedField,
-                    @Cached("getIndex(cachedNames, field)") int index) {
-        if (index == -1) {
-            throw RSpecialFactory.throwFullCallNeeded(value);
-        }
-        Object sharedValue = value;
-        // share only when necessary:
-        if (list.getDataAt(index) != value) {
-            sharedValue = getShareObjectNode().execute(value);
+    @Specialization(guards = {"!list.isShared()", "isNotRNullRList(value)"})
+    public Object doList(RList list, String field, Object value) {
+        if (updateListField == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            updateListField = insert(UpdateListField.create());
         }
-        list.setElement(index, sharedValue);
-        return list;
-    }
-
-    @Specialization(replaces = "doList", guards = {"!list.isShared()", "list.getNames() != null", "isNotRNullRList(value)"})
-    public RList doListDynamic(RList list, String field, Object value) {
-        int index = getIndex(getNamesNode.getNames(list), field);
-        if (index == -1) {
+        boolean result = updateListField.execute(list, field, value);
+        if (!result) {
             throw RSpecialFactory.throwFullCallNeeded(value);
+        } else {
+            return list;
         }
-        Object sharedValue = value;
-        // share only when necessary:
-        if (list.getDataAt(index) != value) {
-            sharedValue = getShareObjectNode().execute(value);
-        }
-        list.setElement(index, sharedValue);
-        return list;
     }
 
     @SuppressWarnings("unused")
     @Fallback
-    public void doFallback(Object container, Object field, Object value) {
+    public RList doFallback(Object container, Object field, Object value) {
         throw RSpecialFactory.throwFullCallNeeded(value);
     }
-
-    private ShareObjectNode getShareObjectNode() {
-        if (shareObject == null) {
-            CompilerDirectives.transferToInterpreterAndInvalidate();
-            shareObject = insert(ShareObjectNode.create());
-        }
-        return shareObject;
-    }
 }
 
 @RBuiltin(name = "$<-", kind = PRIMITIVE, parameterNames = {"", "", "value"}, dispatch = INTERNAL_GENERIC, behavior = PURE)
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedExtractVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedExtractVectorNode.java
index f6e1fdd15a338630359f8e342e595a831ae2df34..903947939fed214361eb750ec927e7f480a2c503 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedExtractVectorNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedExtractVectorNode.java
@@ -53,6 +53,7 @@ import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.RLogical;
 import com.oracle.truffle.r.runtime.data.RNull;
 import com.oracle.truffle.r.runtime.data.RPromise;
+import com.oracle.truffle.r.runtime.data.RS4Object;
 import com.oracle.truffle.r.runtime.data.RString;
 import com.oracle.truffle.r.runtime.data.RStringVector;
 import com.oracle.truffle.r.runtime.data.RTypedValue;
@@ -89,6 +90,8 @@ final class CachedExtractVectorNode extends CachedVectorNode {
 
     @Child private ExtractDimNamesNode extractDimNames;
 
+    @Child private ExtractS4ObjectNode extractS4ObjectNode;
+
     private final ConditionProfile resultHasDimensions = ConditionProfile.createBinaryProfile();
     private final ConditionProfile promiseInEnvironment = ConditionProfile.createBinaryProfile();
 
@@ -109,7 +112,7 @@ final class CachedExtractVectorNode extends CachedVectorNode {
         this.exact = logicalAsBoolean(exact, DEFAULT_EXACT);
         this.dropDimensions = logicalAsBoolean(dropDimensions, DEFAULT_DROP_DIMENSION);
         this.positionsCheckNode = new PositionsCheckNode(mode, vectorType, convertedPositions, this.exact, false, recursive);
-        if (error == null && vectorType != RType.Null && vectorType != RType.Environment) {
+        if (error == null && vectorType != RType.Null && vectorType != RType.Environment && vectorType != RType.S4Object) {
             this.writeVectorNode = WriteIndexedVectorNode.create(vectorType, convertedPositions.length, true, false, false, false);
         }
     }
@@ -148,6 +151,8 @@ final class CachedExtractVectorNode extends CachedVectorNode {
                  * later.
                  */
                 return doEnvironment((REnvironment) castVector, positions);
+            case S4Object:
+                return doS4Object((RS4Object) castVector, positions);
             case Integer:
                 vector = (RAbstractContainer) castVector;
                 break;
@@ -268,6 +273,14 @@ final class CachedExtractVectorNode extends CachedVectorNode {
         throw error(RError.Message.WRONG_ARGS_SUBSET_ENV);
     }
 
+    private Object doS4Object(RS4Object object, Object[] positions) {
+        if (extractS4ObjectNode == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            extractS4ObjectNode = insert(new ExtractS4ObjectNode(mode, exact, dropDimensions));
+        }
+        return extractS4ObjectNode.execute(object, positions);
+    }
+
     private boolean isMissingSingleDimension() {
         return numberOfDimensions == 1 && positionsCheckNode.isMissing();
     }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedReplaceVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedReplaceVectorNode.java
index 73b47fda388a25673352a3f2d033143354008629..40e456bf6c06a3da980ffc7cf9054e56d682aaef 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedReplaceVectorNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedReplaceVectorNode.java
@@ -57,6 +57,7 @@ import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.RMissing;
 import com.oracle.truffle.r.runtime.data.RNull;
 import com.oracle.truffle.r.runtime.data.RPairList;
+import com.oracle.truffle.r.runtime.data.RS4Object;
 import com.oracle.truffle.r.runtime.data.RScalarVector;
 import com.oracle.truffle.r.runtime.data.RShareable;
 import com.oracle.truffle.r.runtime.data.RStringVector;
@@ -102,7 +103,8 @@ final class CachedReplaceVectorNode extends CachedVectorNode {
     @Child private DeleteElementsNode deleteElementsNode;
     @Child private SetNamesAttributeNode setNamesNode;
 
-    CachedReplaceVectorNode(ElementAccessMode mode, RTypedValue vector, Object[] positions, Class<?> valueClass, RType valueType, boolean updatePositionNames, boolean recursive, boolean isValueGt1) {
+    CachedReplaceVectorNode(ElementAccessMode mode, RTypedValue vector, Object[] positions, Class<?> valueClass, RType valueType, boolean updatePositionNames, boolean recursive,
+                    boolean ignoreRecursive, boolean isValueGt1) {
         super(mode, vector, positions, recursive);
 
         if (numberOfDimensions == 1 && positions[0] instanceof String || positions[0] instanceof RAbstractStringVector) {
@@ -122,7 +124,9 @@ final class CachedReplaceVectorNode extends CachedVectorNode {
 
         Object[] convertedPositions = filterPositions(positions);
         this.positionsCheckNode = new PositionsCheckNode(mode, vectorType, convertedPositions, true, true, recursive);
-        if (castType != null && !castType.isNull()) {
+        if (vectorType == RType.S4Object) {
+            replaceS4ObjectNode = new ReplaceS4ObjectNode(mode, ignoreRecursive);
+        } else if (castType != null && !castType.isNull()) {
             this.writeVectorNode = WriteIndexedVectorNode.create(castType, convertedPositions.length, false, true, mode.isSubscript() && !isDeleteElements(), true);
         }
     }
@@ -149,6 +153,12 @@ final class CachedReplaceVectorNode extends CachedVectorNode {
         Object castVector = vectorClass.cast(originalVector);
         Object castValue = valueClass.cast(originalValues);
 
+        if (vectorType == RType.Environment) {
+            return doEnvironment((REnvironment) castVector, positions, castValue);
+        } else if (vectorType == RType.S4Object) {
+            return doS4Object((RS4Object) castVector, positions, castValue);
+        }
+
         Object value;
         if (valueType == RType.Null) {
             if (vectorType == RType.Null) {
@@ -197,8 +207,6 @@ final class CachedReplaceVectorNode extends CachedVectorNode {
             case PairList:
                 vector = ((RPairList) castVector).toRList();
                 break;
-            case Environment:
-                return doEnvironment((REnvironment) castVector, positions, castValue);
             case Language:
                 repType = RContext.getRRuntimeASTAccess().getRepType((RLanguage) castVector);
                 vector = RContext.getRRuntimeASTAccess().asList((RLanguage) castVector);
@@ -483,6 +491,12 @@ final class CachedReplaceVectorNode extends CachedVectorNode {
         return env;
     }
 
+    @Child private ReplaceS4ObjectNode replaceS4ObjectNode;
+
+    private Object doS4Object(RS4Object obj, Object[] positions, Object originalValues) {
+        return replaceS4ObjectNode.execute(obj, positions, originalValues);
+    }
+
     @NodeInfo(cost = NONE)
     public abstract static class ValueProfileNode extends Node {
 
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedVectorNode.java
index bc1808ef44f397cffccff97f64d796d2dfa87c38..595c1452ef73f1fe6d3acff813ca57342c6b85fa 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedVectorNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/CachedVectorNode.java
@@ -146,6 +146,7 @@ abstract class CachedVectorNode extends RBaseNode {
             case PairList:
             case Environment:
             case Expression:
+            case S4Object:
                 return true;
             default:
                 return false;
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ExtractS4ObjectNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ExtractS4ObjectNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..70df3131589553daab5ed53561f33f4a2fb20d76
--- /dev/null
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ExtractS4ObjectNode.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.nodes.access.vector;
+
+import static com.oracle.truffle.r.runtime.RError.Message.OP_NOT_DEFINED_FOR_S4_CLASS;
+
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.r.nodes.objects.GetS4DataSlot;
+import com.oracle.truffle.r.nodes.objects.GetS4DataSlotNodeGen;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RType;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RNull;
+import com.oracle.truffle.r.runtime.data.RS4Object;
+import com.oracle.truffle.r.runtime.data.RTypedValue;
+import com.oracle.truffle.r.runtime.data.model.RAbstractLogicalVector;
+
+public class ExtractS4ObjectNode extends Node {
+    @Child private GetS4DataSlot getS4DataSlotNode = GetS4DataSlotNodeGen.create(RType.Environment);
+    @Child private ExtractVectorNode extract;
+    private final boolean exact;
+    private final boolean dropDimensions;
+
+    public ExtractS4ObjectNode(ElementAccessMode accessMode, boolean exact, boolean dropDimensions) {
+        this.extract = ExtractVectorNode.create(accessMode, true);
+        this.exact = exact;
+        this.dropDimensions = dropDimensions;
+    }
+
+    public Object execute(RS4Object obj, Object[] positions) {
+        RTypedValue dataSlot = getS4DataSlotNode.executeObject(obj);
+        if (dataSlot == RNull.instance) {
+            throw RError.error(RError.SHOW_CALLER, OP_NOT_DEFINED_FOR_S4_CLASS, "$");
+        }
+        return extract.execute(dataSlot, positions, createLogical(exact), createLogical(dropDimensions));
+    }
+
+    private static RAbstractLogicalVector createLogical(boolean b) {
+        return RDataFactory.createLogicalVectorFromScalar(b);
+    }
+}
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceS4ObjectNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceS4ObjectNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..afc04f304e9af843de7d835e03a384c1e7808832
--- /dev/null
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceS4ObjectNode.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.nodes.access.vector;
+
+import static com.oracle.truffle.r.runtime.RError.Message.NO_METHOD_ASSIGNING_SUBSET_S4;
+
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.r.nodes.objects.GetS4DataSlot;
+import com.oracle.truffle.r.nodes.objects.GetS4DataSlotNodeGen;
+import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RType;
+import com.oracle.truffle.r.runtime.data.RNull;
+import com.oracle.truffle.r.runtime.data.RS4Object;
+import com.oracle.truffle.r.runtime.data.RTypedValue;
+
+public class ReplaceS4ObjectNode extends Node {
+    @Child private GetS4DataSlot getS4DataSlotNode = GetS4DataSlotNodeGen.create(RType.Environment);
+    @Child private ReplaceVectorNode replaceVectorNode;
+
+    public ReplaceS4ObjectNode(ElementAccessMode mode, boolean ignoreRecursive) {
+        replaceVectorNode = ReplaceVectorNode.create(mode, ignoreRecursive);
+    }
+
+    public Object execute(RS4Object obj, Object[] positions, Object values) {
+        RTypedValue dataSlot = getS4DataSlotNode.executeObject(obj);
+        if (dataSlot == RNull.instance) {
+            throw RError.error(RError.SHOW_CALLER, NO_METHOD_ASSIGNING_SUBSET_S4);
+        }
+        // No need to update the data slot, the value is env and they have reference semantics.
+        replaceVectorNode.execute(dataSlot, positions, values);
+        return obj;
+    }
+}
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceVectorNode.java
index cef08a1982e2309d3ad3e293ea3a8220dade57e3..f295281ded9fb2cd67094d7e21c4367b724dcc31 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceVectorNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/ReplaceVectorNode.java
@@ -189,7 +189,7 @@ public abstract class ReplaceVectorNode extends RBaseNode {
 
     protected static CachedReplaceVectorNode createDefaultCached(ReplaceVectorNode node, Object vector, Object[] positions, Object value) {
         return new CachedReplaceVectorNode(node.mode, (RTypedValue) vector, positions, value.getClass(), RRuntime.isForeignObject(value) ? RType.TruffleObject : ((RTypedValue) value).getRType(), true,
-                        node.recursive, CachedReplaceVectorNode.isValueLengthGreaterThanOne(value));
+                        node.recursive, node.ignoreRecursive, CachedReplaceVectorNode.isValueLengthGreaterThanOne(value));
     }
 
     public ElementAccessMode getMode() {
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/WriteIndexedVectorNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/WriteIndexedVectorNode.java
index ab744903fc74bb2b3cadb87d7d754738ed94428c..aecab8b403bb429c8b83a54b1f5f561f191b12bd 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/WriteIndexedVectorNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/vector/WriteIndexedVectorNode.java
@@ -373,7 +373,7 @@ abstract class WriteIndexedVectorNode extends Node {
             case List:
                 return new WriteListAction(setListElementAsObject, isReplace);
             default:
-                throw RInternalError.shouldNotReachHere();
+                throw RInternalError.shouldNotReachHere("WriteIndexedScalarNode for " + type);
         }
     }
 
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/ArgumentMatcher.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/ArgumentMatcher.java
index 5e5e1c83c8ccae8f9d443739fd9bb5b15b5681d9..c9baefdb00e4c3eaa15eb028458d5ee2468b3bd7 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/ArgumentMatcher.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/ArgumentMatcher.java
@@ -604,7 +604,8 @@ public class ArgumentMatcher {
                     }
                     if (!matchedSuppliedArgs[suppliedIndex]) {
                         String suppliedName = signature.getName(suppliedIndex);
-                        if (suppliedName == null || suppliedName.isEmpty() || formalSignature.getName(formalIndex).isEmpty()) {
+                        String formalName = formalSignature.getName(formalIndex);
+                        if (suppliedName == null || suppliedName.isEmpty() || formalName == null || formalName.isEmpty()) {
                             // unnamed parameter, match by position
                             break;
                         }
@@ -732,6 +733,10 @@ public class ArgumentMatcher {
 
     /**
      * Searches for partial match of suppliedName inside formalNames and returns its (formal) index.
+     * If there are no varagrs and no match is found for given named actual argument, then this
+     * method raises "unused argument" error. However, if there are any null formal arguments, which
+     * may only happen in the case of builtins, then we let the argument matching proceed to
+     * positional matching. This situation may happen in S4 dispatch to a builtin, e.g. {@code $<-}.
      *
      * @return The position of the given suppliedName inside the formalNames. Throws errors if the
      *         argument has been matched before
@@ -739,6 +744,7 @@ public class ArgumentMatcher {
     private static <T> int findParameterPositionByPartialName(ArgumentsSignature formalsSignature, boolean[] formalsMatchedByExactName, String suppliedName, int[] resultPermutation, int suppliedIndex,
                     boolean hasVarArgs, RBaseNode callingNode, int varArgIndex, IntFunction<String> errorString) {
         assert suppliedName != null && !suppliedName.isEmpty();
+        boolean hasNullFormal = false;
         int found = MatchPermutation.UNMATCHED;
         for (int i = 0; i < formalsSignature.getLength(); i++) {
             if (formalsMatchedByExactName[i]) {
@@ -747,6 +753,7 @@ public class ArgumentMatcher {
             }
 
             String formalName = formalsSignature.getName(i);
+            hasNullFormal |= formalName == null;
             if (formalName != null) {
                 if (formalName.startsWith(suppliedName) && ((varArgIndex != ArgumentsSignature.NO_VARARG && i < varArgIndex) || varArgIndex == ArgumentsSignature.NO_VARARG)) {
                     // partial-match only if the formal argument is positioned before ...
@@ -760,7 +767,7 @@ public class ArgumentMatcher {
                 }
             }
         }
-        if (found >= 0 || hasVarArgs) {
+        if (found >= 0 || hasVarArgs || hasNullFormal) {
             return found;
         }
         throw callingNode.error(RError.Message.UNUSED_ARGUMENT, errorString.apply(suppliedIndex));
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RCallNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RCallNode.java
index a0911e87fc0b1f6e02287cceb93bf9029f3308ec..19b503bd6b756266d92e884834bccd76bbdbe6fb 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RCallNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RCallNode.java
@@ -66,6 +66,7 @@ import com.oracle.truffle.r.nodes.function.S3FunctionLookupNode.Result;
 import com.oracle.truffle.r.nodes.function.call.CallRFunctionNode;
 import com.oracle.truffle.r.nodes.function.call.PrepareArguments;
 import com.oracle.truffle.r.nodes.function.visibility.SetVisibilityNode;
+import com.oracle.truffle.r.nodes.helpers.AccessListField;
 import com.oracle.truffle.r.nodes.profile.TruffleBoundaryNode;
 import com.oracle.truffle.r.nodes.profile.VectorLengthProfile;
 import com.oracle.truffle.r.runtime.Arguments;
@@ -151,6 +152,7 @@ public abstract class RCallNode extends RCallBaseNode implements RSyntaxNode, RS
 
     // needed for INTERNAL_GENERIC calls:
     @Child private FunctionDispatch internalDispatchCall;
+    @Child private AccessListField accessListField;
 
     protected RCaller createCaller(VirtualFrame frame, RFunction function) {
         if (explicitArgs == null) {
@@ -296,11 +298,13 @@ public abstract class RCallNode extends RCallBaseNode implements RSyntaxNode, RS
 
             if (isAttributableProfile.profile(dispatchObject instanceof RAttributeStorage) && isS4Profile.profile(((RAttributeStorage) dispatchObject).isS4())) {
                 RList list = (RList) promiseHelper.checkEvaluate(frame, REnvironment.getRegisteredNamespace("methods").get(".BasicFunsList"));
-                // TODO create a node that looks up the name in the names attribute
-                int index = list.getElementIndexByName(builtin.getName());
-                if (index != -1) {
-                    RFunction basicFun = (RFunction) list.getDataAt(index);
-                    Object result = internalDispatchCall.execute(frame, basicFun, lookupVarArgs(frame), null, null);
+                if (accessListField == null) {
+                    CompilerDirectives.transferToInterpreterAndInvalidate();
+                    accessListField = insert(AccessListField.create());
+                }
+                Object basicFun = accessListField.execute(list, builtin.getName());
+                if (basicFun != null) {
+                    Object result = internalDispatchCall.execute(frame, (RFunction) basicFun, lookupVarArgs(frame), null, null);
                     if (result != RRuntime.DEFERRED_DEFAULT_MARKER) {
                         return result;
                     }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/helpers/AccessListField.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/helpers/AccessListField.java
new file mode 100644
index 0000000000000000000000000000000000000000..3a9d5365527d8637acea1bb4c0f136e0081a86d7
--- /dev/null
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/helpers/AccessListField.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.nodes.helpers;
+
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.r.nodes.access.vector.ExtractListElement;
+import com.oracle.truffle.r.runtime.data.RList;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+
+public abstract class AccessListField extends ListFieldNodeBase {
+    @Child private ExtractListElement extractListElement = ExtractListElement.create();
+
+    public static AccessListField create() {
+        return AccessListFieldNodeGen.create();
+    }
+
+    public abstract Object execute(RList list, Object field);
+
+    @Specialization(limit = "2", guards = {"getNamesNode.getNames(list) == cachedNames", "field == cachedField"})
+    Object doList(RList list, @SuppressWarnings("unused") String field,
+                    @SuppressWarnings("unused") @Cached("list.getNames()") RStringVector cachedNames,
+                    @SuppressWarnings("unused") @Cached("field") String cachedField,
+                    @Cached("getIndex(cachedNames, field)") int index) {
+        if (index == -1) {
+            return null;
+        }
+        return extractListElement.execute(list, index);
+    }
+
+    @Specialization(replaces = "doList")
+    Object doListDynamic(RList list, String field) {
+        int index = getIndex(getNamesNode.getNames(list), field);
+        if (index == -1) {
+            return null;
+        }
+        return extractListElement.execute(list, index);
+    }
+}
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/helpers/ListFieldNodeBase.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/helpers/ListFieldNodeBase.java
new file mode 100644
index 0000000000000000000000000000000000000000..d35004865df29a277536d1f66fa28c6a5ad336e0
--- /dev/null
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/helpers/ListFieldNodeBase.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.nodes.helpers;
+
+import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.GetNamesAttributeNode;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+
+/**
+ * Common code shared between nodes accessing/updating list fields by name.
+ */
+public abstract class ListFieldNodeBase extends Node {
+    @Child protected GetNamesAttributeNode getNamesNode = GetNamesAttributeNode.create();
+
+    protected static int getIndex(RStringVector names, String field) {
+        if (names != null) {
+            int fieldHash = field.hashCode();
+            for (int i = 0; i < names.getLength(); i++) {
+                String current = names.getDataAt(i);
+                if (current == field || hashCodeEquals(current, fieldHash) && contentsEquals(current, field)) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    @TruffleBoundary
+    private static boolean contentsEquals(String current, String field) {
+        return field.equals(current);
+    }
+
+    @TruffleBoundary
+    private static boolean hashCodeEquals(String current, int fieldHash) {
+        return current.hashCode() == fieldHash;
+    }
+}
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/helpers/UpdateListField.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/helpers/UpdateListField.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4536f3561eea5dca299e143e801972fd3d3d6da
--- /dev/null
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/helpers/UpdateListField.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.nodes.helpers;
+
+import com.oracle.truffle.api.CompilerDirectives;
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.r.nodes.function.opt.ShareObjectNode;
+import com.oracle.truffle.r.runtime.data.RList;
+import com.oracle.truffle.r.runtime.data.RNull;
+import com.oracle.truffle.r.runtime.data.RStringVector;
+
+public abstract class UpdateListField extends ListFieldNodeBase {
+    @Child private ShareObjectNode shareObject = ShareObjectNode.create();
+
+    public abstract boolean execute(RList target, String field, Object value);
+
+    @Specialization(limit = "2", guards = {"getNamesNode.getNames(list) == cachedNames", "field == cachedField"})
+    boolean doList(RList list, @SuppressWarnings("unused") String field, Object value,
+                    @SuppressWarnings("unused") @Cached("list.getNames()") RStringVector cachedNames,
+                    @SuppressWarnings("unused") @Cached("field") String cachedField,
+                    @Cached("getIndex(cachedNames, field)") int index) {
+        assert value != RNull.instance && !(value instanceof RList) && value != null;
+        if (index == -1) {
+            return false;
+        }
+        Object sharedValue = value;
+        // share only when necessary:
+        if (list.getDataAt(index) != value) {
+            sharedValue = getShareObjectNode().execute(value);
+        }
+        list.setElement(index, sharedValue);
+        return true;
+    }
+
+    @Specialization(replaces = "doList", guards = {"list.getNames() != null"})
+    boolean doListDynamic(RList list, String field, Object value) {
+        int index = getIndex(getNamesNode.getNames(list), field);
+        if (index == -1) {
+            return false;
+        }
+        Object sharedValue = value;
+        // share only when necessary:
+        if (list.getDataAt(index) != value) {
+            sharedValue = getShareObjectNode().execute(value);
+        }
+        list.setElement(index, sharedValue);
+        return true;
+    }
+
+    private ShareObjectNode getShareObjectNode() {
+        if (shareObject == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            shareObject = insert(ShareObjectNode.create());
+        }
+        return shareObject;
+    }
+
+    public static UpdateListField create() {
+        return UpdateListFieldNodeGen.create();
+    }
+}
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/DispatchGeneric.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/DispatchGeneric.java
index 4098d1e5d501124ac77f8186066ce82ecb77dcb9..7b425b9f8fac56ae5a24ce3e17564e472dd0e378 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/DispatchGeneric.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/objects/DispatchGeneric.java
@@ -23,7 +23,9 @@ import com.oracle.truffle.api.profiles.ValueProfile;
 import com.oracle.truffle.r.nodes.RASTUtils;
 import com.oracle.truffle.r.nodes.access.variables.LocalReadVariableNode;
 import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
+import com.oracle.truffle.r.nodes.helpers.InheritsCheckNode;
 import com.oracle.truffle.r.runtime.RCaller;
+import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.context.RContext;
 import com.oracle.truffle.r.runtime.data.RFunction;
 import com.oracle.truffle.r.runtime.data.RStringVector;
@@ -39,6 +41,7 @@ public abstract class DispatchGeneric extends RBaseNode {
     private final BranchProfile equalsMethodRequired = BranchProfile.create();
     @Child private LoadMethod loadMethod = LoadMethodNodeGen.create();
     @Child private ExecuteMethod executeMethod = new ExecuteMethod();
+    @Child private InheritsCheckNode inheritsInternalDispatchCheckNode;
 
     @TruffleBoundary
     private static String createMultiDispatchString(RStringVector classes) {
@@ -75,9 +78,11 @@ public abstract class DispatchGeneric extends RBaseNode {
             RFunction currentFunction = ReadVariableNode.lookupFunction(".InheritForDispatch", methodsEnv.getFrame(), true, true);
             method = (RFunction) RContext.getEngine().evalFunction(currentFunction, frame.materialize(), RCaller.create(frame, RASTUtils.getOriginalCall(this)), true, null, classes, fdef, mtable);
         }
+        if (method.isBuiltin() || getInheritsInternalDispatchCheckNode().execute(method)) {
+            return RRuntime.DEFERRED_DEFAULT_MARKER;
+        }
         method = loadMethod.executeRFunction(frame, method, fname);
-        Object ret = executeMethod.executeObject(frame, method, fname);
-        return ret;
+        return executeMethod.executeObject(frame, method, fname);
     }
 
     @SuppressWarnings("unused")
@@ -116,4 +121,12 @@ public abstract class DispatchGeneric extends RBaseNode {
         }
         return false;
     }
+
+    private InheritsCheckNode getInheritsInternalDispatchCheckNode() {
+        if (inheritsInternalDispatchCheckNode == null) {
+            CompilerDirectives.transferToInterpreterAndInvalidate();
+            inheritsInternalDispatchCheckNode = insert(new InheritsCheckNode("internalDispatchMethod"));
+        }
+        return inheritsInternalDispatchCheckNode;
+    }
 }
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 b8abebdd2f56fc9ee65e451556b497bb2765297f..1253fd0cf72ca97ed5573ce9832a9b6545c36525 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
@@ -635,6 +635,7 @@ public final class RError extends RuntimeException implements TruffleException {
         POS_NOT_ALLOWED_WITH_NUMERIC("pos argument not allowed with a numeric value"),
         OBJ_CANNOT_BE_ATTRIBUTED("external object cannot be attributed"),
         CANNOT_COERCE_EXTERNAL_OBJECT_TO_VECTOR("no method for coercing this external object to a %s"),
+        NO_METHOD_ASSIGNING_SUBSET_S4("no method for assigning subsets of this S4 class"),
         CANNOT_COERCE_S4_TO_VECTOR("no method for coercing this S4 class to a vector"),
         // the following list is incomplete (but like GNU-R)
         INVALID_FORMAT_DOUBLE("invalid format '%s'; use format %%f, %%e, %%g or %%a for numeric objects"),
@@ -723,6 +724,7 @@ public final class RError extends RuntimeException implements TruffleException {
         RNG_SYMBOL("%s not found in user rng library"),
         CUMMAX_UNDEFINED_FOR_COMPLEX("'cummax' not defined for complex numbers"),
         CUMMIN_UNDEFINED_FOR_COMPLEX("'cummin' not defined for complex numbers"),
+        OP_NOT_DEFINED_FOR_S4_CLASS("%s operator not defined for this S4 class"),
         NMAX_LESS_THAN_ONE("'nmax' must be positive"),
         CHAR_VEC_ARGUMENT("a character vector argument expected"),
         QUOTE_G_ONE("only the first character of 'quote' will be used"),
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
index a427b2e44237e58209a85f48549e645fc59e6d1f..d0a65ff2df9f1741fc7bbb76308ea41b300ff3f0 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
@@ -467,6 +467,10 @@ Class "optionalMethod", by class "standardGeneric", distance 5
 #{ x<-42; y<-asS4(x); isS4(y) }
 [1] TRUE
 
+##com.oracle.truffle.r.test.S4.TestS4.testDispatchToS3ForBuiltins#
+#{ setClass('TestS4S31', representation(f = 'numeric')); p <- new('TestS4S31', f = 2); `$.TestS4S31` <- function(...) 42; p$field }
+[1] 42
+
 ##com.oracle.truffle.r.test.S4.TestS4.testInternalDispatch#
 #setClass('foo', representation(d='numeric')); setMethod(`$`, signature('foo'), function(x, name) 'FOO'); obj <- new('foo'); obj$asdf
 [1] "$"
@@ -604,6 +608,49 @@ Error in validObject(.Object) : invalid class “SingleInt” object: FALSE
 #{ setClass('WrappedIntVec', representation(n = 'numeric')); x0 <- new('WrappedIntVec', n = 1); x1 <- x0; x1@n <- 2; x0@n == x1@n }
 [1] FALSE
 
+##com.oracle.truffle.r.test.S4.TestS4.testRegularFieldAssign#
+#{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); attr(obj, '.Data') <- new.env(); obj$fld2 <- 'value'; list(obj, as.list(attr(obj, '.Data')), obj$fld2); }
+[[1]]
+An object of class "TestS4CornerCases"
+Slot "fld":
+[1] "xyz"
+
+
+[[2]]
+[[2]]$fld2
+[1] "value"
+
+
+[[3]]
+[1] "value"
+
+
+##com.oracle.truffle.r.test.S4.TestS4.testRegularFieldAssign#
+#{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); attr(obj, '.xData') <- new.env(); obj$fld2 <- 'value'; list(obj, as.list(attr(obj, '.xData')), obj$fld2); }
+[[1]]
+An object of class "TestS4CornerCases"
+Slot "fld":
+[1] "xyz"
+
+
+[[2]]
+[[2]]$fld2
+[1] "value"
+
+
+[[3]]
+[1] "value"
+
+
+##com.oracle.truffle.r.test.S4.TestS4.testRegularFieldAssign#Output.IgnoreErrorContext#
+#{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); obj$fld2 <- 'value'; }
+Error in `$<-`(`*tmp*`, fld2, value = "value") :
+  no method for assigning subsets of this S4 class
+
+##com.oracle.truffle.r.test.S4.TestS4.testRegularFieldAssign#Output.IgnoreErrorContext#
+#{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); obj$fld2; }
+Error in obj$fld2 : $ operator not defined for this S4 class
+
 ##com.oracle.truffle.r.test.S4.TestS4.testSlotAccess#Output.IgnoreErrorContext#
 # { x<-42; attr(x, "foo")<-7; x@foo }
 Error: trying to get slot "foo" from an object of a basic class ("numeric") with no slots
@@ -80639,6 +80686,12 @@ Error in rnorm(s = 1, s = 1) :
 #{ f <- match.fun(length) ; f(c(1,2,3)) }
 [1] 3
 
+##com.oracle.truffle.r.test.functions.TestFunctions.testMatching#
+#`$<-`(someNonesense = list(), anotherNonesense = 'foo', 42)
+$foo
+[1] 42
+
+
 ##com.oracle.truffle.r.test.functions.TestFunctions.testMatching#
 #list(`...`=NULL);
 $...
@@ -159442,10 +159495,6 @@ Error: 'R_get_primname' called on a non-primitive
 #.Call(methods:::C_R_set_method_dispatch, TRUE)
 [1] TRUE
 
-##com.oracle.truffle.r.test.library.stats.TestExternal_R_set_method_dispatch.testSetMethodDispatch#
-#.Call(methods:::C_R_set_method_dispatch, c(FALSE,TRUE))
-[1] TRUE
-
 ##com.oracle.truffle.r.test.library.stats.TestExternal_Rmd5.testRmd5#
 #.Call(tools:::Rmd5, "abc")
 Error in get(name, envir = asNamespace(pkg), inherits = FALSE) :
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 e054c71b60788326bb17fb7676cf485e902cb1da..d660c79947e21c355d7ccf9d95220e8a66c8e9a9 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
@@ -187,4 +187,17 @@ public class TestS4 extends TestRBase {
         assertEval("makeActiveBinding('someSymbol10', function(x) { if(missing(x)) print('get0') else print('set0'); NULL }, .GlobalEnv); someSymbol10; someSymbol10 <- 1; makeActiveBinding('someSymbol10', function(x) { if(missing(x)) print('get1') else print('set1'); NULL }, .GlobalEnv); someSymbol10; someSymbol10 <- 1");
         assertEval("makeActiveBinding('var_a', function(x) { if(missing(x)) { print('get'); return(123) } else { print('set'); return(x) } }, .GlobalEnv); inherits(var_a, 'numeric')");
     }
+
+    @Test
+    public void testRegularFieldAssign() {
+        assertEval(Output.IgnoreErrorContext, "{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); obj$fld2 <- 'value'; }");
+        assertEval(Output.IgnoreErrorContext, "{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); obj$fld2; }");
+        assertEval("{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); attr(obj, '.Data') <- new.env(); obj$fld2 <- 'value'; list(obj, as.list(attr(obj, '.Data')), obj$fld2); }");
+        assertEval("{ setClass('TestS4CornerCases', representation(fld = 'character'));  obj <- new('TestS4CornerCases', fld = 'xyz'); attr(obj, '.xData') <- new.env(); obj$fld2 <- 'value'; list(obj, as.list(attr(obj, '.xData')), obj$fld2); }");
+    }
+
+    @Test
+    public void testDispatchToS3ForBuiltins() {
+        assertEval("{ setClass('TestS4S31', representation(f = 'numeric')); p <- new('TestS4S31', f = 2); `$.TestS4S31` <- function(...) 42; p$field }");
+    }
 }
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/functions/TestFunctions.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/functions/TestFunctions.java
index 00c2f892ebbf33e9aaafe888689c8a07691ddfee..753308f58bab44b4d5610cdac02aae83f1ffce54 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/functions/TestFunctions.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/functions/TestFunctions.java
@@ -259,6 +259,7 @@ public class TestFunctions extends TestBase {
         // however, two partial matches produce error, even if one is "longer"
         assertEval("{ foo <- function(xaaa, ...) list(xaa=xaaa, ...); foo(xa=4,xaa=5); }");
         assertEval("list(`...`=NULL);");
+        assertEval("`$<-`(someNonesense = list(), anotherNonesense = 'foo', 42)");
     }
 
     @Test
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/stats/TestExternal_R_set_method_dispatch.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/stats/TestExternal_R_set_method_dispatch.java
index 732977bf7c1cd8b4c5e3368bb8ef3bac2f24d356..79019189b1d23941a26563dc2ac428395ecc4412 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/stats/TestExternal_R_set_method_dispatch.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/stats/TestExternal_R_set_method_dispatch.java
@@ -31,7 +31,6 @@ public class TestExternal_R_set_method_dispatch extends TestBase {
     @Test
     public void testSetMethodDispatch() {
         assertEval(".Call(methods:::C_R_set_method_dispatch, TRUE)");
-        assertEval(".Call(methods:::C_R_set_method_dispatch, c(FALSE,TRUE))");
         assertEval(".Call(methods:::C_R_set_method_dispatch, NULL)");
         assertEval(".Call(methods:::C_R_set_method_dispatch, 1)");
     }