diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/SetS4Object.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/SetS4Object.java
index 552c317a2903d1f017404e1a35629a12bb6b1ea2..4371caeddfe9632d03e9da6cf3d3406bfa7f6d76 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/SetS4Object.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/SetS4Object.java
@@ -22,6 +22,7 @@
  */
 package com.oracle.truffle.r.nodes.builtin.base;
 
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.*;
 import static com.oracle.truffle.r.runtime.builtins.RBehavior.PURE;
 import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.INTERNAL;
 
@@ -32,14 +33,11 @@ import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
 import com.oracle.truffle.r.nodes.objects.AsS4;
 import com.oracle.truffle.r.nodes.objects.AsS4NodeGen;
 import com.oracle.truffle.r.runtime.RError;
-import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.builtins.RBuiltin;
 import com.oracle.truffle.r.runtime.context.RContext;
 import com.oracle.truffle.r.runtime.data.RAttributable;
 import com.oracle.truffle.r.runtime.data.RNull;
 import com.oracle.truffle.r.runtime.data.RSequence;
-import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
-import com.oracle.truffle.r.runtime.data.model.RAbstractLogicalVector;
 
 @RBuiltin(name = "setS4Object", kind = INTERNAL, parameterNames = {"object", "flag", "complete"}, behavior = PURE)
 public abstract class SetS4Object extends RBuiltinNode {
@@ -48,25 +46,15 @@ public abstract class SetS4Object extends RBuiltinNode {
 
     @Override
     protected void createCasts(CastBuilder casts) {
-        casts.toAttributable(0, true, true, true);
-        casts.toLogical(1);
-        casts.toInteger(2);
-    }
-
-    private boolean checkArgs(RAbstractLogicalVector flagVec, RAbstractIntVector completeVec) {
-        if (flagVec.getLength() == 0 || (flagVec.getLength() == 1 && flagVec.getDataAt(0) == RRuntime.LOGICAL_NA) || flagVec.getLength() > 1) {
-            throw RError.error(this, RError.Message.INVALID_ARGUMENT, "flag");
-        }
-        if (completeVec.getLength() == 0 || flagVec.getDataAt(0) == RRuntime.LOGICAL_NA) {
-            throw RError.error(this, RError.Message.INVALID_ARGUMENT, "complete");
-        }
-        return RRuntime.fromLogical(flagVec.getDataAt(0));
+        casts.arg("object").asAttributable(true, true, true);
+        casts.arg("flag").asLogicalVector().mustBe(singleElement(), RError.SHOW_CALLER, RError.Message.INVALID_ARGUMENT, "flag").findFirst().map(toBoolean());
+        // "complete" can be a vector, unlike "flag"
+        casts.arg("complete").asIntegerVector().findFirst(RError.SHOW_CALLER, RError.Message.INVALID_ARGUMENT, "complete");
     }
 
     @Specialization
     @TruffleBoundary
-    protected RNull asS4(RNull object, RAbstractLogicalVector flagVec, RAbstractIntVector completeVec) {
-        boolean flag = checkArgs(flagVec, completeVec);
+    protected RNull asS4(RNull object, boolean flag, @SuppressWarnings("unused") int complete) {
         if (flag) {
             RContext.getInstance().setNullS4Object(true);
         } else {
@@ -80,14 +68,13 @@ public abstract class SetS4Object extends RBuiltinNode {
     }
 
     @Specialization(guards = "!isSequence(object)")
-    protected Object asS4(RAttributable object, RAbstractLogicalVector flagVec, RAbstractIntVector completeVec) {
-        boolean flag = checkArgs(flagVec, completeVec);
-        return asS4.executeObject(object, flag, completeVec.getDataAt(0));
+    protected Object asS4(RAttributable object, boolean flag, int complete) {
+        return asS4.executeObject(object, flag, complete);
     }
 
     @Specialization
-    protected Object asS4(RSequence seq, RAbstractLogicalVector flagVec, RAbstractIntVector completeVec) {
-        return asS4(seq.materialize(), flagVec, completeVec);
+    protected Object asS4(RSequence seq, boolean flag, int complete) {
+        return asS4(seq.materialize(), flag, complete);
     }
 
     protected boolean isSequence(Object o) {
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Slot.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Slot.java
index c916d144022f5e7e3d863cf937e31b466d3735bc..57dc5a43393f2e5352f159744dc7454830252ee1 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Slot.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Slot.java
@@ -39,7 +39,7 @@ public abstract class Slot extends RBuiltinNode {
 
     @Override
     protected void createCasts(CastBuilder casts) {
-        casts.toAttributable(0, true, true, true);
+        casts.arg(0).asAttributable(true, true, true);
     }
 
     private String getName(Object nameObj) {
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/StandardGeneric.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/StandardGeneric.java
index 753985e79b18a00b1517e436a7c09c8ff6849cf5..48572c7a8c378fda8f7eefac49e9ef77660dab36 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/StandardGeneric.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/StandardGeneric.java
@@ -12,10 +12,13 @@
  */
 package com.oracle.truffle.r.nodes.builtin.base;
 
+import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.*;
 import static com.oracle.truffle.r.runtime.RVisibility.CUSTOM;
 import static com.oracle.truffle.r.runtime.builtins.RBehavior.COMPLEX;
 import static com.oracle.truffle.r.runtime.builtins.RBuiltinKind.PRIMITIVE;
 
+import java.util.function.Function;
+
 import com.oracle.truffle.api.CompilerDirectives;
 import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.api.frame.MaterializedFrame;
@@ -28,6 +31,8 @@ import com.oracle.truffle.r.nodes.attributes.AttributeAccess;
 import com.oracle.truffle.r.nodes.attributes.AttributeAccessNodeGen;
 import com.oracle.truffle.r.nodes.builtin.CastBuilder;
 import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
+import com.oracle.truffle.r.nodes.function.ClassHierarchyScalarNode;
+import com.oracle.truffle.r.nodes.function.ClassHierarchyScalarNodeGen;
 import com.oracle.truffle.r.nodes.objects.CollectGenericArgumentsNode;
 import com.oracle.truffle.r.nodes.objects.CollectGenericArgumentsNodeGen;
 import com.oracle.truffle.r.nodes.objects.DispatchGeneric;
@@ -39,15 +44,12 @@ import com.oracle.truffle.r.runtime.RError;
 import com.oracle.truffle.r.runtime.RRuntime;
 import com.oracle.truffle.r.runtime.builtins.RBuiltin;
 import com.oracle.truffle.r.runtime.context.RContext;
-import com.oracle.truffle.r.runtime.data.RAttributable;
-import com.oracle.truffle.r.runtime.data.RAttributeProfiles;
 import com.oracle.truffle.r.runtime.data.RAttributes;
 import com.oracle.truffle.r.runtime.data.RFunction;
 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.RStringVector;
-import com.oracle.truffle.r.runtime.data.model.RAbstractStringVector;
 import com.oracle.truffle.r.runtime.env.REnvironment;
 
 // transcribed from src/main/objects.c
@@ -63,6 +65,7 @@ public abstract class StandardGeneric extends RBuiltinNode {
     @Child private LocalReadVariableNode readSigARgs = LocalReadVariableNode.create(RRuntime.DOT_SIG_ARGS, true);
     @Child private CollectGenericArgumentsNode collectArgumentsNode;
     @Child private DispatchGeneric dispatchGeneric = DispatchGenericNodeGen.create();
+    @Child private ClassHierarchyScalarNode classNode;
 
     @Child private CastNode castIntScalar;
     @Child private CastNode castStringScalar;
@@ -77,10 +80,22 @@ public abstract class StandardGeneric extends RBuiltinNode {
     private final BranchProfile noGenFunFound = BranchProfile.create();
     private final ConditionProfile sameNamesProfile = ConditionProfile.createBinaryProfile();
 
-    private final RAttributeProfiles attrProfiles = RAttributeProfiles.create();
+    private String argClass(Object arg) {
+        if (classNode == null) {
+            classNode = insert(ClassHierarchyScalarNodeGen.create());
+        }
+        return classNode.executeString(arg);
+    }
+
+    @Override
+    protected void createCasts(CastBuilder casts) {
+        casts.arg("f").defaultError(RError.Message.GENERIC, "argument to 'standardGeneric' must be a non-empty character string").mustBe(
+                        stringValue()).asStringVector().findFirst().mustBe(lengthGt(0));
+        Function<Object, Object> argClass = this::argClass;
+        casts.arg("fdef").asAttributable(true, true, true).mustBe(missingValue().or(instanceOf(RFunction.class)), RError.SHOW_CALLER, RError.Message.EXPECTED_GENERIC, argClass);
+    }
 
-    private Object stdGenericInternal(VirtualFrame frame, RAbstractStringVector fVec, RFunction fdef) {
-        String fname = fVec.getDataAt(0);
+    private Object stdGenericInternal(VirtualFrame frame, String fname, RFunction fdef) {
         MaterializedFrame fnFrame = fdef.getEnclosingFrame();
         REnvironment mtable = (REnvironment) readMTableFirst.execute(frame, fnFrame);
         if (mtable == null) {
@@ -105,7 +120,7 @@ public abstract class StandardGeneric extends RBuiltinNode {
         return ret;
     }
 
-    private Object getFunction(VirtualFrame frame, RAbstractStringVector fVec, String fname, Object fnObj) {
+    private Object getFunction(VirtualFrame frame, String fname, Object fnObj) {
         if (fnObj == RNull.instance) {
             noGenFunFound.enter();
             return null;
@@ -128,7 +143,7 @@ public abstract class StandardGeneric extends RBuiltinNode {
         }
         String gen = (String) castStringScalar.execute(genObj);
         if (sameNamesProfile.profile(gen == fname)) {
-            return stdGenericInternal(frame, fVec, fn);
+            return stdGenericInternal(frame, fname, fn);
         } else {
             // in many cases == is good enough (and this will be the fastest path), but it's not
             // always sufficient
@@ -136,21 +151,20 @@ public abstract class StandardGeneric extends RBuiltinNode {
                 noGenFunFound.enter();
                 return null;
             }
-            return stdGenericInternal(frame, fVec, fn);
+            return stdGenericInternal(frame, fname, fn);
         }
     }
 
-    @Specialization(guards = "fVec.getLength() > 0")
-    protected Object stdGeneric(VirtualFrame frame, RAbstractStringVector fVec, RFunction fdef) {
-        return stdGenericInternal(frame, fVec, fdef);
+    @Specialization
+    protected Object stdGeneric(VirtualFrame frame, String fname, RFunction fdef) {
+        return stdGenericInternal(frame, fname, fdef);
     }
 
-    @Specialization(guards = "fVec.getLength() > 0")
-    protected Object stdGeneric(VirtualFrame frame, RAbstractStringVector fVec, @SuppressWarnings("unused") RMissing fdef) {
-        String fname = fVec.getDataAt(0);
+    @Specialization
+    protected Object stdGeneric(VirtualFrame frame, String fname, @SuppressWarnings("unused") RMissing fdef) {
         int n = RArguments.getDepth(frame);
         Object fnObj = RArguments.getFunction(frame);
-        fnObj = getFunction(frame, fVec, fname, fnObj);
+        fnObj = getFunction(frame, fname, fnObj);
         if (fnObj != null) {
             return fnObj;
         }
@@ -161,10 +175,10 @@ public abstract class StandardGeneric extends RBuiltinNode {
         }
         // TODO: GNU R counts to (i < n) - does their equivalent of getDepth return a different
         // value
-        // TODO; shouldn't we count from n to 0?
+        // TODO: shouldn't we count from n to 0?
         for (int i = 0; i <= n; i++) {
             fnObj = sysFunction.executeObject(frame, i);
-            fnObj = getFunction(frame, fVec, fname, fnObj);
+            fnObj = getFunction(frame, fname, fnObj);
             if (fnObj != null) {
                 return fnObj;
             }
@@ -172,14 +186,4 @@ public abstract class StandardGeneric extends RBuiltinNode {
         throw RError.error(this, RError.Message.STD_GENERIC_WRONG_CALL, fname);
     }
 
-    @Specialization
-    protected Object stdGeneric(Object fVec, RAttributable fdef) {
-        if (!(fVec instanceof String || (fVec instanceof RAbstractStringVector && ((RAbstractStringVector) fVec).getLength() > 0))) {
-            throw RError.error(this, RError.Message.GENERIC, "argument to 'standardGeneric' must be a non-empty character string");
-        } else {
-            RStringVector cl = fdef.getClassAttr(attrProfiles);
-            // not a GNU R error message
-            throw RError.error(this, RError.Message.EXPECTED_GENERIC, cl.getLength() == 0 ? RRuntime.STRING_NA : cl.getDataAt(0));
-        }
-    }
 }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/CastToAttributableNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/CastToAttributableNode.java
index 769ee3d5558aa4b64476b6a7476e3e90c4c7d0a5..92608cf7e342cce9085cf9b329b841ccb42fd2e3 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/CastToAttributableNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/CastToAttributableNode.java
@@ -26,6 +26,7 @@ import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.r.runtime.RType;
 import com.oracle.truffle.r.runtime.data.RAttributable;
 import com.oracle.truffle.r.runtime.data.RFunction;
+import com.oracle.truffle.r.runtime.data.RMissing;
 import com.oracle.truffle.r.runtime.data.RNull;
 import com.oracle.truffle.r.runtime.data.RS4Object;
 import com.oracle.truffle.r.runtime.data.RSymbol;
@@ -51,6 +52,12 @@ public abstract class CastToAttributableNode extends CastBaseNode {
         return RNull.instance;
     }
 
+    @Specialization
+    @SuppressWarnings("unused")
+    protected RMissing cast(RMissing rmissing) {
+        return RMissing.instance;
+    }
+
     @Specialization
     protected RAttributable cast(RAbstractContainer container) {
         return container;
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 6428634d5fcb7f20fc4d17888b6da5653f4d304e..de4609f72d21751ab2a827dde1c41e9b106880fb 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
@@ -374,6 +374,29 @@ Error in y@foo <- 42 : object 'y' not found
 ##com.oracle.truffle.r.test.S4.TestS4.testSlotUpdate
 #{ x<-initialize@valueClass; initialize@valueClass<-"foo"; initialize@valueClass<-x }
 
+##com.oracle.truffle.r.test.S4.TestS4.testStdGeneric
+#{ standardGeneric("") }
+Error in standardGeneric("") :
+  argument to 'standardGeneric' must be a non-empty character string
+
+##com.oracle.truffle.r.test.S4.TestS4.testStdGeneric
+#{ standardGeneric("foo", 42) }
+Error: expected a generic function or a primitive for dispatch, got an object of class "numeric"
+
+##com.oracle.truffle.r.test.S4.TestS4.testStdGeneric
+#{ standardGeneric(42) }
+Error in standardGeneric(42) :
+  argument to 'standardGeneric' must be a non-empty character string
+
+##com.oracle.truffle.r.test.S4.TestS4.testStdGeneric
+#{ standardGeneric(character()) }
+Error in standardGeneric(character()) :
+  argument to 'standardGeneric' must be a non-empty character string
+
+##com.oracle.truffle.r.test.S4.TestS4.testStdGeneric
+#{ x<-42; class(x)<-character(); standardGeneric("foo", x) }
+Error: expected a generic function or a primitive for dispatch, got an object of class "numeric"
+
 ##com.oracle.truffle.r.test.builtins.TestBuiltin_Arg.testArg1
 #argv <- list(1+2i);Arg(argv[[1]]);
 [1] 1.107149
@@ -43882,6 +43905,30 @@ Error: unexpected string constant in "argv <- list(''', '"
 ##com.oracle.truffle.r.test.builtins.TestBuiltin_setHook.testsetHook1
 #argv <- structure(list(hookName = 'UserHook::stats4::onUnload',     value = function(pkgname, ...) cat('onUnload', sQuote(pkgname),         'B', '\n')), .Names = c('hookName', 'value'));do.call('setHook', argv)
 
+##com.oracle.truffle.r.test.builtins.TestBuiltin_setS4Object.testSetS4Object
+#{ x<-42; asS4(x, "TRUE") }
+[1] 42
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_setS4Object.testSetS4Object
+#{ x<-42; asS4(x, TRUE, "1") }
+[1] 42
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_setS4Object.testSetS4Object
+#{ x<-42; asS4(x, TRUE, c(1,2)) }
+[1] 42
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_setS4Object.testSetS4Object
+#{ x<-42; asS4(x, TRUE, logical()) }
+Error in asS4(x, TRUE, logical()) : invalid 'complete' argument
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_setS4Object.testSetS4Object
+#{ x<-42; asS4(x, c(TRUE, FALSE)) }
+Error in asS4(x, c(TRUE, FALSE)) : invalid 'flag' argument
+
+##com.oracle.truffle.r.test.builtins.TestBuiltin_setS4Object.testSetS4Object
+#{ x<-42; asS4(x, logical()) }
+Error in asS4(x, logical()) : invalid 'flag' argument
+
 ##com.oracle.truffle.r.test.builtins.TestBuiltin_setS4Object.testsetS4Object1
 #argv <- list(structure('ObjectsWithPackage', class = structure('signature', package = 'methods'), .Names = '.Object', package = 'methods'), TRUE, 0L); .Internal(setS4Object(argv[[1]], argv[[2]], argv[[3]]))
 An object of class “signature”
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 d903558850aaac15c22318a819ca6b86323b9df3..41a29e7c30c60c0d76f902975ed2a4fa92aba4e6 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
@@ -112,6 +112,15 @@ public class TestS4 extends TestRBase {
 
     }
 
+    @Test
+    public void testStdGeneric() {
+        assertEval("{ standardGeneric(42) }");
+        assertEval("{ standardGeneric(character()) }");
+        assertEval("{ standardGeneric(\"\") }");
+        assertEval("{ standardGeneric(\"foo\", 42) }");
+        assertEval("{ x<-42; class(x)<-character(); standardGeneric(\"foo\", x) }");
+    }
+
     @Override
     public String getTestDir() {
         return "S4";
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_setS4Object.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_setS4Object.java
index c3eaee2491f0e1c9a895d9849424fa2c695c276a..eeb9eae3cee9a745ea131db97efcf185b99ab545 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_setS4Object.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_setS4Object.java
@@ -99,4 +99,15 @@ public class TestBuiltin_setS4Object extends TestBase {
         assertEval(Ignored.Unknown,
                         "argv <- list(structure(function (x, y, ...) UseMethod('plot'), target = structure(character(0), .Names = character(0), package = character(0), class = structure('signature', package = 'methods')), defined = structure(character(0), .Names = character(0), package = character(0), class = structure('signature', package = 'methods')), generic = character(0), class = structure('derivedDefaultMethod', package = 'methods')), TRUE, 0L); .Internal(setS4Object(argv[[1]], argv[[2]], argv[[3]]))");
     }
+
+    @Test
+    public void testSetS4Object() {
+        assertEval("{ x<-42; asS4(x, \"TRUE\") }");
+        assertEval("{ x<-42; asS4(x, logical()) }");
+        assertEval("{ x<-42; asS4(x, c(TRUE, FALSE)) }");
+        assertEval("{ x<-42; asS4(x, TRUE, \"1\") }");
+        assertEval("{ x<-42; asS4(x, TRUE, logical()) }");
+        assertEval("{ x<-42; asS4(x, TRUE, c(1,2)) }");
+    }
+
 }