From eecbbf3a1f9542379dd550df2f7c76d3fbfba5f7 Mon Sep 17 00:00:00 2001
From: Tomas Stupka <tomas.stupka@oracle.com>
Date: Thu, 26 Jan 2017 18:34:37 +0100
Subject: [PATCH] Proper "internal generic" lookup for cbind/rbind

---
 .../truffle/r/nodes/builtin/base/Bind.java    | 187 +++++++++---------
 .../r/test/builtins/TestBuiltin_cbind.java    |  38 +++-
 .../r/test/builtins/TestBuiltin_rbind.java    |  37 +++-
 3 files changed, 165 insertions(+), 97 deletions(-)

diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Bind.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Bind.java
index dd7550312e..4519d18124 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Bind.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Bind.java
@@ -32,6 +32,7 @@ import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.dsl.Cached;
 import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.api.frame.VirtualFrame;
+import com.oracle.truffle.api.nodes.Node;
 import com.oracle.truffle.api.profiles.BranchProfile;
 import com.oracle.truffle.api.profiles.ConditionProfile;
 import com.oracle.truffle.api.profiles.ValueProfile;
@@ -43,10 +44,12 @@ import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.SetDimAt
 import com.oracle.truffle.r.nodes.attributes.SpecialAttributesFunctions.SetDimNamesAttributeNode;
 import com.oracle.truffle.r.nodes.builtin.CastBuilder;
 import com.oracle.truffle.r.nodes.builtin.RBuiltinNode;
-import com.oracle.truffle.r.nodes.function.CallMatcherNode;
+import com.oracle.truffle.r.nodes.function.ClassHierarchyNode;
+import com.oracle.truffle.r.nodes.function.ClassHierarchyNodeGen;
+import com.oracle.truffle.r.nodes.function.GetBaseEnvFrameNode;
 import com.oracle.truffle.r.nodes.function.S3FunctionLookupNode;
 import com.oracle.truffle.r.nodes.function.S3FunctionLookupNode.Result;
-import com.oracle.truffle.r.nodes.helpers.InheritsCheckNode;
+import com.oracle.truffle.r.nodes.function.call.RExplicitCallNode;
 import com.oracle.truffle.r.nodes.unary.CastComplexNode;
 import com.oracle.truffle.r.nodes.unary.CastDoubleNode;
 import com.oracle.truffle.r.nodes.unary.CastIntegerNode;
@@ -68,6 +71,7 @@ import com.oracle.truffle.r.runtime.Utils;
 import com.oracle.truffle.r.runtime.builtins.RBuiltin;
 import com.oracle.truffle.r.runtime.data.RArgsValuesAndNames;
 import com.oracle.truffle.r.runtime.data.RDataFactory;
+import com.oracle.truffle.r.runtime.data.RFunction;
 import com.oracle.truffle.r.runtime.data.RList;
 import com.oracle.truffle.r.runtime.data.RNull;
 import com.oracle.truffle.r.runtime.data.RPromise;
@@ -101,9 +105,6 @@ public abstract class Bind extends RBaseNode {
     @Child private CastLogicalNode castLogical;
     @Child private GetDimAttributeNode getDimsNode;
 
-    @Child private S3FunctionLookupNode lookup;
-    @Child private CallMatcherNode callMatcher;
-
     private final BindType type;
 
     private final ConditionProfile nullNamesProfile = ConditionProfile.createBinaryProfile();
@@ -114,17 +115,6 @@ public abstract class Bind extends RBaseNode {
     protected final ValueProfile resultProfile = ValueProfile.createClassProfile();
     protected final ValueProfile vectorProfile = ValueProfile.createClassProfile();
 
-    @Child private PrecedenceNode precedenceNode = PrecedenceNodeGen.create();
-
-    protected int precedence(RArgsValuesAndNames args) {
-        int precedence = -1;
-        Object[] array = args.getArguments();
-        for (int i = 0; i < array.length; i++) {
-            precedence = Math.max(precedence, precedenceNode.executeInteger(array[i], false));
-        }
-        return precedence;
-    }
-
     protected Bind(BindType type) {
         this.type = type;
     }
@@ -154,32 +144,11 @@ public abstract class Bind extends RBaseNode {
     }
 
     @SuppressWarnings("unused")
-    @Specialization(guards = "precedence == NO_PRECEDENCE")
+    @Specialization(guards = {"precedence == NO_PRECEDENCE"})
     protected RNull allNull(VirtualFrame frame, int deparseLevel, Object[] args, RArgsValuesAndNames promiseArgs, int precedence) {
         return RNull.instance;
     }
 
-    private static final ArgumentsSignature SIGNATURE = ArgumentsSignature.get("deparse.level", "...");
-
-    private static final RStringVector DATA_FRAME_CLASS = RDataFactory.createStringVectorFromScalar("data.frame");
-
-    @Specialization(guards = {"args.length > 0", "isDataFrame(args)"})
-    protected Object allDataFrame(VirtualFrame frame, int deparseLevel, @SuppressWarnings("unused") Object[] args, RArgsValuesAndNames promiseArgs, @SuppressWarnings("unused") int precedence) {
-        if (lookup == null) {
-            CompilerDirectives.transferToInterpreterAndInvalidate();
-            lookup = insert(S3FunctionLookupNode.create(false, false));
-        }
-        Result lookupResult = lookup.execute(frame, type.toString(), DATA_FRAME_CLASS, null, frame.materialize(), null);
-        if (lookupResult == null) {
-            throw RInternalError.shouldNotReachHere();
-        }
-        if (callMatcher == null) {
-            CompilerDirectives.transferToInterpreterAndInvalidate();
-            callMatcher = insert(CallMatcherNode.create(false));
-        }
-        return callMatcher.execute(frame, SIGNATURE, new Object[]{deparseLevel, promiseArgs}, lookupResult.function, lookupResult.targetFunctionName, lookupResult.createS3Args(frame));
-    }
-
     private Object bindInternal(int deparseLevel, Object[] args, RArgsValuesAndNames promiseArgs, CastNode castNode, boolean needsVectorCast, SetDimAttributeNode setDimNode,
                     SetDimNamesAttributeNode setDimNamesNode, GetDimNamesAttributeNode getDimNamesNode, GetNamesAttributeNode getNamesNode) {
         ArgumentsSignature signature = promiseArgs.getSignature();
@@ -243,8 +212,8 @@ public abstract class Bind extends RBaseNode {
         }
     }
 
-    @Specialization(guards = {"precedence == LOGICAL_PRECEDENCE", "args.length > 1", "!isDataFrame(args)"})
-    protected Object allLogical(int deparseLevel, Object[] args, RArgsValuesAndNames promiseArgs, @SuppressWarnings("unused") int precedence,
+    @Specialization(guards = {"precedence == LOGICAL_PRECEDENCE", "args.length > 1"})
+    protected Object allLogical(int deparseLevel, Object[] args, RArgsValuesAndNames promiseArgs, @SuppressWarnings("unused") int precedence,  //
                     @Cached("create()") CastLogicalNode cast,
                     @Cached("create()") SetDimAttributeNode setDimNode,
                     @Cached("create()") SetDimNamesAttributeNode setDimNamesNode,
@@ -253,8 +222,8 @@ public abstract class Bind extends RBaseNode {
         return bindInternal(deparseLevel, args, promiseArgs, cast, true, setDimNode, setDimNamesNode, getDimNamesNode, getNamesNode);
     }
 
-    @Specialization(guards = {"precedence == INT_PRECEDENCE", "args.length > 1", "!isDataFrame(args)"})
-    protected Object allInt(int deparseLevel, Object[] args, RArgsValuesAndNames promiseArgs, @SuppressWarnings("unused") int precedence,
+    @Specialization(guards = {"precedence == INT_PRECEDENCE", "args.length > 1"})
+    protected Object allInt(int deparseLevel, Object[] args, RArgsValuesAndNames promiseArgs, @SuppressWarnings("unused") int precedence, //
                     @Cached("create()") CastIntegerNode cast,
                     @Cached("create()") SetDimAttributeNode setDimNode,
                     @Cached("create()") SetDimNamesAttributeNode setDimNamesNode,
@@ -263,8 +232,8 @@ public abstract class Bind extends RBaseNode {
         return bindInternal(deparseLevel, args, promiseArgs, cast, true, setDimNode, setDimNamesNode, getDimNamesNode, getNamesNode);
     }
 
-    @Specialization(guards = {"precedence == DOUBLE_PRECEDENCE", "args.length > 1", "!isDataFrame(args)"})
-    protected Object allDouble(int deparseLevel, Object[] args, RArgsValuesAndNames promiseArgs, @SuppressWarnings("unused") int precedence,
+    @Specialization(guards = {"precedence == DOUBLE_PRECEDENCE", "args.length > 1"})
+    protected Object allDouble(int deparseLevel, Object[] args, RArgsValuesAndNames promiseArgs, @SuppressWarnings("unused") int precedence, //
                     @Cached("create()") CastDoubleNode cast,
                     @Cached("create()") SetDimAttributeNode setDimNode,
                     @Cached("create()") SetDimNamesAttributeNode setDimNamesNode,
@@ -273,8 +242,8 @@ public abstract class Bind extends RBaseNode {
         return bindInternal(deparseLevel, args, promiseArgs, cast, true, setDimNode, setDimNamesNode, getDimNamesNode, getNamesNode);
     }
 
-    @Specialization(guards = {"precedence == STRING_PRECEDENCE", "args.length> 1", "!isDataFrame(args)"})
-    protected Object allString(int deparseLevel, Object[] args, RArgsValuesAndNames promiseArgs, @SuppressWarnings("unused") int precedence,
+    @Specialization(guards = {"precedence == STRING_PRECEDENCE", "args.length> 1"})
+    protected Object allString(int deparseLevel, Object[] args, RArgsValuesAndNames promiseArgs, @SuppressWarnings("unused") int precedence, //
                     @Cached("create()") CastStringNode cast,
                     @Cached("create()") SetDimAttributeNode setDimNode,
                     @Cached("create()") SetDimNamesAttributeNode setDimNamesNode,
@@ -283,8 +252,8 @@ public abstract class Bind extends RBaseNode {
         return bindInternal(deparseLevel, args, promiseArgs, cast, true, setDimNode, setDimNamesNode, getDimNamesNode, getNamesNode);
     }
 
-    @Specialization(guards = {"precedence == COMPLEX_PRECEDENCE", "args.length > 1", "!isDataFrame(args)"})
-    protected Object allComplex(int deparseLevel, Object[] args, RArgsValuesAndNames promiseArgs, @SuppressWarnings("unused") int precedence,
+    @Specialization(guards = {"precedence == COMPLEX_PRECEDENCE", "args.length > 1"})
+    protected Object allComplex(int deparseLevel, Object[] args, RArgsValuesAndNames promiseArgs, @SuppressWarnings("unused") int precedence, //
                     @Cached("create()") CastComplexNode cast,
                     @Cached("create()") SetDimAttributeNode setDimNode,
                     @Cached("create()") SetDimNamesAttributeNode setDimNamesNode,
@@ -293,8 +262,8 @@ public abstract class Bind extends RBaseNode {
         return bindInternal(deparseLevel, args, promiseArgs, cast, true, setDimNode, setDimNamesNode, getDimNamesNode, getNamesNode);
     }
 
-    @Specialization(guards = {"precedence == LIST_PRECEDENCE", "args.length > 1", "!isDataFrame(args)"})
-    protected Object allList(int deparseLevel, Object[] args, RArgsValuesAndNames promiseArgs, @SuppressWarnings("unused") int precedence,
+    @Specialization(guards = {"precedence == LIST_PRECEDENCE", "args.length > 1"})
+    protected Object allList(int deparseLevel, Object[] args, RArgsValuesAndNames promiseArgs, @SuppressWarnings("unused") int precedence, //
                     @Cached("create()") CastListNode cast,
                     @Cached("create()") SetDimAttributeNode setDimNode,
                     @Cached("create()") SetDimNamesAttributeNode setDimNamesNode,
@@ -448,43 +417,6 @@ public abstract class Bind extends RBaseNode {
         return RRuntime.NAMES_ATTR_EMPTY_VALUE;
     }
 
-    @Child private InheritsCheckNode inheritsCheck = new InheritsCheckNode(RRuntime.CLASS_DATA_FRAME);
-
-    protected boolean isDataFrame(Object[] args) {
-        for (int i = 0; i < args.length; i++) {
-            if (inheritsCheck.execute(args[i])) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    @RBuiltin(name = "cbind", kind = INTERNAL, parameterNames = {"deparse.level", "..."}, behavior = COMPLEX)
-    public abstract static class CbindInternal extends RBuiltinNode {
-
-        @Child private Bind bind = BindNodeGen.create(BindType.cbind);
-        @Child private PrecedenceNode precedenceNode = PrecedenceNodeGen.create();
-
-        @Override
-        protected void createCasts(CastBuilder casts) {
-            casts.arg("deparse.level").asIntegerVector().findFirst(0);
-        }
-
-        private int precedence(Object[] args) {
-            int precedence = -1;
-            for (int i = 0; i < args.length; i++) {
-                precedence = Math.max(precedence, precedenceNode.executeInteger(args[i], false));
-            }
-            return precedence;
-        }
-
-        @Specialization
-        protected Object bind(VirtualFrame frame, int deparseLevel, RArgsValuesAndNames args) {
-            return bind.execute(frame, deparseLevel, args.getArguments(), (RArgsValuesAndNames) RArguments.getArgument(frame, 0), precedence(args.getArguments()));
-        }
-    }
-
     private final BranchProfile everSeenNotEqualRows = BranchProfile.create();
     private final BranchProfile everSeenNotEqualColumns = BranchProfile.create();
 
@@ -525,7 +457,6 @@ public abstract class Bind extends RBaseNode {
         int[] dims = getDimensions(vec, rawDimensions);
         RVector<?> res = (RVector<?>) vec.copyWithNewDimensions(dims);
         setDimNamesNode.execute(res, RDataFactory.createList(type == BindType.cbind ? new Object[]{dimNamesA, dimNamesB} : new Object[]{dimNamesB, dimNamesA}));
-        res.copyRegAttributesFrom(vec);
         return res;
     }
 
@@ -585,28 +516,94 @@ public abstract class Bind extends RBaseNode {
         return result;
     }
 
+    @RBuiltin(name = "cbind", kind = INTERNAL, parameterNames = {"deparse.level", "..."}, behavior = COMPLEX)
+    public abstract static class CbindInternal extends AbstractBind {
+        public CbindInternal() {
+            super(BindType.cbind);
+        }
+    }
+
     @RBuiltin(name = "rbind", kind = INTERNAL, parameterNames = {"deparse.level", "..."}, behavior = COMPLEX)
-    public abstract static class RbindInternal extends RBuiltinNode {
+    public abstract static class RbindInternal extends AbstractBind {
+        public RbindInternal() {
+            super(BindType.rbind);
+        }
+    }
+
+    protected abstract static class AbstractBind extends RBuiltinNode {
+        @Child private ClassHierarchyNode classHierarchy = ClassHierarchyNodeGen.create(false, false);
+        private final ConditionProfile hasClassProfile = ConditionProfile.createBinaryProfile();
+        private final ConditionProfile hasDispatchFunction = ConditionProfile.createBinaryProfile();
+
+        @Child private Bind bind;
+        @Child private RExplicitCallNode dispatchCallNode;
+        @Child private PrecedenceNode precedenceNode;
+
+        @Child private S3FunctionLookupNode lookup;
+        @Child private GetBaseEnvFrameNode getBaseEnv;
+
+        private final BindType type;
 
-        @Child private Bind bind = BindNodeGen.create(BindType.rbind);
-        @Child private PrecedenceNode precedenceNode = PrecedenceNodeGen.create();
+        public AbstractBind(BindType type) {
+            this.type = type;
+        }
 
         @Override
         protected void createCasts(CastBuilder casts) {
             casts.arg("deparse.level").asIntegerVector().findFirst(0);
         }
 
-        private int precedence(Object[] args) {
+        @Specialization
+        protected Object bind(VirtualFrame frame, int deparseLevel, RArgsValuesAndNames args) {
+            RFunction dispatchFunction = createDispatchFunction(frame, args.getArguments());
+            if (hasDispatchFunction.profile(dispatchFunction != null)) {
+                if (dispatchCallNode == null) {
+                    CompilerDirectives.transferToInterpreterAndInvalidate();
+                    dispatchCallNode = insert(RExplicitCallNode.create());
+                }
+                return dispatchCallNode.execute(frame, dispatchFunction, (RArgsValuesAndNames) RArguments.getArgument(frame, 0));
+            } else {
+                if (bind == null) {
+                    CompilerDirectives.transferToInterpreterAndInvalidate();
+                    bind = insert(BindNodeGen.create(type));
+                }
+                return bind.execute(frame, deparseLevel, args.getArguments(), (RArgsValuesAndNames) RArguments.getArgument(frame, 0), precedence(args.getArguments()));
+            }
+        }
+
+        protected int precedence(Object[] args) {
             int precedence = -1;
+            if (precedenceNode == null) {
+                CompilerDirectives.transferToInterpreterAndInvalidate();
+                precedenceNode = insert(PrecedenceNodeGen.create());
+            }
             for (int i = 0; i < args.length; i++) {
                 precedence = Math.max(precedence, precedenceNode.executeInteger(args[i], false));
             }
             return precedence;
         }
 
-        @Specialization
-        protected Object bind(VirtualFrame frame, int deparseLevel, RArgsValuesAndNames args) {
-            return bind.execute(frame, deparseLevel, args.getArguments(), (RArgsValuesAndNames) RArguments.getArgument(frame, 0), precedence(args.getArguments()));
+        private RFunction createDispatchFunction(VirtualFrame frame, Object[] args) {
+            Result result = null;
+            for (Object arg : args) {
+                RStringVector clazz = classHierarchy.execute(arg);
+                if (hasClassProfile.profile(clazz != null)) {
+                    if (lookup == null) {
+                        CompilerDirectives.transferToInterpreterAndInvalidate();
+                        lookup = insert(S3FunctionLookupNode.create(false, false));
+                        getBaseEnv = insert(GetBaseEnvFrameNode.create());
+                    }
+                    Result r = lookup.execute(frame, type.toString(), clazz, null, frame.materialize(), getBaseEnv.execute());
+                    if (r != null) {
+                        if (result == null) {
+                            result = r;
+                        } else if (!result.targetFunctionName.equals(r.targetFunctionName)) {
+                            return null;
+                        }
+                    }
+                }
+            }
+            return result != null ? result.function : null;
         }
     }
 
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_cbind.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_cbind.java
index d57ec648ca..4366b6226e 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_cbind.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_cbind.java
@@ -4,7 +4,7 @@
  * http://www.gnu.org/licenses/gpl-2.0.html
  *
  * Copyright (c) 2012-2014, Purdue University
- * Copyright (c) 2013, 2016, Oracle and/or its affiliates
+ * Copyright (c) 2013, 2017, Oracle and/or its affiliates
  *
  * All rights reserved.
  */
@@ -92,5 +92,41 @@ public class TestBuiltin_cbind extends TestBase {
         assertEval("cbind(character(0), 'f')");
         assertEval("cbind(55, character(0))");
         assertEval("cbind(a=55, character(0))");
+
+        assertEval("v <- 1; attr(v, 'a') <- 'a'; cbind(v); cbind(v, v)");
+        assertEval("v <- 1; attr(v, 'a') <- 'a'; attr(v, 'a1') <- 'a1'; cbind(v); cbind(v, v)");
+        assertEval("v <- 1:3; attr(v, 'a') <- 'a'; attr(v, 'a1') <- 'a1'; cbind(v); cbind(v, v)");
+        assertEval("v <- 1:3; v1<-1:3; attr(v, 'a') <- 'a'; attr(v1, 'a1') <- 'a1'; cbind(v, v1)");
     }
+
+    @Test
+    public void testGenericDispatch() {
+        assertEval("{ v <- 1; class(v) <- 'foo'; cbind.foo <- function(...) 'foo'; cbind(v) }");
+        assertEval("{ v <- 1; class(v) <- 'foo'; cbind.foo <- function(...) 'foo'; v2 <- 1; class(v2) <- 'foo'; cbind(v2) }");
+        assertEval("{ v <- 1; class(v) <- 'foo'; assign('cbind.foo', function(x) {'foo'}, envir=.__S3MethodsTable__.); cbind(v) ; rm('cbind.foo', envir=.__S3MethodsTable__.)}");
+
+        // segfault in gnur
+        assertEval(Ignored.ReferenceError, "{ v <- 1; class(v) <- 'foo'; cbind.foo <- length; cbind(v) }");
+        assertEval(Ignored.WrongCaller, "{ v <- 1; class(v) <- 'foo'; cbind.foo <- rawToBits; cbind(v) }");
+
+        assertEval("{ v <- 1; class(v) <- 'foo'; cbind(v) }");
+        assertEval("{ v <- 1; cbind.foo <- function(...) 'foo'; cbind(v) }");
+
+        assertEval("{ v <- 1; class(v) <- 'foo'; cbind.foo <- function(deparse.level, ...) 'foo'; cbind(v) }");
+        assertEval("{ v <- 1; class(v) <- 'foo'; cbind.foo <- function(deparse.level, x) 'foo'; cbind(v) }");
+        assertEval("{ v <- 1; class(v) <- 'foo'; cbind.foo <- function(deparse.level, x1, x2) 'foo'; cbind(v) }");
+        assertEval("{ v <- 1; class(v) <- 'foo'; cbind.foo <- function(x0, deparse.level, x1, x2) 'foo'; cbind(v) }");
+        assertEval("{ v <- 1; class(v) <- 'foo'; cbind.foo <- function(x0, x1, x2) 'foo'; cbind(v) }");
+        assertEval("{ v <- 1; class(v) <- 'foo'; cbind.foo <- function(x) 'foo'; cbind(v) }");
+        assertEval(Ignored.WrongCaller, "{ v <- 1; class(v) <- 'foo'; cbind.foo <- function() 'foo'; cbind(v) }");
+
+        assertEval("{ v <- 1; class(v) <- c('foo1', 'foo2'); cbind.foo1 <- function(...) 'foo1'; cbind(v) }");
+        assertEval("{ v <- 1; class(v) <- c('foo1', 'foo2'); cbind.foo2 <- function(...) 'foo2'; cbind(v) }");
+        assertEval("{ v <- 1; class(v) <- c('foo1', 'foo2'); cbind.foo1 <- function(...) 'foo1'; cbind.foo2 <- function(...) 'foo2'; cbind(v) }");
+
+        assertEval("{ v1 <- 1; class(v1) <- 'foo1'; cbind.foo1 <- function(...) 'foo1'; v2 <- 2; class(v2) <- 'foo2'; cbind.foo2 <- function(...) 'foo2'; cbind(v1, v2) }");
+        assertEval("{ v1 <- 1; class(v1) <- 'foo1'; cbind.foo1 <- function(...) 'foo1'; v2 <- 2; class(v2) <- 'foo2'; cbind(v1, v2) }");
+        assertEval("{ v1 <- 1; class(v1) <- 'foo1'; v2 <- 2; class(v2) <- 'foo2'; cbind.foo2 <- function(...) 'foo2'; cbind(v1, v2) }");
+    }
+
 }
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rbind.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rbind.java
index a5a22ee9a4..af4dada520 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rbind.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/builtins/TestBuiltin_rbind.java
@@ -4,7 +4,7 @@
  * http://www.gnu.org/licenses/gpl-2.0.html
  *
  * Copyright (c) 2012-2014, Purdue University
- * Copyright (c) 2013, 2016, Oracle and/or its affiliates
+ * Copyright (c) 2013, 2017, Oracle and/or its affiliates
  *
  * All rights reserved.
  */
@@ -76,5 +76,40 @@ public class TestBuiltin_rbind extends TestBase {
         assertEval("rbind(character(0), 'f')");
         assertEval("rbind(55, character(0))");
         assertEval("rbind(a=55, character(0))");
+
+        assertEval("v <- 1; attr(v, 'a') <- 'a'; rbind(v); rbind(v, v)");
+        assertEval("v <- 1; attr(v, 'a') <- 'a'; attr(v, 'a1') <- 'a1'; rbind(v); rbind(v, v)");
+        assertEval("v <- 1:3; attr(v, 'a') <- 'a'; attr(v, 'a1') <- 'a1'; rbind(v); rbind(v, v)");
+        assertEval("v <- 1:3; v1<-1:3; attr(v, 'a') <- 'a'; attr(v1, 'a1') <- 'a1'; rbind(v, v1)");
+    }
+
+    @Test
+    public void testGenericDispatch() {
+        assertEval("{ v <- 1; class(v) <- 'foo'; rbind.foo <- function(...) 'foo'; rbind(v) }");
+        assertEval("{ v <- 1; class(v) <- 'foo'; rbind.foo <- function(...) 'foo'; v2 <- 1; class(v2) <- 'foo'; rbind(v2) }");
+        assertEval("{ v <- 1; class(v) <- 'foo'; assign('rbind.foo', function(x) {'foo'}, envir=.__S3MethodsTable__.); rbind(v) ; rm('rbind.foo', envir=.__S3MethodsTable__.)}");
+
+        // segfault in gnur
+        assertEval(Ignored.ReferenceError, "{ v <- 1; class(v) <- 'foo'; rbind.foo <- length; rbind(v) }");
+        assertEval(Ignored.WrongCaller, "{ v <- 1; class(v) <- 'foo'; rbind.foo <- rawToBits; rbind(v) }");
+
+        assertEval("{ v <- 1; class(v) <- 'foo'; rbind(v) }");
+        assertEval("{ v <- 1; rbind.foo <- function(...) 'foo'; rbind(v) }");
+
+        assertEval("{ v <- 1; class(v) <- 'foo'; rbind.foo <- function(deparse.level, ...) 'foo'; rbind(v) }");
+        assertEval("{ v <- 1; class(v) <- 'foo'; rbind.foo <- function(deparse.level, x) 'foo'; rbind(v) }");
+        assertEval("{ v <- 1; class(v) <- 'foo'; rbind.foo <- function(deparse.level, x1, x2) 'foo'; rbind(v) }");
+        assertEval("{ v <- 1; class(v) <- 'foo'; rbind.foo <- function(x0, deparse.level, x1, x2) 'foo'; rbind(v) }");
+        assertEval("{ v <- 1; class(v) <- 'foo'; rbind.foo <- function(x0, x1, x2) 'foo'; rbind(v) }");
+        assertEval("{ v <- 1; class(v) <- 'foo'; rbind.foo <- function(x) 'foo'; rbind(v) }");
+        assertEval(Ignored.WrongCaller, "{ v <- 1; class(v) <- 'foo'; rbind.foo <- function() 'foo'; rbind(v) }");
+
+        assertEval("{ v <- 1; class(v) <- c('foo1', 'foo2'); rbind.foo1 <- function(...) 'foo1'; rbind(v) }");
+        assertEval("{ v <- 1; class(v) <- c('foo1', 'foo2'); rbind.foo2 <- function(...) 'foo2'; rbind(v) }");
+        assertEval("{ v <- 1; class(v) <- c('foo1', 'foo2'); rbind.foo1 <- function(...) 'foo1'; rbind.foo2 <- function(...) 'foo2'; rbind(v) }");
+
+        assertEval("{ v1 <- 1; class(v1) <- 'foo1'; rbind.foo1 <- function(...) 'foo1'; v2 <- 2; class(v2) <- 'foo2'; rbind.foo2 <- function(...) 'foo2'; rbind(v1, v2) }");
+        assertEval("{ v1 <- 1; class(v1) <- 'foo1'; rbind.foo1 <- function(...) 'foo1'; v2 <- 2; class(v2) <- 'foo2'; rbind(v1, v2) }");
+        assertEval("{ v1 <- 1; class(v1) <- 'foo1'; v2 <- 2; class(v2) <- 'foo2'; rbind.foo2 <- function(...) 'foo2'; rbind(v1, v2) }");
     }
 }
-- 
GitLab