diff --git a/NEWS.md b/NEWS.md
index 2d607e9bad..315bad2dec 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -69,7 +69,7 @@ unit = "s")
 
 8. Compiler support for OpenMP is now detected during installation, which allows data.table to compile from source (in single threaded mode) on macOS which, frustratingly, does not include OpenMP support by default, [#2161](https://github.com/Rdatatable/data.table/issues/2161), unlike Windows and Linux. A helpful message is emitted during installation from source, and on package startup as before. Many thanks to @jimhester for the PR. This was typically a problem just after release to CRAN in the few days before macOS binaries (which do support OpenMP) are made available by CRAN.
 
-9. `rbindlist` now supports columns of type `expression`, [#546](https://github.com/Rdatatable/data.table/issues/546). Thanks @jangorecki for the report.
+9. `rbindlist` now supports columns of type `expression`, [#546](https://github.com/Rdatatable/data.table/issues/546). Thanks @jangorecki for the report and @sritchie73 for the PR.
 
 10. The dimensions of objects in a `list` column are now displayed, [#3671](https://github.com/Rdatatable/data.table/issues/3671). Thanks to @randomgambit for the request, and Tyson Barrett for the PR.
 
diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw
index f6d17a0762..f65ebc8f9f 100644
--- a/inst/tests/tests.Rraw
+++ b/inst/tests/tests.Rraw
@@ -16733,12 +16733,51 @@ test(2128.3, DT[, .SD, .SDcols=function(x) x==1], error='conditions were not met
 test(2128.4, DT[, .SD, .SDcols=function(x) 2L],   error='conditions were not met for: [a, b, c]')
 test(2128.5, DT[, .SD, .SDcols=function(x) NA],   error='conditions were not met for: [a, b, c]')
 
-# expression columns in rbindlist, #546
+# Test non-atomic columns in rbindlist, e.g. expressions, #546
 A = data.table(c1 = 1, c2 = 'asd', c3 = expression(as.character(Sys.time())))
 B = data.table(c1 = 3, c2 = 'qwe', c3 = expression(as.character(Sys.time()+5)))
-test(2129, rbind(A,B)$c3, expression(as.character(Sys.time()), as.character(Sys.time()+5)))
+DT = data.table(c1 = c(1, 3), c2 = c("asd", "qwe"), 
+                c3 = list(expression(as.character(Sys.time())), expression(as.character(Sys.time() + 5))))
+test(2129.1, rbind(A,B), DT)
 
-# print dims in list-columns, #3671
+A = data.table(c1 = 1, c2 = 'asd', c3 = expression(as.character(Sys.time())))
+B = data.table(c1 = 3:5, c2 = c('qwe', "foo", "bar"), 
+               c3 = expression(1+1, print("test"), as.character(Sys.time()+5)))
+DT = data.table(c1 = c(1, 3:5), c2 = c("asd", "qwe", "foo", "bar"), 
+                c3 = list(expression(as.character(Sys.time())), expression(1+1), expression(print("test")), 
+                          expression(as.character(Sys.time()+5))))
+test(2129.2, rbind(A,B), DT)
+
+A = data.table(c1 = 1:2, c2 = c('asd', 'xzy'), c3 = expression(as.character(Sys.time()), 1+2))
+B = data.table(c1 = 3:5, c2 = c('qwe', "foo", "bar"), 
+               c3 = expression(1+1, print("test"), as.character(Sys.time()+5)))
+DT = data.table(c1 = c(1:2, 3:5), c2 = c("asd", "xzy", "qwe", "foo", "bar"), 
+                c3 = list(expression(as.character(Sys.time())), expression(1+2), 
+                          expression(1+1), expression(print("test")), 
+                          expression(as.character(Sys.time()+5))))
+test(2129.3, rbind(A,B), DT)                          
+
+A = data.table(c1 = 1, c2 = 'asd', c3 = expression(as.character(Sys.time())))
+B = data.table(c1 = 3:5, c2 = c('qwe', "foo", "bar"), c3 = 4:6)
+DT = data.table(c1 = c(1, 3:5), c2 = c("asd", "qwe", "foo", "bar"), 
+                c3 = list(expression(as.character(Sys.time())), 4L, 5L, 6L))
+test(2129.4, rbind(A,B), DT)
+
+A = data.table(c1 = 1, c2 = quote(1+1)) # length(quote(1+1)) = 3, so A[,.N] = 3 with c1 recycled
+B = data.table(c1 = 2, c2 = 'asd')
+test(2129.5, rbind(A, B)$c2, list(quote(1+1), quote(1+1), quote(1+1), 'asd')) # list(quote(1+1)) is recycled
+
+A = data.table(c1 = 1:3, c2 = pairlist(A=1, B=list(1, 2:3), C=pairlist(C1=1, C2=3:4)))
+test(2129.6, rbind(A,B)$c2, c(as.list(A$c2), 'asd')) # top level pairlist becomes list
+
+if (test_bit64) {
+  A = data.table(A=list(1, 2:3, 4))
+  B = data.table(A=as.integer64(5:7))
+  target = data.table(A=list(1, 2:3, 4, as.integer64(5), as.integer64(6), as.integer64(7)))
+  test(2129.7, rbind(A,B), target) # list of atomic vectors should preserve class
+}
+
+# print dims in list-colrmns, #3671
 DT = data.table(
   x = 1:2,
   y = list(data.table(x=1, y=1),
diff --git a/src/data.table.h b/src/data.table.h
index f2687f52ea..7413711673 100644
--- a/src/data.table.h
+++ b/src/data.table.h
@@ -233,6 +233,7 @@ bool islocked(SEXP x);
 SEXP islockedR(SEXP x);
 bool need2utf8(SEXP x);
 SEXP coerceUtf8IfNeeded(SEXP x);
+SEXP coerceAsList(SEXP x, int len, int *nprotect);
 
 // types.c
 char *end(char *start);
diff --git a/src/init.c b/src/init.c
index aed2da3dbd..a02cbdeb9d 100644
--- a/src/init.c
+++ b/src/init.c
@@ -221,7 +221,7 @@ R_ExternalMethodDef externalMethods[] = {
 };
 
 static void setSizes() {
-  for (int i=0; i<100; ++i) { sizes[i]=0; typeorder[i]=0; }
+  for (int i=0; i<100; ++i) { sizes[i]=0; typeorder[i]=7; } // typeorder init value must be > listed types below
   // only these types are currently allowed as column types :
   sizes[LGLSXP] =  sizeof(int);       typeorder[LGLSXP] =  0;
   sizes[RAWSXP] =  sizeof(Rbyte);     typeorder[RAWSXP] =  1;
diff --git a/src/rbindlist.c b/src/rbindlist.c
index f39b63f0a7..ad1bb10993 100644
--- a/src/rbindlist.c
+++ b/src/rbindlist.c
@@ -292,8 +292,8 @@ SEXP rbindlist(SEXP l, SEXP usenamesArg, SEXP fillArg, SEXP idcolArg)
       }
       SEXP thisCol = VECTOR_ELT(li, w);
       int thisType = TYPEOF(thisCol);
-      // Use >= for #546 -- TYPEORDER=0 for both LGLSXP and EXPRSXP (but also NULL)
-      if (TYPEORDER(thisType)>=TYPEORDER(maxType) && !isNull(thisCol)) maxType=thisType;
+      if (TYPEORDER(thisType)>TYPEORDER(VECSXP) && !isNull(thisCol)) maxType=VECSXP; 
+      else if (TYPEORDER(thisType)>TYPEORDER(maxType) && !isNull(thisCol)) maxType=thisType; 
       if (isFactor(thisCol)) {
         if (isNull(getAttrib(thisCol,R_LevelsSymbol))) error(_("Column %d of item %d has type 'factor' but has no levels; i.e. malformed."), w+1, i+1);
         factor = true;
@@ -309,7 +309,7 @@ SEXP rbindlist(SEXP l, SEXP usenamesArg, SEXP fillArg, SEXP idcolArg)
       }
       if (firsti==-1) { firsti=i; firstw=w; firstCol=thisCol; }
       else {
-        if (!factor && !int64) {
+        if (!factor && !int64 && maxType != VECSXP) {
           if (!R_compute_identical(PROTECT(getAttrib(thisCol, R_ClassSymbol)),
                                    PROTECT(getAttrib(firstCol, R_ClassSymbol)),
                                    0)) {
@@ -322,11 +322,11 @@ SEXP rbindlist(SEXP l, SEXP usenamesArg, SEXP fillArg, SEXP idcolArg)
 
     if (!foundName) { static char buff[12]; sprintf(buff,"V%d",j+1), SET_STRING_ELT(ansNames, idcol+j, mkChar(buff)); foundName=buff; }
     if (factor) maxType=INTSXP;  // if any items are factors then a factor is created (could be an option)
-    if (int64 && maxType!=REALSXP)
+    if (int64 && maxType!=REALSXP && maxType!=VECSXP)
       error(_("Internal error: column %d of result is determined to be integer64 but maxType=='%s' != REALSXP"), j+1, type2char(maxType)); // # nocov
     SEXP target;
     SET_VECTOR_ELT(ans, idcol+j, target=allocVector(maxType, nrow));  // does not initialize logical & numerics, but does initialize character and list
-    if (!factor) copyMostAttrib(firstCol, target); // all but names,dim and dimnames; mainly for class. And if so, we want a copy here, not keepattr's SET_ATTRIB.
+    if (!factor && maxType != VECSXP) copyMostAttrib(firstCol, target); // all but names,dim and dimnames; mainly for class. And if so, we want a copy here, not keepattr's SET_ATTRIB.
 
     if (factor && anyNotStringOrFactor) {
       // in future warn, or use list column instead ... warning(_("Column %d contains a factor but not all items for the column are character or factor"), idcol+j+1);
@@ -514,20 +514,22 @@ SEXP rbindlist(SEXP l, SEXP usenamesArg, SEXP fillArg, SEXP idcolArg)
         if (w==-1 || !length(thisCol=VECTOR_ELT(li, w))) {  // !length for zeroCol warning above; #1871
           writeNA(target, ansloc, thisnrow);  // writeNA is integer64 aware and writes INT64_MIN
         } else {
-          if ((TYPEOF(target)==VECSXP || TYPEOF(target)==EXPRSXP) && TYPEOF(thisCol)!=TYPEOF(target)) {
-            // do an as.list() on the atomic column; #3528
-            thisCol = PROTECT(coerceVector(thisCol, TYPEOF(target))); nprotect++;
+          int cnprotect = 0;
+          if (maxType==VECSXP) {
+            thisCol = coerceAsList(thisCol, thisnrow, &cnprotect); // cnprotect incremented by 0 or 1
           }
+
           // else coerces if needed within memrecycle; with a no-alloc direct coerce from 1.12.4 (PR #3909)
           const char *ret = memrecycle(target, R_NilValue, ansloc, thisnrow, thisCol, 0, -1, idcol+j+1, foundName);
           if (ret) warning(_("Column %d of item %d: %s"), w+1, i+1, ret);
           // e.g. when precision is lost like assigning 3.4 to integer64; test 2007.2
           // TODO: but maxType should handle that and this should never warn
+          UNPROTECT(cnprotect); // pop coerced 'thisCol' from PROTECT stack - not need now copied into 'target'.
         }
         ansloc += thisnrow;
       }
     }
   }
-  UNPROTECT(nprotect);  // ans, coercedForFactor, thisCol
+  UNPROTECT(nprotect);  // ans, coercedForFactor
   return(ans);
 }
diff --git a/src/utils.c b/src/utils.c
index e2c12940ff..80cf0b6dfc 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -356,3 +356,48 @@ SEXP coerceUtf8IfNeeded(SEXP x) {
   return(ans);
 }
 
+SEXP coerceAsList(SEXP x, int len, int *nprotect) {
+  SEXP coerced;
+  if (TYPEOF(x)==VECSXP) {
+    return(x);
+  } if (TYPEOF(x) == LISTSXP) {
+    coerced = PROTECT(coerceVector(x, VECSXP)); (*nprotect)++; // top level pairlist converted to regular list
+  } else if (isVector(x)) {
+    // Allocate VECSXP container, 
+    coerced = PROTECT(allocVector(VECSXP, length(x))); (*nprotect)++;
+    for(int i=0; i<length(x); ++i) {
+      // Allocate length-1 vector of appropriate type and set it in the right place in the VECSXP container
+      SEXP thisElement = PROTECT(allocVector(TYPEOF(x), 1)); (*nprotect)++;
+      SET_VECTOR_ELT(coerced, i, thisElement); UNPROTECT(1); (*nprotect)--; // thisElement now PROTECTED in coerced
+      
+      // Copy any class attributes from x into each element of coerced
+      // Must happen before memrecycle for memrecycle to handle class
+      // correctly (currently only relevant for integer64)
+      copyMostAttrib(x, thisElement); 
+      
+      // Use memrecycle to give each list element the correct value
+      const char *ret = memrecycle(thisElement, R_NilValue, 0, 1, x, i, 1, 0, "V1");
+      if (ret) error(_("Internal Error: memrecycle could not copy vector element into list")); // # nocov
+      // ret > 0 should be impossible because TYPEOF(thisElement) == TYPEOF(x)
+    }
+  } else if (!isVector(x)) { 
+    // Anything not a vector we can assign directly through SET_VECTOR_ELT
+    // Although tecnically there should only be one list element for any type met here,
+    // the length of the type may be > 1, in which case the other columns in data.table
+    // will have been recycled. We therefore in turn have to recycle the list elements
+    // to match the number of rows.
+    if (len < 1) // len used here instead of length(x) because length(x) does not reflect length of target vector data.table
+      error("Internal Error: len provided to C function coerceAsList must be > 0\n"); // # nocov
+    coerced = PROTECT(allocVector(VECSXP, len)); (*nprotect)++;
+    for(int i=0; i<len; ++i) {
+      SET_VECTOR_ELT(coerced, i, x);
+    }
+  } else {
+    // should be unreachable
+    error("Internal error: cannot coerce type %s to list\n", type2char(TYPEOF(x))); // # nocov
+  }
+  
+  // *nprotect should have been incremented by just 1 here - 'coerced' 
+  // should be at the top of the PROTECT stack.
+  return(coerced);
+}
