diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementBlockNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementBlockNode.java
index 49ca3331fd2a9d03770957d1190e14d9a9b980e8..cab106e8d6d968585878eadc7fc7d24ffc6cfe7b 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementBlockNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementBlockNode.java
@@ -25,29 +25,22 @@ package com.oracle.truffle.r.nodes.control;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import com.oracle.truffle.api.CompilerAsserts;
 import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.frame.VirtualFrame;
-import com.oracle.truffle.api.nodes.ExplodeLoop;
 import com.oracle.truffle.api.nodes.Node;
 import com.oracle.truffle.api.source.SourceSection;
 import com.oracle.truffle.r.nodes.RASTUtils;
 import com.oracle.truffle.r.nodes.access.RemoveAndAnswerNode;
 import com.oracle.truffle.r.nodes.access.WriteVariableNode;
 import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
-import com.oracle.truffle.r.nodes.function.RCallSpecialNode;
-import com.oracle.truffle.r.nodes.function.RCallSpecialNode.RecursiveSpecialBailout;
 import com.oracle.truffle.r.nodes.function.visibility.SetVisibilityNode;
-import com.oracle.truffle.r.nodes.unary.GetNonSharedNodeGen;
 import com.oracle.truffle.r.runtime.ArgumentsSignature;
 import com.oracle.truffle.r.runtime.RError;
-import com.oracle.truffle.r.runtime.builtins.RSpecialFactory.FullCallNeededException;
-import com.oracle.truffle.r.runtime.context.RContext;
-import com.oracle.truffle.r.runtime.data.RDataFactory;
 import com.oracle.truffle.r.runtime.data.RLanguage;
 import com.oracle.truffle.r.runtime.data.RNull;
-import com.oracle.truffle.r.runtime.nodes.RNode;
 import com.oracle.truffle.r.runtime.nodes.RSourceSectionNode;
 import com.oracle.truffle.r.runtime.nodes.RSyntaxCall;
 import com.oracle.truffle.r.runtime.nodes.RSyntaxConstant;
@@ -62,17 +55,10 @@ import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
  * fallback to generic implementation.
  */
 public final class ReplacementBlockNode extends RSourceSectionNode implements RSyntaxNode, RSyntaxCall {
-    /**
-     * Used to initialize {@link #tempNamesStartIndex}. When we are processing a replacement AST, we
-     * set this value so that newly created replacements within the original replacement have
-     * different temporary variable names. Example {@code x[x[1]<-2]<-3}.
-     */
-    private static int tempNamesCount = 0;
 
-    /**
-     * Should be one of {@link ReplacementNode} or {@link ReplacementNodeSpecial}.
-     */
-    @Child private ReplacementBase replacementNode;
+    private static AtomicInteger rhsNameIndex = new AtomicInteger(-1);
+
+    @Child private ReplacementNode replacementNode;
 
     @Child private WriteVariableNode storeRhs;
     @Child private RemoveAndAnswerNode removeRhs;
@@ -81,15 +67,15 @@ public final class ReplacementBlockNode extends RSourceSectionNode implements RS
     private final String operator;
     private final RSyntaxNode lhs;
     private final boolean isSuper;
-    private final int tempNamesStartIndex;
+    private final String rhsName;
 
     public ReplacementBlockNode(SourceSection src, String operator, RSyntaxNode lhs, RSyntaxNode rhs, boolean isSuper) {
         super(src);
         assert "<-".equals(operator) || "<<-".equals(operator) || "=".equals(operator);
         assert lhs != null && rhs != null;
-        tempNamesStartIndex = tempNamesCount;
-        storeRhs = WriteVariableNode.createAnonymous(getRhsName(), rhs.asRNode(), WriteVariableNode.Mode.INVISIBLE);
-        removeRhs = RemoveAndAnswerNode.create(getRhsName());
+        rhsName = "*rhs*" + rhsNameIndex.incrementAndGet();
+        storeRhs = WriteVariableNode.createAnonymous(rhsName, rhs.asRNode(), WriteVariableNode.Mode.INVISIBLE);
+        removeRhs = RemoveAndAnswerNode.create(rhsName);
         this.operator = operator;
         this.lhs = lhs;
         this.isSuper = isSuper;
@@ -100,7 +86,8 @@ public final class ReplacementBlockNode extends RSourceSectionNode implements RS
         storeRhs.execute(frame);
         getReplacementNode().execute(frame);
         try {
-            return removeRhs.execute(frame);
+            Object result = removeRhs.execute(frame);
+            return result;
         } finally {
             visibility.execute(frame, false);
         }
@@ -135,23 +122,15 @@ public final class ReplacementBlockNode extends RSourceSectionNode implements RS
         return new RSyntaxElement[]{lhs, storeRhs.getRhs().asRSyntaxNode()};
     }
 
-    private RNode getReplacementNode() {
+    private ReplacementNode getReplacementNode() {
         if (replacementNode == null) {
             CompilerDirectives.transferToInterpreterAndInvalidate();
-            replacementNode = insert(createReplacementNode(true));
+            replacementNode = insert(createReplacementNode());
         }
         return replacementNode;
     }
 
-    private String getRhsName() {
-        return "*tmpr*" + (tempNamesStartIndex - 1);
-    }
-
-    private RNode createReplacementNodeWithoutSpecials() {
-        return createReplacementNode(false);
-    }
-
-    private ReplacementBase createReplacementNode(boolean useSpecials) {
+    private ReplacementNode createReplacementNode() {
         CompilerAsserts.neverPartOfCompilation();
 
         /*
@@ -160,7 +139,6 @@ public final class ReplacementBlockNode extends RSourceSectionNode implements RS
          */
         List<RSyntaxCall> calls = new ArrayList<>();
         RSyntaxElement current = lhs;
-        boolean onlySpecials = true;
         while (!(current instanceof RSyntaxLookup)) {
             if (!(current instanceof RSyntaxCall)) {
                 if (current instanceof RSyntaxConstant && ((RSyntaxConstant) current).getValue() == RNull.instance) {
@@ -176,69 +154,11 @@ public final class ReplacementBlockNode extends RSourceSectionNode implements RS
             if (call.getSyntaxArguments().length == 0 || !(syntaxLHS instanceof RSyntaxLookup || isNamespaceLookupCall(syntaxLHS))) {
                 throw RError.error(this, RError.Message.INVALID_NULL_LHS);
             }
-            onlySpecials &= call instanceof RCallSpecialNode;
             current = call.getSyntaxArguments()[0];
         }
         RSyntaxLookup variable = (RSyntaxLookup) current;
-        SourceSection source = getSourceSection();
-
-        // Note: if specials are turned off in FastR, onlySpecials will never be true
-        if (onlySpecials && useSpecials && !isSuper) {
-            return createSpecialReplacement(source, calls, variable);
-        }
-
-        return createGenericReplacement(source, calls, variable);
-    }
-
-    /**
-     * Creates a replacement that consists only of {@link RCallSpecialNode} calls.
-     */
-    private ReplacementBase createSpecialReplacement(SourceSection source, List<RSyntaxCall> calls, RSyntaxLookup variable) {
-        RNode extractFunc = createReplacementForVariableUsing(variable, isSuper);
-        for (int i = calls.size() - 1; i >= 1; i--) {
-            extractFunc = createSpecialFunctionQuery(extractFunc.asRSyntaxNode(), calls.get(i));
-        }
-        RNode updateFunc = createFunctionUpdate(source, extractFunc.asRSyntaxNode(), ReadVariableNode.create(getRhsName()), calls.get(0));
-        assert updateFunc instanceof RCallSpecialNode : "should be only specials";
-        return new ReplacementNodeSpecial((RCallSpecialNode) updateFunc);
-    }
-
-    /**
-     * When there are more than two function calls in LHS, then we save some function calls by
-     * saving the intermediate results into temporary variables and reusing them.
-     */
-    private ReplacementBase createGenericReplacement(SourceSection source, List<RSyntaxCall> calls, RSyntaxLookup variable) {
-        List<RNode> instructions = new ArrayList<>();
-        int tempNamesIndex = tempNamesStartIndex;
-        tempNamesCount += calls.size() + 1;
-
-        /*
-         * Create the calls that extract inner components - only needed for complex replacements
-         * like "a(b(x)) <- z" (where we would extract "b(x)").
-         */
-        for (int i = calls.size() - 1; i >= 1; i--) {
-            ReadVariableNode newLhs = ReadVariableNode.create("*tmp*" + (tempNamesIndex + i + 1));
-            RNode update = createSpecialFunctionQuery(newLhs, calls.get(i));
-            instructions.add(WriteVariableNode.createAnonymous("*tmp*" + (tempNamesIndex + i), update, WriteVariableNode.Mode.INVISIBLE));
-        }
-        /*
-         * Create the update calls, for "a(b(x)) <- z", this would be `a<-` and `b<-`.
-         */
-        for (int i = 0; i < calls.size(); i++) {
-            RNode update = createFunctionUpdate(source, ReadVariableNode.create("*tmp*" + (tempNamesIndex + i + 1)), ReadVariableNode.create("*tmpr*" + (tempNamesIndex + i - 1)),
-                            calls.get(i));
-            if (i < calls.size() - 1) {
-                instructions.add(WriteVariableNode.createAnonymous("*tmpr*" + (tempNamesIndex + i), update, WriteVariableNode.Mode.INVISIBLE));
-            } else {
-                instructions.add(WriteVariableNode.createAnonymous(variable.getIdentifier(), update, WriteVariableNode.Mode.REGULAR, isSuper));
-            }
-        }
-
-        ReadVariableNode variableValue = createReplacementForVariableUsing(variable, isSuper);
-        ReplacementNode newReplacementNode = new ReplacementNode(variableValue, "*tmp*" + (tempNamesIndex + calls.size()), instructions);
-
-        tempNamesCount -= calls.size() + 1;
-        return newReplacementNode;
+        ReadVariableNode varRead = createReplacementForVariableUsing(variable, isSuper);
+        return ReplacementNodeGen.create(getSourceSection(), calls, rhsName, variable.getIdentifier(), isSuper, varRead);
     }
 
     private static ReadVariableNode createReplacementForVariableUsing(RSyntaxLookup var, boolean isSuper) {
@@ -269,151 +189,21 @@ public final class ReplacementBlockNode extends RSourceSectionNode implements RS
         return false;
     }
 
-    /**
-     * Creates a call that looks like {@code fun} but has the first argument replaced with
-     * {@code newLhs}.
-     */
-    private static RNode createSpecialFunctionQuery(RSyntaxNode newLhs, RSyntaxCall fun) {
-        RSyntaxElement[] arguments = fun.getSyntaxArguments();
-
-        RSyntaxNode[] argNodes = new RSyntaxNode[arguments.length];
-        for (int i = 0; i < arguments.length; i++) {
-            argNodes[i] = i == 0 ? newLhs : process(arguments[i]);
-        }
-
-        return RCallSpecialNode.createCallInReplace(fun.getSourceSection(), process(fun.getSyntaxLHS()).asRNode(), fun.getSyntaxSignature(), argNodes).asRNode();
-    }
-
-    /**
-     * Creates a call that looks like {@code fun}, but has its first argument replaced with
-     * {@code newLhs}, its target turned into an update function ("foo<-"), with the given value
-     * added to the arguments list.
-     */
-    private static RNode createFunctionUpdate(SourceSection source, RSyntaxNode newLhs, RSyntaxNode rhs, RSyntaxCall fun) {
-        RSyntaxElement[] arguments = fun.getSyntaxArguments();
-
-        ArgumentsSignature signature = fun.getSyntaxSignature();
-        RSyntaxNode[] argNodes = new RSyntaxNode[arguments.length + 1];
-        String[] names = new String[argNodes.length];
-        for (int i = 0; i < arguments.length; i++) {
-            names[i] = signature.getName(i);
-            argNodes[i] = i == 0 ? newLhs : process(arguments[i]);
-        }
-        argNodes[argNodes.length - 1] = rhs;
-        names[argNodes.length - 1] = "value";
-
-        RSyntaxElement syntaxLHS = fun.getSyntaxLHS();
-        RSyntaxNode newSyntaxLHS;
-        if (syntaxLHS instanceof RSyntaxLookup) {
-            RSyntaxLookup lookupLHS = (RSyntaxLookup) syntaxLHS;
-            String symbol = lookupLHS.getIdentifier();
-            if ("slot".equals(symbol) || "@".equals(symbol)) {
-                // this is pretty gross, but at this point seems like the only way to get setClass
-                // to work properly
-                argNodes[0] = GetNonSharedNodeGen.create(argNodes[0].asRNode());
-            }
-            newSyntaxLHS = lookup(lookupLHS.getSourceSection(), symbol + "<-", true);
-        } else {
-            // data types (and lengths) are verified in isNamespaceLookupCall
-            RSyntaxCall callLHS = (RSyntaxCall) syntaxLHS;
-            RSyntaxElement[] oldArgs = callLHS.getSyntaxArguments();
-            RSyntaxNode[] newArgs = new RSyntaxNode[2];
-            newArgs[0] = (RSyntaxNode) oldArgs[0];
-            newArgs[1] = lookup(oldArgs[1].getSourceSection(), ((RSyntaxLookup) oldArgs[1]).getIdentifier() + "<-", true);
-            newSyntaxLHS = RCallSpecialNode.createCall(callLHS.getSourceSection(), ((RSyntaxNode) callLHS.getSyntaxLHS()).asRNode(), callLHS.getSyntaxSignature(), newArgs);
-        }
-        return RCallSpecialNode.createCall(source, newSyntaxLHS.asRNode(), ArgumentsSignature.get(names), argNodes).asRNode();
-    }
-
-    private static RSyntaxNode process(RSyntaxElement original) {
-        return RContext.getASTBuilder().process(original);
-    }
-
-    private static RSyntaxNode lookup(SourceSection source, String symbol, boolean functionLookup) {
-        return RContext.getASTBuilder().lookup(source, symbol, functionLookup);
-    }
-
     /*
      * Encapsulates check for the specific structure of replacements, to display the replacement
      * instead of the "internal" form (with *tmp*, etc.) of the update call.
      */
     public static RLanguage getRLanguage(RLanguage language) {
+        // TODO: fix me!
         RSyntaxNode sn = (RSyntaxNode) language.getRep();
         Node parent = RASTUtils.unwrapParent(sn.asNode());
         if (parent instanceof WriteVariableNode) {
             WriteVariableNode wvn = (WriteVariableNode) parent;
-            if (wvn.getParent() instanceof ReplacementBase) {
-                // the parent of Replacement interface is always ReplacementBlockNode
-                return RDataFactory.createLanguage((RNode) wvn.getParent().getParent());
-            }
+            return ReplacementNode.getLanguage(wvn);
         }
-
         return null;
     }
 
-    /**
-     * Base class for nodes implementing the actual replacement.
-     */
-    private abstract static class ReplacementBase extends RNode {
-    }
-
-    /**
-     * Replacement that is made of only special calls, if one of the special calls falls back to
-     * full version, the replacement also falls back to {@link ReplacementNode}.
-     */
-    private static final class ReplacementNodeSpecial extends ReplacementBase {
-
-        @Child private RCallSpecialNode replaceCall;
-
-        ReplacementNodeSpecial(RCallSpecialNode replaceCall) {
-            this.replaceCall = replaceCall;
-            this.replaceCall.setPropagateFullCallNeededException(true);
-        }
-
-        @Override
-        public Object execute(VirtualFrame frame) {
-            try {
-                // Note: the very last call is the actual assignment, e.g. [[<-, if this call's
-                // argument is shared, it bails out. Moreover, if that call's argument is not
-                // shared, it could not be extracted from a shared container (list), so we should be
-                // OK with not calling any other update function and just update the value directly.
-                replaceCall.execute(frame);
-            } catch (FullCallNeededException | RecursiveSpecialBailout e) {
-                assert getParent() instanceof ReplacementBlockNode;
-                RNode newReplacement = ((ReplacementBlockNode) getParent()).createReplacementNodeWithoutSpecials();
-                return replace(newReplacement).execute(frame);
-            }
-            return null;
-        }
-    }
-
-    /**
-     * Holds the sequence of nodes created for R's replacement assignment.
-     */
-    public static final class ReplacementNode extends ReplacementBase {
-
-        @Child private WriteVariableNode storeValue;
-        @Children private final RNode[] updates;
-        @Child private RemoveAndAnswerNode removeTemp;
-
-        public ReplacementNode(RNode variable, String tmpSymbol, List<RNode> updates) {
-            this.storeValue = WriteVariableNode.createAnonymous(tmpSymbol, variable, WriteVariableNode.Mode.INVISIBLE);
-            this.updates = updates.toArray(new RNode[updates.size()]);
-            this.removeTemp = RemoveAndAnswerNode.create(tmpSymbol);
-        }
-
-        @Override
-        @ExplodeLoop
-        public Object execute(VirtualFrame frame) {
-            storeValue.execute(frame);
-            for (RNode update : updates) {
-                update.execute(frame);
-            }
-            removeTemp.execute(frame);
-            return null;
-        }
-    }
-
     /**
      * Used by the parser for assignments that miss a left hand side. This node will raise an error
      * once executed.
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..5175306e093570627ebb05a5004a4f0607f46953
--- /dev/null
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/control/ReplacementNode.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (c) 2016, 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.control;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.oracle.truffle.api.CompilerAsserts;
+import com.oracle.truffle.api.dsl.Cached;
+import com.oracle.truffle.api.dsl.NodeChild;
+import com.oracle.truffle.api.dsl.Specialization;
+import com.oracle.truffle.api.dsl.TypeSystemReference;
+import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.ExplodeLoop;
+import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.source.SourceSection;
+import com.oracle.truffle.r.nodes.EmptyTypeSystemFlatLayout;
+import com.oracle.truffle.r.nodes.access.RemoveAndAnswerNode;
+import com.oracle.truffle.r.nodes.access.WriteVariableNode;
+import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
+import com.oracle.truffle.r.nodes.function.RCallSpecialNode;
+import com.oracle.truffle.r.nodes.function.RCallSpecialNode.RecursiveSpecialBailout;
+import com.oracle.truffle.r.nodes.unary.GetNonSharedNodeGen;
+import com.oracle.truffle.r.runtime.ArgumentsSignature;
+import com.oracle.truffle.r.runtime.builtins.RSpecialFactory.FullCallNeededException;
+import com.oracle.truffle.r.runtime.context.RContext;
+import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RLanguage;
+import com.oracle.truffle.r.runtime.nodes.RNode;
+import com.oracle.truffle.r.runtime.nodes.RSyntaxCall;
+import com.oracle.truffle.r.runtime.nodes.RSyntaxElement;
+import com.oracle.truffle.r.runtime.nodes.RSyntaxLookup;
+import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
+
+@NodeChild(value = "target")
+@TypeSystemReference(EmptyTypeSystemFlatLayout.class)
+abstract class ReplacementNode extends RNode {
+
+    /**
+     * Used to initialize {@link #tempNamesStartIndex}. When we are processing a replacement AST, we
+     * set this value so that newly created replacements within the original replacement have
+     * different temporary variable names. Example {@code x[x[1]<-2]<-3}.
+     */
+    public static int tempNamesCount = 0;
+
+    private final int tempNamesStartIndex;
+    private final SourceSection source;
+    private final List<RSyntaxCall> calls;
+    private final String targetVarName;
+    private final String rhsVarName;
+    private final boolean isSuper;
+
+    ReplacementNode(SourceSection source, List<RSyntaxCall> calls, String rhsVarName, String targetVarName, boolean isSuper) {
+        this.source = source;
+        this.calls = calls;
+        this.rhsVarName = rhsVarName;
+        this.targetVarName = targetVarName;
+        this.isSuper = isSuper;
+        tempNamesStartIndex = tempNamesCount;
+    }
+
+    @Specialization
+    protected Object doRObject(VirtualFrame frame, Object target,
+                    @Cached("createTargetTmpWrite()") WriteVariableNode targetTmpWrite,
+                    @Cached("createTargetTmpRemove()") RemoveAndAnswerNode targetTmpRemove,
+                    @Cached("createTargetWrite()") WriteVariableNode targetWrite,
+                    @Cached("createReplacementNode()") ReplacementBase replacement) {
+        targetTmpWrite.execute(frame, target);
+        replacement.execute(frame);
+        targetWrite.execute(frame, targetTmpRemove.execute(frame));
+        return null;
+    }
+
+    protected final WriteVariableNode createTargetTmpWrite() {
+        return WriteVariableNode.createAnonymous(getTargetTmpName(), null, WriteVariableNode.Mode.INVISIBLE);
+    }
+
+    protected final WriteVariableNode createTargetWrite() {
+        return WriteVariableNode.createAnonymous(targetVarName, null, WriteVariableNode.Mode.INVISIBLE, isSuper);
+    }
+
+    protected final RemoveAndAnswerNode createTargetTmpRemove() {
+        return RemoveAndAnswerNode.create(getTargetTmpName());
+    }
+
+    protected final ReplacementBase createReplacementNode() {
+        return createReplacementNode(true);
+    }
+
+    private RNode createReplacementNodeWithoutSpecials() {
+        return createReplacementNode(false);
+    }
+
+    private ReplacementBase createReplacementNode(boolean useSpecials) {
+        CompilerAsserts.neverPartOfCompilation();
+        // Note: if specials are turned off in FastR, onlySpecials will never be true
+        boolean createSpecial = hasOnlySpecialCalls() && useSpecials && !isSuper;
+        return createSpecial ? createSpecialReplacement(source, calls) : createGenericReplacement(source, calls);
+    }
+
+    private String getTargetTmpName() {
+        return "*tmp*" + tempNamesStartIndex;
+    }
+
+    private boolean hasOnlySpecialCalls() {
+        for (int i = 0; i < calls.size(); i++) {
+            if (!(calls.get(i) instanceof RCallSpecialNode)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Creates a replacement that consists only of {@link RCallSpecialNode} calls.
+     */
+    private SpecialReplacementNode createSpecialReplacement(SourceSection source, List<RSyntaxCall> calls) {
+        RNode extractFunc = ReadVariableNode.create(getTargetTmpName());
+        for (int i = calls.size() - 1; i >= 1; i--) {
+            extractFunc = createSpecialFunctionQuery(extractFunc.asRSyntaxNode(), calls.get(i));
+        }
+        RNode updateFunc = createFunctionUpdate(source, extractFunc.asRSyntaxNode(), ReadVariableNode.create(rhsVarName), calls.get(0));
+        assert updateFunc instanceof RCallSpecialNode : "should be only specials";
+        return new SpecialReplacementNode((RCallSpecialNode) updateFunc);
+    }
+
+    /**
+     * When there are more than two function calls in LHS, then we save some function calls by
+     * saving the intermediate results into temporary variables and reusing them.
+     */
+    private GenericReplacementNode createGenericReplacement(SourceSection source, List<RSyntaxCall> calls) {
+        List<RNode> instructions = new ArrayList<>();
+        tempNamesCount += tempNamesStartIndex + calls.size() + 1;
+
+        /*
+         * Create the calls that extract inner components - only needed for complex replacements
+         * like "a(b(x)) <- z" (where we would extract "b(x)"). The extracted values are saved into
+         * temporary variables *tmp*{index} indexed from tempNamesIndex to (tempNamesIndex +
+         * calls.size()-1), the first such temporary variable holds the "target" of the replacement,
+         * 'x' in our example (the assignment from 'x' is not done in this loop).
+         */
+        for (int i = calls.size() - 1, tmpIndex = 0; i >= 1; i--, tmpIndex++) {
+            ReadVariableNode newLhs = ReadVariableNode.create("*tmp*" + (tempNamesStartIndex + tmpIndex));
+            RNode update = createSpecialFunctionQuery(newLhs, calls.get(i));
+            instructions.add(WriteVariableNode.createAnonymous("*tmp*" + (tempNamesStartIndex + tmpIndex + 1), update, WriteVariableNode.Mode.INVISIBLE));
+        }
+        /*
+         * Create the update calls, for "a(b(x)) <- z", this would be `a<-` and `b<-`, the
+         * intermediate results are stored to temporary variables *tmpr*{index}.
+         */
+        for (int i = 0; i < calls.size(); i++) {
+            int tmpIndex = tempNamesStartIndex + calls.size() - i - 1;
+            String tmprName = i == 0 ? rhsVarName : "*tmpr*" + (tempNamesStartIndex + i - 1);
+            RNode update = createFunctionUpdate(source, ReadVariableNode.create("*tmp*" + tmpIndex), ReadVariableNode.create(tmprName), calls.get(i));
+            if (i < calls.size() - 1) {
+                instructions.add(WriteVariableNode.createAnonymous("*tmpr*" + (tempNamesStartIndex + i), update, WriteVariableNode.Mode.INVISIBLE));
+            } else {
+                instructions.add(WriteVariableNode.createAnonymous(getTargetTmpName(), update, WriteVariableNode.Mode.REGULAR));
+            }
+        }
+
+        GenericReplacementNode newReplacementNode = new GenericReplacementNode(instructions);
+        tempNamesCount -= calls.size() + 1;
+        return newReplacementNode;
+    }
+
+    /**
+     * Creates a call that looks like {@code fun} but has the first argument replaced with
+     * {@code newLhs}.
+     */
+    private static RNode createSpecialFunctionQuery(RSyntaxNode newLhs, RSyntaxCall fun) {
+        RSyntaxElement[] arguments = fun.getSyntaxArguments();
+
+        RSyntaxNode[] argNodes = new RSyntaxNode[arguments.length];
+        for (int i = 0; i < arguments.length; i++) {
+            argNodes[i] = i == 0 ? newLhs : process(arguments[i]);
+        }
+
+        return RCallSpecialNode.createCallInReplace(fun.getSourceSection(), process(fun.getSyntaxLHS()).asRNode(), fun.getSyntaxSignature(), argNodes).asRNode();
+    }
+
+    /**
+     * Creates a call that looks like {@code fun}, but has its first argument replaced with
+     * {@code newLhs}, its target turned into an update function ("foo<-"), with the given value
+     * added to the arguments list.
+     */
+    private static RNode createFunctionUpdate(SourceSection source, RSyntaxNode newLhs, RSyntaxNode rhs, RSyntaxCall fun) {
+        RSyntaxElement[] arguments = fun.getSyntaxArguments();
+
+        ArgumentsSignature signature = fun.getSyntaxSignature();
+        RSyntaxNode[] argNodes = new RSyntaxNode[arguments.length + 1];
+        String[] names = new String[argNodes.length];
+        for (int i = 0; i < arguments.length; i++) {
+            names[i] = signature.getName(i);
+            argNodes[i] = i == 0 ? newLhs : process(arguments[i]);
+        }
+        argNodes[argNodes.length - 1] = rhs;
+        names[argNodes.length - 1] = "value";
+
+        RSyntaxElement syntaxLHS = fun.getSyntaxLHS();
+        RSyntaxNode newSyntaxLHS;
+        if (syntaxLHS instanceof RSyntaxLookup) {
+            RSyntaxLookup lookupLHS = (RSyntaxLookup) syntaxLHS;
+            String symbol = lookupLHS.getIdentifier();
+            if ("slot".equals(symbol) || "@".equals(symbol)) {
+                // this is pretty gross, but at this point seems like the only way to get setClass
+                // to work properly
+                argNodes[0] = GetNonSharedNodeGen.create(argNodes[0].asRNode());
+            }
+            newSyntaxLHS = lookup(lookupLHS.getSourceSection(), symbol + "<-", true);
+        } else {
+            // data types (and lengths) are verified in isNamespaceLookupCall
+            RSyntaxCall callLHS = (RSyntaxCall) syntaxLHS;
+            RSyntaxElement[] oldArgs = callLHS.getSyntaxArguments();
+            RSyntaxNode[] newArgs = new RSyntaxNode[2];
+            newArgs[0] = (RSyntaxNode) oldArgs[0];
+            newArgs[1] = lookup(oldArgs[1].getSourceSection(), ((RSyntaxLookup) oldArgs[1]).getIdentifier() + "<-", true);
+            newSyntaxLHS = RCallSpecialNode.createCall(callLHS.getSourceSection(), ((RSyntaxNode) callLHS.getSyntaxLHS()).asRNode(), callLHS.getSyntaxSignature(), newArgs);
+        }
+        return RCallSpecialNode.createCall(source, newSyntaxLHS.asRNode(), ArgumentsSignature.get(names), argNodes).asRNode();
+    }
+
+    private static RSyntaxNode process(RSyntaxElement original) {
+        return RContext.getASTBuilder().process(original);
+    }
+
+    private static RSyntaxNode lookup(SourceSection source, String symbol, boolean functionLookup) {
+        return RContext.getASTBuilder().lookup(source, symbol, functionLookup);
+    }
+
+    static RLanguage getLanguage(WriteVariableNode wvn) {
+        Node parent = wvn.getParent();
+        if (parent instanceof ReplacementBase) {
+            Node replacementBlock = ((ReplacementBase) parent).getReplacementNodeParent().getParent();
+            assert replacementBlock instanceof ReplacementBlockNode;
+            return RDataFactory.createLanguage((RNode) replacementBlock);
+        }
+        return null;
+    }
+
+    /**
+     * Base class for nodes implementing the actual replacement.
+     */
+    protected abstract static class ReplacementBase extends RNode {
+        protected final ReplacementNode getReplacementNodeParent() {
+            // Note: new DSL puts another node in between ReplacementBase instance and
+            // ReplacementNode, to be flexible we traverse the parents until we reach it
+            Node current = this;
+            do {
+                current = current.getParent();
+            } while (!(current instanceof ReplacementNode));
+            return (ReplacementNode) current;
+        }
+    }
+
+    /**
+     * Replacement that is made of only special calls, if one of the special calls falls back to
+     * full version, the replacement also falls back to {@link ReplacementNode}.
+     */
+    private static final class SpecialReplacementNode extends ReplacementBase {
+
+        @Child private RCallSpecialNode replaceCall;
+
+        SpecialReplacementNode(RCallSpecialNode replaceCall) {
+            this.replaceCall = replaceCall;
+            this.replaceCall.setPropagateFullCallNeededException(true);
+        }
+
+        @Override
+        public Object execute(VirtualFrame frame) {
+            try {
+                // Note: the very last call is the actual assignment, e.g. [[<-, if this call's
+                // argument is shared, it bails out. Moreover, if that call's argument is not
+                // shared, it could not be extracted from a shared container (list), so we should be
+                // OK with not calling any other update function and just update the value directly.
+                replaceCall.execute(frame);
+            } catch (FullCallNeededException | RecursiveSpecialBailout e) {
+                RNode newReplacement = getReplacementNodeParent().createReplacementNodeWithoutSpecials();
+                return replace(newReplacement).execute(frame);
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Holds the sequence of nodes created for R's replacement assignment.
+     */
+    private static final class GenericReplacementNode extends ReplacementBase {
+        @Children private final RNode[] updates;
+
+        GenericReplacementNode(List<RNode> updates) {
+            this.updates = updates.toArray(new RNode[updates.size()]);
+        }
+
+        @Override
+        @ExplodeLoop
+        public Object execute(VirtualFrame frame) {
+            for (RNode update : updates) {
+                update.execute(frame);
+            }
+            return null;
+        }
+    }
+}