diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Call.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Call.java
index 5a7652c91b1d4b84bd89298d2422ee1cd6eb0189..ed7e87edb2e586f2f863dda24acc486d091afb84 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Call.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Call.java
@@ -28,6 +28,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.dsl.Fallback;
 import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.r.nodes.RASTUtils;
+import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
 import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
 import com.oracle.truffle.r.runtime.ArgumentsSignature;
 import com.oracle.truffle.r.runtime.RBuiltin;
@@ -66,17 +67,12 @@ public abstract class Call extends RBuiltinNode {
 
     @TruffleBoundary
     private static RLanguage makeCall(String name, RArgsValuesAndNames args) {
-        return makeCall0(name, false, args);
-    }
-
-    @TruffleBoundary
-    private static RLanguage makeCall(RFunction function, RArgsValuesAndNames args) {
-        return makeCall0(function, false, args);
+        return makeCall0(ReadVariableNode.createFunctionLookup(RSyntaxNode.EAGER_DEPARSE, name), false, args);
     }
 
     @TruffleBoundary
     protected static RLanguage makeCallSourceUnavailable(String name, RArgsValuesAndNames args) {
-        return makeCall0(name, true, args);
+        return makeCall0(ReadVariableNode.createFunctionLookup(RSyntaxNode.EAGER_DEPARSE, name), true, args);
     }
 
     @TruffleBoundary
@@ -92,6 +88,7 @@ public abstract class Call extends RBuiltinNode {
      */
     @TruffleBoundary
     private static RLanguage makeCall0(Object fn, boolean sourceUnavailable, RArgsValuesAndNames argsAndNames) {
+        assert !(fn instanceof String);
         int argLength = argsAndNames == null ? 0 : argsAndNames.getLength();
         RSyntaxNode[] args = new RSyntaxNode[argLength];
         Object[] values = argsAndNames == null ? null : argsAndNames.getArguments();
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/S3DispatchFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/S3DispatchFunctions.java
index 8bcf277f71c80bf715ad75b3809d4370326e4fcd..973ab1d8d24b20fc25cfc83da64e1ed292e5d173 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/S3DispatchFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/S3DispatchFunctions.java
@@ -155,7 +155,7 @@ public abstract class S3DispatchFunctions extends RBuiltinNode {
             return promiseCheckHelper.checkEvaluate(frame, enclosingArg);
         }
 
-        private Object getFirstNonMissingArg(VirtualFrame frame, int startIdx) {
+        private static Object getFirstNonMissingArg(VirtualFrame frame, int startIdx) {
             for (int i = startIdx; i < RArguments.getArgumentsLength(frame); i++) {
                 Object arg = RArguments.getArgument(frame, i);
                 if (arg instanceof RArgsValuesAndNames) {
@@ -167,7 +167,7 @@ public abstract class S3DispatchFunctions extends RBuiltinNode {
             return null;
         }
 
-        private Object getFirstVarArg(RArgsValuesAndNames varArgs) {
+        private static Object getFirstVarArg(RArgsValuesAndNames varArgs) {
             return varArgs.isEmpty() ? null : varArgs.getArgument(0);
         }
     }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttr.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttr.java
index 746b59fe0ee6ec33372bfb7ed9f63b9faa3bbf33..5e9ec273085f2326f29af4ef0c1840b2c0b72ba8 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttr.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttr.java
@@ -40,7 +40,6 @@ import com.oracle.truffle.r.runtime.RBuiltin;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RError.Message;
 import com.oracle.truffle.r.runtime.RRuntime;
-import com.oracle.truffle.r.runtime.RVisibility;
 import com.oracle.truffle.r.runtime.data.RAttributable;
 import com.oracle.truffle.r.runtime.data.RAttributeProfiles;
 import com.oracle.truffle.r.runtime.data.RDataFactory;
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttributes.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttributes.java
index 63ea192dae31a78996d4c6c15daa96a5eb0210ee..8561c8a3ae9128fc2bee70af49475caca1ab8652 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttributes.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateAttributes.java
@@ -38,7 +38,6 @@ import com.oracle.truffle.r.nodes.unary.CastToVectorNodeGen;
 import com.oracle.truffle.r.runtime.RBuiltin;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RRuntime;
-import com.oracle.truffle.r.runtime.RVisibility;
 import com.oracle.truffle.r.runtime.data.RAttributable;
 import com.oracle.truffle.r.runtime.data.RAttributeProfiles;
 import com.oracle.truffle.r.runtime.data.RList;
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateDim.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateDim.java
index 6f8f43ac1550396976f0aef97a84e83caf28a977..73a903458df5bf8022897a21a3c5147503a8d751 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateDim.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateDim.java
@@ -32,7 +32,6 @@ import com.oracle.truffle.r.nodes.function.opt.ReuseNonSharedNode;
 import com.oracle.truffle.r.nodes.unary.CastIntegerNode;
 import com.oracle.truffle.r.runtime.RBuiltin;
 import com.oracle.truffle.r.runtime.RError;
-import com.oracle.truffle.r.runtime.RVisibility;
 import com.oracle.truffle.r.runtime.data.RNull;
 import com.oracle.truffle.r.runtime.data.RVector;
 import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateDimNames.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateDimNames.java
index fe00dd8e0b7dc7873484e444fb9b9200444500f7..d91d4a8fbbe67167fb4ef437b99865e6abca9fe0 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateDimNames.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/UpdateDimNames.java
@@ -38,7 +38,6 @@ import com.oracle.truffle.r.nodes.unary.CastToVectorNodeGen;
 import com.oracle.truffle.r.runtime.RBuiltin;
 import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RRuntime;
-import com.oracle.truffle.r.runtime.RVisibility;
 import com.oracle.truffle.r.runtime.data.RAttributes;
 import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.RNull;
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RASTUtils.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RASTUtils.java
index 468a38c94047b395513141e9baafccf2f1bded1d..098ec29439080109c0b0c614fb0f34e0e2546096 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RASTUtils.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RASTUtils.java
@@ -227,20 +227,15 @@ public class RASTUtils {
             fn = ((ConstantNode) fn).getValue();
         }
         SourceSection sourceSection = sourceUnavailable ? RSyntaxNode.SOURCE_UNAVAILABLE : RSyntaxNode.EAGER_DEPARSE;
-        if (fn instanceof String) {
-            return RCallNode.createCall(sourceSection, RASTUtils.createReadVariableNode(((String) fn)), signature, arguments);
-        } else if (fn instanceof ReadVariableNode) {
+        if (fn instanceof ReadVariableNode) {
             return RCallNode.createCall(sourceSection, (ReadVariableNode) fn, signature, arguments);
         } else if (fn instanceof NamedRNode) {
             return RCallNode.createCall(RSyntaxNode.SOURCE_UNAVAILABLE, (NamedRNode) fn, signature, arguments);
-        } else if (fn instanceof RFunction) {
-            RFunction rfn = (RFunction) fn;
-            return RCallNode.createCall(sourceSection, ConstantNode.create(rfn), signature, arguments);
         } else if (fn instanceof RCallNode) {
             return RCallNode.createCall(sourceSection, (RCallNode) fn, signature, arguments);
         } else {
-            // this of course would not make much sense if trying to evaluate this call, yet it's
-            // syntactically possible, for example as a result of:
+            // apart from RFunction, this of course would not make much sense if trying to evaluate
+            // this call, yet it's syntactically possible, for example as a result of:
             // f<-function(x,y) sys.call(); x<-f(7, 42); x[c(2,3)]
             return RCallNode.createCall(sourceSection, ConstantNode.create(fn), signature, arguments);
         }
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 69062c1655dec49aa44f39b57d06833321cd102f..aa919f2e6f4d7e37624ddeed5e6b753b343f5f4b 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
@@ -62,9 +62,9 @@ import com.oracle.truffle.r.runtime.nodes.RNode;
  * <p>
  * {@link ArgumentMatcher} serves the purpose of matching {@link CallArgumentsNode} to
  * {@link FormalArguments} of a specific function, see
- * {@link #matchArguments(RRootNode, UnmatchedArguments, RBaseNode, boolean)} . The other match
- * functions are used for special cases, where builtins make it necessary to re-match parameters,
- * e.g.:
+ * {@link #matchArguments(RRootNode, UnmatchedArguments, S3DefaultArguments, RBaseNode, boolean)} .
+ * The other match functions are used for special cases, where builtins make it necessary to
+ * re-match parameters, e.g.:
  * {@link #matchArgumentsEvaluated(RRootNode, RArgsValuesAndNames, S3DefaultArguments, boolean, RBaseNode)}
  * for 'UseMethod'.
  * </p>
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RCallerHelper.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RCallerHelper.java
index ae91e5ca92e025d130c5df8032d301b55041bd42..13b9a157c77574f6ee21f603f8d72fa7281aebad 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RCallerHelper.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/function/RCallerHelper.java
@@ -26,9 +26,11 @@ import java.util.function.Supplier;
 
 import com.oracle.truffle.r.nodes.RASTUtils;
 import com.oracle.truffle.r.nodes.access.ConstantNode;
+import com.oracle.truffle.r.nodes.access.variables.ReadVariableNode;
 import com.oracle.truffle.r.runtime.ArgumentsSignature;
 import com.oracle.truffle.r.runtime.RCaller;
 import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
+import com.oracle.truffle.r.runtime.data.RFunction;
 import com.oracle.truffle.r.runtime.data.RMissing;
 import com.oracle.truffle.r.runtime.data.RPromise;
 import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
@@ -51,7 +53,18 @@ public final class RCallerHelper {
      * @param arguments array with arguments and corresponding names. This method strips any
      *            {@code RMissing} arguments and unrolls all varargs withint arguments array.
      */
-    public static Supplier<RSyntaxNode> createFromArguments(final Object function, final RArgsValuesAndNames arguments) {
+    public static Supplier<RSyntaxNode> createFromArguments(RFunction function, RArgsValuesAndNames arguments) {
+        return createFromArgumentsInternal(function, arguments);
+    }
+
+    /**
+     * @see #createFromArguments(RFunction, RArgsValuesAndNames)
+     */
+    public static Supplier<RSyntaxNode> createFromArguments(String function, RArgsValuesAndNames arguments) {
+        return createFromArgumentsInternal(function, arguments);
+    }
+
+    public static Supplier<RSyntaxNode> createFromArgumentsInternal(final Object function, final RArgsValuesAndNames arguments) {
         return new Supplier<RSyntaxNode>() {
 
             RSyntaxNode syntaxNode = null;
@@ -87,7 +100,8 @@ public final class RCallerHelper {
                             index++;
                         }
                     }
-                    syntaxNode = RASTUtils.createCall(function, true, ArgumentsSignature.get(signature), syntaxArguments);
+                    Object replacedFunction = function instanceof String ? ReadVariableNode.createFunctionLookup(RSyntaxNode.EAGER_DEPARSE, (String) function) : function;
+                    syntaxNode = RASTUtils.createCall(replacedFunction, true, ArgumentsSignature.get(signature), syntaxArguments);
                 }
                 return syntaxNode;
             }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSubstitute.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSubstitute.java
index 72e39909c7fa7747e7c51128393dc61f129d28ae..2311952c672034262afba9cc3be67c3675ad88bd 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSubstitute.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSubstitute.java
@@ -23,6 +23,7 @@
 package com.oracle.truffle.r.runtime;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.r.runtime.RError.Message;
@@ -72,6 +73,10 @@ public class RSubstitute {
         }
     }
 
+    private static boolean isLookup(RSyntaxElement element, String identifier) {
+        return element instanceof RSyntaxLookup && identifier.equals(((RSyntaxLookup) element).getIdentifier());
+    }
+
     /**
      * This method returns a newly created AST fragment for the given original element, with the
      * given substitutions.<br/>
@@ -87,8 +92,31 @@ public class RSubstitute {
 
             @Override
             protected T visit(RSyntaxCall element) {
-                ArrayList<Argument<T>> args = createArguments(element.getSyntaxSignature(), element.getSyntaxArguments());
-                return builder.call(RSyntaxNode.LAZY_DEPARSE, accept(element.getSyntaxLHS()), args);
+                RSyntaxElement lhs = element.getSyntaxLHS();
+                RSyntaxElement[] arguments = element.getSyntaxArguments();
+                /*
+                 * Handle the special case of replacements in a$b, a@b, a$b<- and a@b<-, where FastR
+                 * currently uses a string constant instead of a lookup.
+                 */
+                if ((arguments.length == 2 && isLookup(lhs, "$") || isLookup(lhs, "@")) || (arguments.length == 3 && isLookup(lhs, "$<-") || isLookup(lhs, "@<-"))) {
+                    if (arguments[1] instanceof RSyntaxConstant) {
+                        String field = RRuntime.asStringLengthOne(((RSyntaxConstant) arguments[1]).getValue());
+                        if (field != null) {
+                            RSyntaxElement substitute = substituteElement(env.get(field));
+                            if (field != null) {
+                                if (substitute instanceof RSyntaxLookup) {
+                                    substitute = RSyntaxConstant.createDummyConstant(RSyntaxNode.LAZY_DEPARSE, ((RSyntaxLookup) substitute).getIdentifier());
+                                }
+                                if (substitute instanceof RSyntaxConstant) {
+                                    arguments = Arrays.copyOf(arguments, arguments.length, RSyntaxElement[].class);
+                                    arguments[1] = substitute;
+                                }
+                            }
+                        }
+                    }
+                }
+                ArrayList<Argument<T>> args = createArguments(element.getSyntaxSignature(), arguments);
+                return builder.call(RSyntaxNode.LAZY_DEPARSE, accept(lhs), args);
             }
 
             private ArrayList<Argument<T>> createArguments(ArgumentsSignature signature, RSyntaxElement[] arguments) {
@@ -126,8 +154,7 @@ public class RSubstitute {
             @Override
             protected T visit(RSyntaxLookup element) {
                 // this takes care of replacing variable lookups
-                Object val = env.get(element.getIdentifier());
-                RSyntaxElement substitute = substituteElement(val);
+                RSyntaxElement substitute = substituteElement(env.get(element.getIdentifier()));
                 if (substitute != null) {
                     return builder.process(substitute);
                 } else {
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 543bc8292147d9518233da9e1430efd5b4c73080..d0cf9c52f50523c4590ae8f06e1b49487032207e 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
@@ -46473,6 +46473,42 @@ foo2({
 #f<-function(..., list=character()) { substitute(list(...))[-1L] }; as.character(f("config"))
 [1] "config"
 
+##com.oracle.truffle.r.test.builtins.TestBuiltin_substitute.testSubstitute
+#f<-function(x,name) substitute(x$name <- 5); f(foo, bar); foo <- new.env(); eval(f(foo,bar)); foo$bar
+foo$bar <- 5
+[1] 5
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_substitute.testSubstitute
+#f<-function(x,name) substitute(x$name); f(foo, bar)
+foo$bar
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_substitute.testSubstitute
+#f<-function(x,name) substitute(x$name); f(foo, bar); foo <- new.env(); foo$bar <- 1; eval(f(foo,bar))
+foo$bar
+[1] 1
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_substitute.testSubstitute
+#f<-function(x,name) substitute(x$name<-1); f(foo, bar)
+foo$bar <- 1
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_substitute.testSubstitute
+#f<-function(x,name) substitute(x@name <- 5); f(foo, bar); setClass('cl', representation(bar='numeric')); foo <- new('cl'); eval(f(foo,bar)); foo@bar
+foo@bar <- 5
+[1] 5
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_substitute.testSubstitute
+#f<-function(x,name) substitute(x@name); f(foo, bar)
+foo@bar
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_substitute.testSubstitute
+#f<-function(x,name) substitute(x@name); f(foo, bar); setClass('cl', representation(bar='numeric')); foo <- new('cl'); foo@bar <- 1; eval(f(foo,bar))
+foo@bar
+[1] 1
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_substitute.testSubstitute
+#f<-function(x,name) substitute(x@name<-2); f(foo, bar)
+foo@bar <- 2
+
 ##com.oracle.truffle.r.test.builtins.TestBuiltin_substitute.testSubstitute
 #{ delayedAssign("expr", a * b) ; substitute(expr) }
 expr
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_substitute.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_substitute.java
index 22ad8c1265b37e2d900a20acf35571a585a25f09..f77ea55ad9e0df862695904d94611606dfbe520c 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_substitute.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_substitute.java
@@ -78,5 +78,14 @@ public class TestBuiltin_substitute extends TestBase {
         assertEval("{ substitute({class(y) <- x; y}, list(x=42)) }");
 
         assertEval("f<-function(...) { print(typeof(get('...'))); environment() }; e <- f(c(1,2), b=15, c=44); substitute(foo2({...}), e)");
+
+        assertEval("f<-function(x,name) substitute(x$name); f(foo, bar)");
+        assertEval("f<-function(x,name) substitute(x@name); f(foo, bar)");
+        assertEval("f<-function(x,name) substitute(x$name<-1); f(foo, bar)");
+        assertEval("f<-function(x,name) substitute(x@name<-2); f(foo, bar)");
+        assertEval("f<-function(x,name) substitute(x$name); f(foo, bar); foo <- new.env(); foo$bar <- 1; eval(f(foo,bar))");
+        assertEval("f<-function(x,name) substitute(x$name <- 5); f(foo, bar); foo <- new.env(); eval(f(foo,bar)); foo$bar");
+        assertEval("f<-function(x,name) substitute(x@name); f(foo, bar); setClass('cl', representation(bar='numeric')); foo <- new('cl'); foo@bar <- 1; eval(f(foo,bar))");
+        assertEval("f<-function(x,name) substitute(x@name <- 5); f(foo, bar); setClass('cl', representation(bar='numeric')); foo <- new('cl'); eval(f(foo,bar)); foo@bar");
     }
 }