[med-svn] [r-cran-tibble] 01/06: New upstream version 1.3.4

Andreas Tille tille at debian.org
Fri Sep 29 21:49:55 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository r-cran-tibble.

commit dc9f4741a6b0667c60a34ae54960a16ac41c59b8
Author: Andreas Tille <tille at debian.org>
Date:   Fri Sep 29 23:44:06 2017 +0200

    New upstream version 1.3.4
---
 DESCRIPTION                                        |  34 +--
 LICENSE                                            |   2 +-
 MD5                                                | 177 ++++++-----
 NAMESPACE                                          |  21 +-
 NEWS.md                                            | 113 ++++++-
 R/RcppExports.R                                    |   4 +-
 R/add.R                                            |  69 +++--
 R/all-equal.r                                      | 103 -------
 R/as_tibble.R                                      | 169 +++++++++++
 R/check-names.R                                    |  39 ++-
 R/compat-lazyeval.R                                |  90 ++++++
 R/compat-purrr.R                                   | 161 ++++++++++
 R/dataframe.R                                      | 333 ---------------------
 R/enframe.R                                        |  22 +-
 R/glimpse.R                                        |  43 ++-
 R/has-name.R                                       |  19 +-
 R/lst.R                                            |  69 +++++
 R/repair-names.R                                   |  99 +++++-
 R/rownames.R                                       |  48 +--
 R/tbl-df.r                                         |  34 ++-
 R/tibble-pkg.R                                     |  65 ++++
 R/tibble.R                                         | 212 +++++++++----
 R/tribble.R                                        | 138 ++++++---
 R/type-sum.r                                       |  42 +--
 R/utils-format.r                                   | 230 +++++++++-----
 R/utils.r                                          |  58 ++--
 README.md                                          | 135 +++------
 build/vignette.rds                                 | Bin 230 -> 219 bytes
 inst/doc/{formatting.R => extending.R}             |   0
 inst/doc/{formatting.Rmd => extending.Rmd}         |   8 +-
 inst/doc/{formatting.html => extending.html}       |  54 ++--
 inst/doc/tibble.R                                  |  25 +-
 inst/doc/tibble.Rmd                                |  39 ++-
 inst/doc/tibble.html                               |  98 +++---
 man/add_column.Rd                                  |   1 -
 man/add_row.Rd                                     |   3 +-
 man/all_equal.Rd                                   |  54 ----
 man/as_tibble.Rd                                   |  59 ++--
 man/enframe.Rd                                     |  15 +-
 man/figures/logo.png                               | Bin 0 -> 27136 bytes
 man/formatting.Rd                                  |  14 +-
 man/frame_matrix.Rd                                |  28 ++
 man/glimpse.Rd                                     |   8 +-
 man/has_name.Rd                                    |  11 +-
 man/is.tibble.Rd                                   |   3 +-
 man/knit_print.trunc_mat.Rd                        |   1 -
 man/obj_sum.Rd                                     |  17 +-
 man/repair_names.Rd                                |  32 --
 man/rownames.Rd                                    |  17 +-
 man/tibble-package.Rd                              |  58 ++--
 man/tibble.Rd                                      |  51 ++--
 man/tidy_names.Rd                                  |  60 ++++
 man/tribble.Rd                                     |  11 +-
 src/RcppExports.cpp                                |  22 +-
 tests/testthat/helper-data.R                       |  14 +
 tests/testthat/helper-encoding.R                   |  53 ++++
 tests/testthat/helper-output.R                     |  13 +-
 tests/testthat/helper-type-sum.R                   |   9 +
 tests/testthat/output/glimpse/all-35.txt           |   2 +-
 tests/testthat/output/glimpse/all-50.txt           |   2 +-
 tests/testthat/output/glimpse/all-70.txt           |   2 +-
 tests/testthat/output/glimpse/non-syntactic.txt    |   4 +
 tests/testthat/output/trunc_mat/POSIXlt-8-60.txt   |   2 +-
 tests/testthat/output/trunc_mat/all--30.txt        |   2 +-
 tests/testthat/output/trunc_mat/all--300.txt       |  17 +-
 tests/testthat/output/trunc_mat/all-1-30-0.txt     |   2 +-
 tests/testthat/output/trunc_mat/all-1-30-2.txt     |   2 +-
 tests/testthat/output/trunc_mat/all-knit-120.txt   |   4 +-
 tests/testthat/output/trunc_mat/all-knit-60.txt    |   4 +-
 tests/testthat/output/trunc_mat/iris--70.txt       |  20 +-
 tests/testthat/output/trunc_mat/iris-3-5.txt       |   2 +-
 tests/testthat/output/trunc_mat/iris-5-30.txt      |   2 +-
 tests/testthat/output/trunc_mat/iris_unk-10-70.txt |  18 +-
 tests/testthat/output/trunc_mat/long-5-30.txt      |   2 +-
 tests/testthat/output/trunc_mat/mtcars-8-30.txt    |   2 +-
 tests/testthat/output/trunc_mat/mtcars-knit-60.txt |   2 +-
 tests/testthat/output/trunc_mat/newline.txt        |   5 +
 tests/testthat/output/trunc_mat/non-syntactic.txt  |   4 +
 tests/testthat/output/trunc_mat/wide-8-60.txt      |   2 +-
 tests/testthat/output/trunc_mat/zero_cols-5-30.txt |   2 +-
 tests/testthat/output/trunc_mat/zero_rows--30.txt  |   2 +-
 tests/testthat/test-add.R                          | 129 +++++---
 tests/testthat/test-data-frame.R                   | 190 ++++++++++--
 tests/testthat/test-enframe.R                      |  11 +
 tests/testthat/test-equality.R                     |  92 ------
 tests/testthat/test-glimpse.R                      |  15 +-
 tests/testthat/test-lst.R                          |   5 +
 tests/testthat/test-matrix.R                       |   7 +
 tests/testthat/test-nibble.R                       | 106 -------
 tests/testthat/test-obj-sum.R                      |   6 +-
 tests/testthat/test-options.R                      |  60 ++++
 tests/testthat/test-repair_names.R                 |  13 -
 tests/testthat/test-rownames.R                     |  60 +++-
 tests/testthat/test-tbl-df.R                       | 157 ++++++++--
 tests/testthat/test-tidy_names.R                   |  86 ++++++
 tests/testthat/test-tribble.R                      | 176 +++++++++++
 tests/testthat/test-trunc-mat.R                    |  40 ++-
 tests/testthat/test-type_sum.R                     |   7 +
 tests/testthat/test-utils-format.R                 |  20 ++
 vignettes/{formatting.Rmd => extending.Rmd}        |   8 +-
 vignettes/tibble.Rmd                               |  39 ++-
 101 files changed, 2945 insertions(+), 1695 deletions(-)

diff --git a/DESCRIPTION b/DESCRIPTION
index 05f0746..197310c 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -1,32 +1,32 @@
 Package: tibble
 Encoding: UTF-8
-Version: 1.2
+Version: 1.3.4
 Title: Simple Data Frames
-Description: Provides a 'tbl_df' class that offers better checking and
-    printing capabilities than traditional data frames.
+Description: Provides a 'tbl_df' class (the 'tibble') that provides
+    stricter checking and better formatting than the traditional data frame.
 Authors at R: c(
-    person("Hadley", "Wickham", , "hadley at rstudio.com", "aut"),
-    person("Romain", "Francois", , "romain at r-enthusiasts.com", "aut"),
     person("Kirill", "Müller", , "krlmlr+r at mailbox.org", c("aut", "cre")),
+    person("Hadley", "Wickham", , "hadley at rstudio.com", "aut"),
+    person("Romain", "Francois", , "romain at r-enthusiasts.com", "ctb"),
     person("RStudio", role = "cph")
     )
-URL: https://github.com/hadley/tibble
-BugReports: https://github.com/hadley/tibble/issues
-Depends: R (>= 3.1.2)
-Imports: methods, assertthat, utils, lazyeval (>= 0.1.10), Rcpp
-Suggests: testthat, withr, knitr (>= 1.5.32), rmarkdown, nycflights13,
-        microbenchmark
+URL: http://tibble.tidyverse.org/, https://github.com/tidyverse/tibble
+BugReports: https://github.com/tidyverse/tibble/issues
+Depends: R (>= 3.1.0)
+Imports: methods, rlang, Rcpp (>= 0.12.3), utils
+Suggests: covr, dplyr, knitr (>= 1.5.32), microbenchmark, nycflights13,
+        testthat, rmarkdown, withr
 LinkingTo: Rcpp
 LazyData: yes
 License: MIT + file LICENSE
-RoxygenNote: 5.0.1
+RoxygenNote: 6.0.1
 VignetteBuilder: knitr
 NeedsCompilation: yes
-Packaged: 2016-08-26 11:42:44 UTC; muelleki
-Author: Hadley Wickham [aut],
-  Romain Francois [aut],
-  Kirill Müller [aut, cre],
+Packaged: 2017-08-21 16:04:36 UTC; muelleki
+Author: Kirill Müller [aut, cre],
+  Hadley Wickham [aut],
+  Romain Francois [ctb],
   RStudio [cph]
 Maintainer: Kirill Müller <krlmlr+r at mailbox.org>
 Repository: CRAN
-Date/Publication: 2016-08-26 21:50:28
+Date/Publication: 2017-08-22 07:16:29 UTC
diff --git a/LICENSE b/LICENSE
index 46b1e49..003aacf 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,2 +1,2 @@
-YEAR: 2013-2016
+YEAR: 2013-2017
 COPYRIGHT HOLDER: RStudio
diff --git a/MD5 b/MD5
index 8a52707..07420c1 100644
--- a/MD5
+++ b/MD5
@@ -1,94 +1,105 @@
-b2e9ae1b9352939da5c5fd2625b5d1a0 *DESCRIPTION
-b9b3d969565e7a0cae5fc295df89b8df *LICENSE
-44698edb7b450b59cc7185e36be83e85 *NAMESPACE
-96d757bfaa09b04e8f74fd1a52a7d466 *NEWS.md
-eb9619f7830ea8f3050299ae1b3d60e5 *R/RcppExports.R
-22673a507154c6aaff91ebef883ef4f5 *R/add.R
-2766f5518bf33bff07c6aeeaf67f8559 *R/all-equal.r
-ee288bac55cdb66c414ae04b6505be7a *R/check-names.R
-053be47b9adf56d4cca566ee0f84d677 *R/dataframe.R
-17f857477f0b4d27765ffa9f622e6a3a *R/enframe.R
-6eef80de53c4c474d74e4a3799d772b5 *R/glimpse.R
-93ceb5444587193dffc940a5cf514528 *R/has-name.R
-4519520146ba19ec0e365cae28a9eb92 *R/repair-names.R
-34e22b5a8d8caffad8f6847f86b23842 *R/rownames.R
-0f41126f668536900cef37ef232838f3 *R/tbl-df.r
-a64dfb358f5b5a7af5f9055d01af3e1e *R/tibble.R
-783e93f5ab08a3d365e2306c28352c48 *R/tribble.R
-abbbf29429acc59b04cb26835195f522 *R/type-sum.r
-a5ca35ad1819dcfaa6742f86a789c045 *R/utils-format.r
-0bda8fe6f887b1c24ab243553753e578 *R/utils.r
-b8a67878190a75c0e2410a01c3cf5e13 *README.md
-6d3e129b933b7c3596ab817585e5e7c0 *build/vignette.rds
-004d2bcbbbefd366635f6dbc32f6045d *inst/doc/formatting.R
-59535b317f2c66f3b8ad1406061f1b9b *inst/doc/formatting.Rmd
-6710ae71bcd615dd4c6d66c88b6fa951 *inst/doc/formatting.html
-b3661ba91509d77490523eec7c8752ee *inst/doc/tibble.R
-e6e4a4fefa97e6baa72268a5aaf1b09b *inst/doc/tibble.Rmd
-56c99c7b9a0e13f631d98eb447245e15 *inst/doc/tibble.html
-a9db417eedafc8f650a0060a80afdf24 *man/add_column.Rd
-b0377576b964cc0e1a118a19926b5454 *man/add_row.Rd
-ddc140d2b85ac02ecd4d6fa45ec60795 *man/all_equal.Rd
-775f766e6220494ffd9975270c758ae9 *man/as_tibble.Rd
-9fc2813fca4a54cdf9f9462f4e4161c1 *man/enframe.Rd
-e2b25f484fee0c8ec71add8f777e2bf8 *man/formatting.Rd
-b0974f5e23669cd9e4358c101ed1145a *man/glimpse.Rd
-45ee0ec6fa0f6c2d377b5cefa83a0049 *man/has_name.Rd
-4746e48b255539a8015e149fe745a84e *man/is.tibble.Rd
-407773a91a2714a9c5ba3741cff95103 *man/knit_print.trunc_mat.Rd
-442bd7f4be4f16275f30e2cd1ef5126d *man/obj_sum.Rd
-1f16ab048d6f018a58bebc7c7e6763f3 *man/repair_names.Rd
-d40ab396cf65dca644a42a6addba20aa *man/rownames.Rd
-bffd0d9278358ea709bd7bc0f8bda634 *man/tibble-package.Rd
-c1493b3d06b93f334a229a1de6f6e324 *man/tibble.Rd
-e56afd8ca208190010590f3cec63c7a7 *man/tribble.Rd
-b330a9146ed0494723fa830de0f9044f *src/RcppExports.cpp
+1376c9e899231bcb2f8220a379ef603b *DESCRIPTION
+4ac396cdf32c44b3d07b7d6c9fd388d0 *LICENSE
+a5ca521a41c6b9965048042b3e0c4ec4 *NAMESPACE
+de1ffa87e2e88c9c8d3d574165fb397a *NEWS.md
+cbf131ef577c70578d9f456090a2d0ff *R/RcppExports.R
+3fb56642e7270c2355bcfb6254135e67 *R/add.R
+6eb4f1bd9fd076b51f05fb9e84a95891 *R/as_tibble.R
+51173772b8547b7f582b3cd4b11ffb26 *R/check-names.R
+5a4c5e10d841e8a4db5eb4c9bb9d1726 *R/compat-lazyeval.R
+1f93a1dc9780598f4c7fed9392807141 *R/compat-purrr.R
+12c4bde1fc2edb4c52a7d7b68126f86f *R/enframe.R
+0831347539fbb9d6ec0b8ba08257758c *R/glimpse.R
+37e008c2005f365de1b92fc79ca0c77f *R/has-name.R
+df65c43f4e204880006d2bf4954539c5 *R/lst.R
+a2bd5f781856fc0de739f19c125e5c1e *R/repair-names.R
+996402579097014ed8ee335fcede494d *R/rownames.R
+f1e12e569a43fe792f618a0fe7e34468 *R/tbl-df.r
+d96e7126681cb80282e528528f9efc2a *R/tibble-pkg.R
+e8b16712bf06ca14a01216604f4c681b *R/tibble.R
+98d31495818bd026ca78e1b8056beb48 *R/tribble.R
+f5b383a240e5292134cd0365afb26003 *R/type-sum.r
+f61a198aa11bc6c4c6c654a446be5db8 *R/utils-format.r
+363d105ac175bba26b9d7a7f9d8c2fb2 *R/utils.r
+360ee83b45345505b3d0081cce9b57c2 *README.md
+e1c5ab6038569a0574af519351afa41b *build/vignette.rds
+004d2bcbbbefd366635f6dbc32f6045d *inst/doc/extending.R
+f5191ceda247e779a271ef21b4427e88 *inst/doc/extending.Rmd
+f0102542c4fed03a2194c739ae92905c *inst/doc/extending.html
+992bc39bcc5ec455d14952ffc78d6ff4 *inst/doc/tibble.R
+d827ea23f552c20d92d909dcd7553739 *inst/doc/tibble.Rmd
+4d3c490b687dabcb64f11a66d77d639b *inst/doc/tibble.html
+9aa4bd0bbe081b2a22189082b94b0e20 *man/add_column.Rd
+4285516d28e3704fa8549b487547b356 *man/add_row.Rd
+fe3326efb8138b5ffd8005c2e2341c5b *man/as_tibble.Rd
+82535c19f20b1ee61c989c8e0281defc *man/enframe.Rd
+2db3b0e7250ce7abb3301a26a21c743c *man/figures/logo.png
+fcd2dd753af00d377debcc422b8d4f9f *man/formatting.Rd
+3efaffe329351a031a41474e7b33741a *man/frame_matrix.Rd
+78ba066d7022f80dd21b7dd82d13920c *man/glimpse.Rd
+aabbbcc13c9fab2d885865c35666c452 *man/has_name.Rd
+2af16d0272f4af1391935be9c53ea222 *man/is.tibble.Rd
+998d02902cd2a2d13d7d1b77b32255ea *man/knit_print.trunc_mat.Rd
+a7f9775ace1d447719b6ec518e6523f0 *man/obj_sum.Rd
+bf8bed5b51d59158c30065d072ff3fb5 *man/rownames.Rd
+a88f6b5a83adaa05eec15e6633507c0c *man/tibble-package.Rd
+32fc63746786daf7600014a4ef915cc5 *man/tibble.Rd
+409540a95ce0eae1d41b3d0f53944ea2 *man/tidy_names.Rd
+a44537d4bfea7809475eab29d6e80206 *man/tribble.Rd
+24bd1acb4726307bc3e5efe98a128291 *src/RcppExports.cpp
 4f8ba78cba02ac2dd80ad056f3257cf8 *src/matrixToDataFrame.cpp
 934bcde5b57357bf4ec7ac5793041f17 *tests/testthat.R
-9f597b81fd89f044d1242ee86767bfe2 *tests/testthat/helper-data.R
-236aac070ae1970e0b086b328d25a656 *tests/testthat/helper-output.R
+f0bd04db7bfe3776f959f3abaf371f7d *tests/testthat/helper-data.R
+e84fdfe6f028d32ba75cb12b73079802 *tests/testthat/helper-encoding.R
+34c5b3bd230fbdd568ebd86d3645dc21 *tests/testthat/helper-output.R
+326a3311eca9503eababc34197f6b893 *tests/testthat/helper-type-sum.R
 897f112238d560f580b6a6c2602676c4 *tests/testthat/helper-unknown-rows.R
 c72ceac37b3a6f8ce601e7c692c0b757 *tests/testthat/output/glimpse/5.txt
-6b385238353522edd3bf4416de1dbf68 *tests/testthat/output/glimpse/all-35.txt
-e6cafb5ba57c19960362703f8364d5a2 *tests/testthat/output/glimpse/all-50.txt
-50b2e81888a4bfec784fce942345242b *tests/testthat/output/glimpse/all-70.txt
+59e332f7548b4667eedff8617438e2e4 *tests/testthat/output/glimpse/all-35.txt
+123d31e1a2489dab3bac4711e3c97758 *tests/testthat/output/glimpse/all-50.txt
+f383d85ec899fcfcd22ce71bc2e296f9 *tests/testthat/output/glimpse/all-70.txt
 82df862794444dc5d7a3eb4cdc83e981 *tests/testthat/output/glimpse/iris-70.txt
 0d74f615866191dd8527f360e3394ae8 *tests/testthat/output/glimpse/iris-empty-70.txt
 7a3f9a2883c851cf26f735fe6363e1fb *tests/testthat/output/glimpse/mtcars-70.txt
-d4332c0a89832a00336ab26bdd9444cc *tests/testthat/output/trunc_mat/POSIXlt-8-60.txt
-7fb8f60258181c9a4ee71576f0f2c7d0 *tests/testthat/output/trunc_mat/all--30.txt
-214e588f18aa610efdf81e7494eed72d *tests/testthat/output/trunc_mat/all--300.txt
-f576682fe5285723cbc15fe754be67ec *tests/testthat/output/trunc_mat/all-1-30-0.txt
-51ce95b0b64793299d2f0d5e68793999 *tests/testthat/output/trunc_mat/all-1-30-2.txt
-f0cde25ef1a1091a3e02567a7a665fcd *tests/testthat/output/trunc_mat/all-knit-120.txt
-8414d8080216f00a5c2d3ae7b061cb23 *tests/testthat/output/trunc_mat/all-knit-60.txt
-3a231a5e6301b027eff1508a16a36162 *tests/testthat/output/trunc_mat/iris--70.txt
-7a5a611b258bbb88f7fe800f0fff435a *tests/testthat/output/trunc_mat/iris-3-5.txt
-745bd57ce074fcd3738a3495120f5180 *tests/testthat/output/trunc_mat/iris-5-30.txt
-35ec2e9d790daa26072c26958d143a40 *tests/testthat/output/trunc_mat/iris_unk-10-70.txt
-d83fea704dc163b15a012b93b2e54662 *tests/testthat/output/trunc_mat/long-5-30.txt
+7abb3cfcde3d7fa037ea8bbbcc142272 *tests/testthat/output/glimpse/non-syntactic.txt
+742911f037307b39164eb87d6acf625e *tests/testthat/output/trunc_mat/POSIXlt-8-60.txt
+27c30963ae2459be042816725709012e *tests/testthat/output/trunc_mat/all--30.txt
+3a9320597278f76e7a495288e7625242 *tests/testthat/output/trunc_mat/all--300.txt
+102409f5cc131dc1fb86adce9a7bcd2d *tests/testthat/output/trunc_mat/all-1-30-0.txt
+32503abc8baa59edd0adb895815708f7 *tests/testthat/output/trunc_mat/all-1-30-2.txt
+65ce459d531ee7f7804f836308005830 *tests/testthat/output/trunc_mat/all-knit-120.txt
+a72670f9a47d4ac6a34348fbb976293a *tests/testthat/output/trunc_mat/all-knit-60.txt
+e59b0d251308472df2bc42c6e6cda9a2 *tests/testthat/output/trunc_mat/iris--70.txt
+d8da007f0cb942ac3e91a624e36f59cb *tests/testthat/output/trunc_mat/iris-3-5.txt
+cca423768bf9439e49a48e6d764a5fca *tests/testthat/output/trunc_mat/iris-5-30.txt
+21c932b59921250abc5adb627db43428 *tests/testthat/output/trunc_mat/iris_unk-10-70.txt
+66b95735245f47c3f4614b295e32d938 *tests/testthat/output/trunc_mat/long-5-30.txt
 6a9c2f34c5cea6d9809470dede9cc1b0 *tests/testthat/output/trunc_mat/long_unk-5-30.txt
-f14405de2d72657b5cd2dcdcbeb0ad10 *tests/testthat/output/trunc_mat/mtcars-8-30.txt
-22f428f7006b28215d69ac7f7d51c5c2 *tests/testthat/output/trunc_mat/mtcars-knit-60.txt
-157b15602882d1b0c698ba564b42c8d8 *tests/testthat/output/trunc_mat/wide-8-60.txt
+28d72b7395af2be7be313929e017971d *tests/testthat/output/trunc_mat/mtcars-8-30.txt
+4d6b584fcf14f289071ff495102cc770 *tests/testthat/output/trunc_mat/mtcars-knit-60.txt
+9374af5fb3005d2b0b4c0c7425f0304c *tests/testthat/output/trunc_mat/newline.txt
+7745a3f34da50c86beacbc9fcf050c17 *tests/testthat/output/trunc_mat/non-syntactic.txt
+4527375972a709a4db3e896bf444a18e *tests/testthat/output/trunc_mat/wide-8-60.txt
 9d2265837d2166c8f0dd40ae5b84d7fc *tests/testthat/output/trunc_mat/zero-cols_unk-5-30.txt
 2b45ff53f989aab58ba94b1d6496f8cf *tests/testthat/output/trunc_mat/zero-rows_unk-5-30.txt
-c9a877ac50cdc83efe505ee07eb94e42 *tests/testthat/output/trunc_mat/zero_cols-5-30.txt
-f4cbe49da6c2a6c795bb73ff7939c295 *tests/testthat/output/trunc_mat/zero_rows--30.txt
-fb8040e67e9a11cb4d0c41931085e2ac *tests/testthat/test-add.R
-fb97cb3387406ceb662e28d9f54969a6 *tests/testthat/test-data-frame.R
-f02fd3cfc8ea195e9daa1f98d47e3d69 *tests/testthat/test-enframe.R
-58f5b25559d0b761384152a082165a2e *tests/testthat/test-equality.R
-fc78843ed14552ade6a8aa7a5e6355ac *tests/testthat/test-glimpse.R
+3065a5e3434746894681d924f1effe73 *tests/testthat/output/trunc_mat/zero_cols-5-30.txt
+03deb9a3dcf8d1e169ee83812fbc1984 *tests/testthat/output/trunc_mat/zero_rows--30.txt
+8e88daef21857dc28afafb991799cbe1 *tests/testthat/test-add.R
+4861c66a944840a77b57736008b235ec *tests/testthat/test-data-frame.R
+77d7fed1e07c6fe0b95aaad0a1fe20b7 *tests/testthat/test-enframe.R
+2ab3305c8d68fb39c4c5d0a6b499a48d *tests/testthat/test-glimpse.R
 f0df9cdc9aebd923cd9307ea78d88599 *tests/testthat/test-has-name.R
-80a22c8f001c0b7f4703b9b1eb01abb4 *tests/testthat/test-lst.R
-d9b7265ff4b99b4f015c52bf58d46846 *tests/testthat/test-matrix.R
-9aed81144588f240ef722377ea33184e *tests/testthat/test-nibble.R
-26d59e5cbbd6d85806a249dd09018d32 *tests/testthat/test-obj-sum.R
-cb4a7b816230647cc54be3b6a11e7deb *tests/testthat/test-options.R
-80907cf7a15f840a511fba9edbc39452 *tests/testthat/test-repair_names.R
-3a8690468bd9fab7cb48af157e194f26 *tests/testthat/test-rownames.R
-67255210bad12a8d337c5d1800ba9200 *tests/testthat/test-tbl-df.R
-970ed238e0bf2810520f72962e447f3b *tests/testthat/test-trunc-mat.R
-59535b317f2c66f3b8ad1406061f1b9b *vignettes/formatting.Rmd
-e6e4a4fefa97e6baa72268a5aaf1b09b *vignettes/tibble.Rmd
+15763cbdfc6f6dba50e2d8961ac14eed *tests/testthat/test-lst.R
+e59955e1c9d8131bc258788aef976ac2 *tests/testthat/test-matrix.R
+9a4b100f626d7f44362f5470f6fa0c14 *tests/testthat/test-obj-sum.R
+652ae23d96e576d41df774c372426183 *tests/testthat/test-options.R
+257131d73135fdceaef99cb9fbcb758c *tests/testthat/test-repair_names.R
+5721206d4b58044e5505ce8f589cb546 *tests/testthat/test-rownames.R
+4392e55f6237d1225eeefe4285781e73 *tests/testthat/test-tbl-df.R
+be9d5b4ce2b894b94330f0ecc6849b60 *tests/testthat/test-tidy_names.R
+440db24addc4cc4c45930aa040ae5596 *tests/testthat/test-tribble.R
+adff836188146afde593eb8c70dedf7b *tests/testthat/test-trunc-mat.R
+24859ead9ba31215eddee63a7eb656b6 *tests/testthat/test-type_sum.R
+72c743cf3bfdfdfd6d26d875e80f6539 *tests/testthat/test-utils-format.R
+f5191ceda247e779a271ef21b4427e88 *vignettes/extending.Rmd
+d827ea23f552c20d92d909dcd7553739 *vignettes/tibble.Rmd
diff --git a/NAMESPACE b/NAMESPACE
index 9a1db1c..32a360e 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -4,7 +4,6 @@ S3method("$",tbl_df)
 S3method("[",tbl_df)
 S3method("[[",tbl_df)
 S3method("row.names<-",tbl_df)
-S3method(all.equal,tbl_df)
 S3method(as.data.frame,tbl_df)
 S3method(as_data_frame,"NULL")
 S3method(as_data_frame,data.frame)
@@ -21,12 +20,16 @@ S3method(as_tibble,matrix)
 S3method(as_tibble,poly)
 S3method(as_tibble,table)
 S3method(as_tibble,tbl_df)
+S3method(as_tibble,ts)
 S3method(check_names_before_after,character)
 S3method(check_names_before_after,default)
 S3method(check_names_df,character)
 S3method(check_names_df,default)
 S3method(check_names_df,logical)
 S3method(check_names_df,numeric)
+S3method(format,tbl)
+S3method(format,tbl_df)
+S3method(format,trunc_mat)
 S3method(format_v,character)
 S3method(format_v,default)
 S3method(format_v,list)
@@ -43,14 +46,13 @@ S3method(is_vector_s3,ordered)
 S3method(obj_sum,POSIXlt)
 S3method(obj_sum,default)
 S3method(obj_sum,list)
+S3method(print,tbl)
 S3method(print,tbl_df)
 S3method(print,trunc_mat)
 S3method(quote_n,character)
 S3method(quote_n,default)
 S3method(tbl_sum,default)
-S3method(tbl_sum,grouped_df)
-S3method(tbl_sum,tbl_df)
-S3method(tbl_sum,tbl_sql)
+S3method(tbl_sum,tbl)
 S3method(type_sum,Date)
 S3method(type_sum,POSIXt)
 S3method(type_sum,data.frame)
@@ -61,13 +63,16 @@ S3method(type_sum,ordered)
 S3method(type_sum,tbl_df)
 export(add_column)
 export(add_row)
+export(as.tibble)
 export(as_data_frame)
 export(as_tibble)
 export(column_to_rownames)
 export(data_frame)
 export(data_frame_)
+export(deframe)
 export(enframe)
 export(frame_data)
+export(frame_matrix)
 export(glimpse)
 export(has_name)
 export(has_rownames)
@@ -80,17 +85,19 @@ export(lst_)
 export(obj_sum)
 export(remove_rownames)
 export(repair_names)
+export(rowid_to_column)
 export(rownames_to_column)
+export(set_tidy_names)
 export(tbl_sum)
 export(tibble)
 export(tibble_)
+export(tidy_names)
 export(tribble)
 export(trunc_mat)
 export(type_sum)
-import(assertthat)
+import(rlang)
 importFrom(Rcpp,sourceCpp)
-importFrom(stats,setNames)
 importFrom(utils,head)
 importFrom(utils,str)
 importFrom(utils,tail)
-useDynLib(tibble)
+useDynLib(tibble, .registration = TRUE)
diff --git a/NEWS.md b/NEWS.md
index 5b99cc3..4c18494 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,111 @@
+# tibble 1.3.4 (2017-08-21)
+
+## Bug fixes
+
+- Values of length 1 in a `tibble()` call are recycled prior to evaluating subsequent arguments, improving consistency with `mutate()` (#213).
+- Recycling of values of length 1 in a `tibble()` call maintains their class (#284).
+- `add_row()` now always preserves the column data types of the input data frame the same way as `rbind()` does (#296).
+- `lst()` now again handles duplicate names, the value defined last is used in case of a clash.
+- Adding columns to zero-row data frames now also works when mixing lengths 1 and 0 in the new columns (#167).
+- The `validate` argument is now also supported in `as_tibble.tbl_df()`, with default to `FALSE` (#278).  It must be passed as named argument, as in `as_tibble(validate = TRUE)`.
+
+## Formatting
+
+- `format_v()` now always surrounds lists with `[]` brackets, even if their length is one. This affects `glimpse()` output for list columns (#106).
+- Factor levels are escaped when printing (#277).
+- Non-syntactic names are now also escaped in `glimpse()` (#280).
+- `tibble()` gives a consistent error message in the case of duplicate column names (#291).
+
+
+# tibble 1.3.3 (2017-05-27)
+
+## Bug fixes
+
+- Added `format()` and `print()` methods for both `tbl` and `tbl_df` classes, to protect against malformed tibbles that inherit from `"tbl_df"` but not `"tbl"`, as created e.g. by `ungroup()` in dplyr 0.5.0 and earlier (#256, #263).
+- The column width for non-syntactic columns is computed correctly again (#258).
+- Printing a tibble doesn't apply quote escaping to list columns.
+- Fix error in `tidy_names(syntactic = TRUE, quiet = FALSE)` if not all names are fixed (#260, @imanuelcostigan).
+- Remove unused import declaration for assertthat.
+
+
+# tibble 1.3.1 (2017-05-16)
+
+## Bug fixes
+
+- Subsetting zero columns no longer returns wrong number of rows (#241, @echasnovski).
+
+
+## Interface changes
+
+- New `set_tidy_names()` and `tidy_names()`, a simpler version of `repair_names()` which works unchanged for now (#217).
+- New `rowid_to_column()` that adds a `rowid` column as first column and removes row names (#243, @barnettjacob).
+- The `all.equal.tbl_df()` method has been removed, calling `all.equal()` now forwards to `base::all.equal.data.frame()`. To compare tibbles ignoring row and column order, please use `dplyr::all_equal()` (#247).
+
+
+## Formatting
+
+- Printing now uses `x` again instead of the Unicode multiplication sign, to avoid encoding issues (#216).
+- String values are now quoted when printing if they contain non-printable characters or quotes (#253).
+- The `print()`, `format()`, and `tbl_sum()` methods are now implemented for class `"tbl"` and not for `"tbl_df"`. This allows subclasses to use tibble's formatting facilities. The formatting of the header can be tweaked by implementing `tbl_sum()` for the subclass, which is expected to return a named character vector. The `print.tbl_df()` method is still implemented for compatibility with downstream packages, but only calls `NextMethod()`.
+- Own printing routine, not relying on `print.data.frame()` anymore. Now providing `format.tbl_df()` and full support for Unicode characters in names and data, also for `glimpse()` (#235).
+
+
+## Misc
+
+- Improve formatting of error messages (#223).
+- Using `rlang` instead of `lazyeval` (#225, @lionel-), and `rlang` functions (#244).
+- `tribble()` now handles values that have a class (#237, @NikNakk).
+- Minor efficiency gains by replacing `any(is.na())` with `anyNA()` (#229, @csgillespie).
+- The `microbenchmark` package is now used conditionally (#245).
+- `pkgdown` website.
+
+
+# tibble 1.3.0 (2017-01-10)
+
+## Bug fixes
+
+- Time series matrices (objects of class `mts` and `ts`) are now supported in `as_tibble()` (#184).
+- The `all_equal()` function (called by `all.equal.tbl_df()`) now forwards to `dplyr` and fails with a helpful message if not installed. Data frames with list columns cannot be compared anymore, and differences in the declared class (`data.frame` vs. `tbl_df`) are ignored. The `all.equal.tbl_df()` method gives a warning and forwards to `NextMethod()` if `dplyr` is not installed; call `all.equal(as.data.frame(...), ...)` to avoid the warning. This ensures consistent behavior of this funct [...]
+
+## Interface changes
+
+- Now requiring R 3.1.0 instead of R 3.1.3 (#189).
+- Add `as.tibble()` as an alias to `as_tibble()` (#160, @LaDilettante).
+- New `frame_matrix()`, similar to `frame_data()` but for matrices (#140, #168, @LaDilettante).
+- New `deframe()` as reverse operation to `enframe()` (#146, #214).
+- Removed unused dependency on `assertthat`.
+
+## Features
+
+### General
+
+- Keep column classes when adding row to empty tibble (#171, #177, @LaDilettante).
+- Singular and plural variants for error messages that mention a list of objects (#116, #138, @LaDilettante).
+- `add_column()` can add columns of length 1 (#162, #164, @LaDilettante).
+
+### Input validation
+
+- An attempt to read or update a missing column now throws a clearer warning (#199).
+- An attempt to call `add_row()` for a grouped data frame results in a helpful error message (#179).
+
+### Printing
+
+- Render Unicode multiplication sign as `x` if it cannot be represented in the current locale (#192, @ncarchedi).
+- Backtick `NA` names in printing (#206, #207, @jennybc).
+- `glimpse()` now uses `type_sum()` also for S3 objects (#185, #186, @holstius).
+- The `max.print` option is ignored when printing a tibble (#194, #195, @t-kalinowski).
+
+## Documentation
+
+- Fix typo in `obj_sum` documentation (#193, @etiennebr).
+- Reword documentation for `tribble()` (#191, @kwstat).
+- Now explicitly stating minimum Rcpp version 0.12.3.
+
+## Internal
+
+- Using registration of native routines.
+
+
 # tibble 1.2 (2016-08-26)
 
 ## Bug fixes
@@ -86,7 +194,7 @@ Follow-up release.
 - Subsetting with empty index (e.g., `x[]`) also removes row names.
 
 
-# Documentation
+## Documentation
 
 - Document behavior of `as_tibble.tbl_df()` for subclasses (#60).
 - Document and test that subsetting removes row names.
@@ -105,8 +213,7 @@ Follow-up release.
 - Use new `expect_output_file()` from `testthat`.
 
 
-Version 1.0 (2016-03-21)
-===
+# Version 1.0 (2016-03-21)
 
 - Initial CRAN release
 
diff --git a/R/RcppExports.R b/R/RcppExports.R
index 423023d..74d7ed2 100644
--- a/R/RcppExports.R
+++ b/R/RcppExports.R
@@ -1,7 +1,7 @@
-# This file was generated by Rcpp::compileAttributes
+# Generated by using Rcpp::compileAttributes() -> do not edit by hand
 # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
 
 matrixToDataFrame <- function(x) {
-    .Call('tibble_matrixToDataFrame', PACKAGE = 'tibble', x)
+    .Call(`_tibble_matrixToDataFrame`, x)
 }
 
diff --git a/R/add.R b/R/add.R
index 1f3756b..e069b5d 100644
--- a/R/add.R
+++ b/R/add.R
@@ -1,12 +1,12 @@
 #' Add rows to a data frame
 #'
 #' This is a convenient way to add one or more rows of data to an existing data
-#' frame. See \code{\link{tribble}} for an easy way to create an complete
+#' frame. See [tribble()] for an easy way to create an complete
 #' data frame row-by-row.
 #'
 #' @param .data Data frame to append to.
 #' @param ... Name-value pairs. If you don't supply the name of a variable,
-#'   it'll be given the value \code{NA}.
+#'   it'll be given the value `NA`.
 #' @param .before,.after One-based row index where to add the new rows,
 #'   default: after last row
 #' @family addition
@@ -32,39 +32,53 @@
 #' }
 #' @export
 add_row <- function(.data, ..., .before = NULL, .after = NULL) {
+  if (inherits(.data, "grouped_df")) {
+    stop("Can't add rows to grouped data frames", call. = FALSE)
+  }
+
   df <- tibble(...)
   attr(df, "row.names") <- .set_row_names(max(1L, nrow(df)))
 
   extra_vars <- setdiff(names(df), names(.data))
   if (length(extra_vars) > 0) {
-    stopc(
-      "This row would add new variables: ", format_n(extra_vars)
-    )
+    stopc(pluralise_msg("Can't add row with new variable(s) ", extra_vars))
   }
 
   missing_vars <- setdiff(names(.data), names(df))
-  df[missing_vars] <- lapply(.data[missing_vars], na_value)
+  df[missing_vars] <- map(.data[missing_vars], na_value)
   df <- df[names(.data)]
 
   pos <- pos_from_before_after(.before, .after, nrow(.data))
-
-  if (pos <= 0L) {
-    out <- rbind(df, .data)
-  } else if (pos >= nrow(.data)) {
-    out <- rbind(.data, df)
-  } else {
-    indexes <- seq_len(pos)
-    out <- rbind(.data[indexes, ], df, .data[-indexes, ])
-  }
+  out <- rbind_at(.data, df, pos)
 
   set_class(remove_rownames(out), class(.data))
 }
 
 na_value <- function(boilerplate) {
-  if (is.list(boilerplate))
+  if (is.list(boilerplate)) {
     list(NULL)
-  else
+  } else {
     NA
+  }
+}
+
+rbind_at <- function(old, new, pos) {
+  if (nrow(old) == 0) {
+    old <- old[1, ]
+    out <- rbind(old, new)[-1, ]
+  } else {
+    out <- rbind(old, new)
+    if (pos < nrow(old)) {
+      pos <- max(pos, 0L)
+      idx <- c(
+        seq2(1L, pos),
+        seq2(nrow(old) + 1L, nrow(old) + nrow(new)),
+        seq2(pos + 1L, nrow(old))
+      )
+      out <- out[idx, ]
+    }
+  }
+  out
 }
 
 #' Add columns to a data frame
@@ -102,13 +116,22 @@ add_column <- function(.data, ..., .before = NULL, .after = NULL) {
   }
 
   if (nrow(df) != nrow(.data)) {
-    stopc("Expected ", nrow(.data), " rows, got ", nrow(df))
+    if (nrow(df) == 1) {
+      df <- df[rep(1L, nrow(.data)), ]
+    } else {
+      stopc(
+        "`.data` must have ", nrow(.data),
+        pluralise_n(" row(s)", nrow(.data)),
+        ", not ", nrow(df)
+      )
+    }
   }
 
   extra_vars <- intersect(names(df), names(.data))
   if (length(extra_vars) > 0) {
     stopc(
-      "Columns already in data frame: ", format_n(extra_vars)
+      pluralise_msg("Column(s) ", extra_vars),
+      pluralise(" already exist[s]", extra_vars)
     )
   }
 
@@ -137,17 +160,17 @@ pos_from_before_after_names <- function(before, after, names) {
 }
 
 pos_from_before_after <- function(before, after, len) {
-  if (is.null(before)) {
-    if (is.null(after)) {
+  if (is_null(before)) {
+    if (is_null(after)) {
       len
     } else {
       after
     }
   } else {
-    if (is.null(after)) {
+    if (is_null(after)) {
       before - 1L
     } else {
-      stopc("Can't specify both .before and .after")
+      stopc("Can't specify both `.before` and `.after`")
     }
   }
 }
diff --git a/R/all-equal.r b/R/all-equal.r
deleted file mode 100644
index 9fc1ad9..0000000
--- a/R/all-equal.r
+++ /dev/null
@@ -1,103 +0,0 @@
-#' Flexible equality comparison for data frames.
-#'
-#' When comparing two \code{tbl_df} using \code{\link{all.equal}}, column and
-#' row order is ignored by default, and types are not coerced.  The \code{dplyr}
-#' package provides a much more efficient implementation for this functionality.
-#'
-#' @param target,current Two data frames to compare.
-#' @param ignore_col_order Should order of columns be ignored?
-#' @param ignore_row_order Should order of rows be ignored?
-#' @param convert Should similar classes be converted? Currently this will
-#'   convert factor to character and integer to double.
-#' @param ... Ignored. Needed for compatibility with \code{all.equal}.
-#' @return \code{TRUE} if equal, otherwise a character vector describing
-#'   the reasons why they're not equal. Use \code{\link{isTRUE}} if using the
-#'   result in an \code{if} expression.
-#' @examples
-#' scramble <- function(x) x[sample(nrow(x)), sample(ncol(x))]
-#' mtcars_df <- as_tibble(mtcars)
-#'
-#' # By default, ordering of rows and columns ignored
-#' all.equal(mtcars_df, scramble(mtcars_df))
-#'
-#' # But those can be overriden if desired
-#' all.equal(mtcars_df, scramble(mtcars_df), ignore_col_order = FALSE)
-#' all.equal(mtcars_df, scramble(mtcars_df), ignore_row_order = FALSE)
-#'
-#' # By default all.equal is sensitive to variable differences
-#' df1 <- tibble(x = "a")
-#' df2 <- tibble(x = factor("a"))
-#' all.equal(df1, df2)
-#' # But you can request to convert similar types
-#' all.equal(df1, df2, convert = TRUE)
-all_equal <- function(target, current, ignore_col_order = TRUE,
-                      ignore_row_order = TRUE, convert = FALSE, ...) {
-
-  if (!identical(class(target), class(current))) {
-    return(paste0("Different types: ",
-                  "x ", format_n(class(target)), ", ",
-                  "y ", format_n(class(current))))
-  }
-  if (nrow(target) != nrow(current)) {
-    return("Different number of rows")
-  }
-  extra_x <- setdiff(names(target), names(current))
-  if (length(extra_x) > 0L) {
-    return(paste0("Cols in x but not y: ", format_n(extra_x)))
-  }
-  extra_y <- setdiff(names(current), names(target))
-  if (length(extra_y) > 0L) {
-    return(paste0("Cols in y but not x: ", format_n(extra_y)))
-  }
-  if (!ignore_col_order && names(target) != names(current)) {
-    return("Column names same but in different order")
-  }
-
-  current <- as_tibble(remove_rownames(current))
-  target <- as_tibble(remove_rownames(target))
-
-  current <- current[names(target)]
-
-  types <- unlist(mapply(
-    function(x, y) {
-      if (!identical(class(x), class(y))) {
-        paste0("x ", class(x), ", y ", class(y))
-      }
-    },
-    target, current
-  ))
-
-  if (length(types) > 0L) {
-    types <- paste0("Incompatible type for column ", names(types), ": ", types)
-    if (convert) {
-      lapply(types, warningc)
-    } else {
-      return(types)
-    }
-  }
-
-  factor_levels <- unlist(mapply(
-    function(x, y) {
-      if (!identical(levels(x), levels(y))) {
-        TRUE
-      }
-    },
-    target, current
-  ))
-
-  if (length(factor_levels) > 0L) {
-    return(paste0("Factor levels not equal for column ", names(factor_levels)))
-  }
-
-  if (ignore_row_order) {
-    target <- target[do.call(order, unname(target)), ]
-    current <- current[do.call(order, unname(current)), ]
-  }
-
-  all.equal(as.data.frame(target), as.data.frame(current), ...)
-}
-
-#' @export
-#' @rdname all_equal
-#' @method all.equal tbl_df
-all.equal.tbl_df <- all_equal
diff --git a/R/as_tibble.R b/R/as_tibble.R
new file mode 100644
index 0000000..70b8a9f
--- /dev/null
+++ b/R/as_tibble.R
@@ -0,0 +1,169 @@
+#' Coerce lists and matrices to data frames.
+#'
+#' [as.data.frame()] is effectively a thin wrapper around `data.frame`,
+#' and hence is rather slow (because it calls [data.frame()] on each element
+#' before [cbind]ing together). `as_tibble` is a new S3 generic
+#' with more efficient methods for matrices and data frames.
+#'
+#' This is an S3 generic. tibble includes methods for data frames (adds tbl_df
+#' classes), tibbles (returns unchanged input), lists, matrices, and tables.
+#' Other types are first coerced via `as.data.frame()` with
+#' `stringsAsFactors = FALSE`.
+#'
+#' `as_data_frame` and `as.tibble` are aliases.
+#'
+#' @param x A list. Each element of the list must have the same length.
+#' @param ... Other arguments passed on to individual methods.
+#' @param validate When `TRUE`, verifies that the input is a valid data
+#'   frame (i.e. all columns are named, and are 1d vectors or lists). You may
+#'   want to suppress this when you know that you already have a valid data
+#'   frame and you want to save some time, or to explicitly enable it
+#'   if you have a tibble that you want to re-check.
+#' @export
+#' @examples
+#' l <- list(x = 1:500, y = runif(500), z = 500:1)
+#' df <- as_tibble(l)
+#'
+#' m <- matrix(rnorm(50), ncol = 5)
+#' colnames(m) <- c("a", "b", "c", "d", "e")
+#' df <- as_tibble(m)
+#'
+#' # as_tibble is considerably simpler than as.data.frame
+#' # making it more suitable for use when you have things that are
+#' # lists
+#' \dontrun{
+#' if (requireNamespace("microbenchmark", quiet = TRUE)) {
+#'   l2 <- replicate(26, sample(letters), simplify = FALSE)
+#'   names(l2) <- letters
+#'   microbenchmark::microbenchmark(
+#'     as_tibble(l2, validate = FALSE),
+#'     as_tibble(l2),
+#'     as.data.frame(l2)
+#'   )
+#' }
+#'
+#' if (requireNamespace("microbenchmark", quiet = TRUE)) {
+#'   m <- matrix(runif(26 * 100), ncol = 26)
+#'   colnames(m) <- letters
+#'   microbenchmark::microbenchmark(
+#'     as_tibble(m),
+#'     as.data.frame(m)
+#'   )
+#' }
+#' }
+as_tibble <- function(x, ...) {
+  UseMethod("as_tibble")
+}
+
+#' @export
+#' @rdname as_tibble
+as_tibble.tbl_df <- function(x, ..., validate = FALSE) {
+  if (validate) return(NextMethod())
+  x
+}
+
+#' @export
+#' @rdname as_tibble
+as_tibble.data.frame <- function(x, validate = TRUE, ...) {
+  list_to_tibble(x, validate, raw_rownames(x))
+}
+
+#' @export
+#' @rdname as_tibble
+as_tibble.list <- function(x, validate = TRUE, ...) {
+  if (length(x) == 0) {
+    list_to_tibble(repair_names(list()), validate = FALSE, .set_row_names(0L))
+  } else {
+    list_to_tibble(x, validate)
+  }
+}
+
+list_to_tibble <- function(x, validate, rownames = NULL) {
+  # this is to avoid any method dispatch that may happen when processing x
+  x <- unclass(x)
+
+  if (validate) {
+    x <- check_tibble(x)
+  }
+  x <- recycle_columns(x)
+
+  if (is.null(rownames)) {
+    rownames <- .set_row_names(NROW(x[[1L]]))
+  }
+
+  class(x) <- c("tbl_df", "tbl", "data.frame")
+  attr(x, "row.names") <- rownames
+
+  x
+}
+
+#' @export
+#' @rdname as_tibble
+as_tibble.matrix <- function(x, ...) {
+  matrixToDataFrame(x)
+}
+
+#' @export
+as_tibble.poly <- function(x, ...) {
+  as_tibble(unclass(x))
+}
+
+#' @export
+as_tibble.ts <- function(x, ...) {
+  as_tibble(as.data.frame(x, ...))
+}
+
+#' @export
+#' @param n Name for count column, default: `"n"`.
+#' @rdname as_tibble
+as_tibble.table <- function(x, n = "n", ...) {
+  as_tibble(as.data.frame(x, responseName = n, stringsAsFactors = FALSE))
+}
+
+#' @export
+#' @rdname as_tibble
+as_tibble.NULL <- function(x, ...) {
+  as_tibble(list())
+}
+
+#' @export
+#' @rdname as_tibble
+as_tibble.default <- function(x, ...) {
+  value <- x
+  as_tibble(as.data.frame(value, stringsAsFactors = FALSE, ...))
+}
+
+#' @export
+#' @rdname as_tibble
+#' @usage NULL
+as.tibble <- function(x, ...) {
+  UseMethod("as_tibble")
+}
+
+#' @export
+#' @rdname as_tibble
+#' @usage NULL
+as_data_frame <- function(x, ...) {
+  UseMethod("as_data_frame")
+}
+
+#' @export
+as_data_frame.tbl_df <- as_tibble.tbl_df
+
+#' @export
+as_data_frame.data.frame <- as_tibble.data.frame
+
+#' @export
+as_data_frame.list <- as_tibble.list
+
+#' @export
+as_data_frame.matrix <- as_tibble.matrix
+
+#' @export
+as_data_frame.table <- as_tibble.table
+
+#' @export
+as_data_frame.NULL <- as_tibble.NULL
+
+#' @export
+as_data_frame.default <- as_tibble.default
diff --git a/R/check-names.R b/R/check-names.R
index 8fb9b82..5167abd 100644
--- a/R/check-names.R
+++ b/R/check-names.R
@@ -1,5 +1,3 @@
-# check_names_df ----------------------------------------------------------
-
 check_names_df <- function(j, ...) UseMethod("check_names_df")
 
 #' @export
@@ -9,35 +7,34 @@ check_names_df.default <- function(j, ...) {
 
 #' @export
 check_names_df.character <- function(j, x) {
-  check_needs_no_dim(j)
-
-  pos <- safe_match(j, names(x))
-  if(any(is.na(pos))){
-    unknown_names <- j[is.na(pos)]
-    stopc("Unknown columns ", format_n(unknown_names))
-  }
-  pos
+  check_names_before_after.character(j, names(x))
 }
 
 #' @export
 check_names_df.numeric <- function(j, x) {
   check_needs_no_dim(j)
 
-  if (any(is.na(j))) {
+  if (anyNA(j)) {
     stopc("NA column indexes not supported")
   }
 
   non_integer <- (j != trunc(j))
   if (any(non_integer)) {
-    stopc("Invalid non-integer column indexes: ", format_n(j[non_integer]))
+    stopc(pluralise_msg("Column index(es) must be integer, not ", j[non_integer]))
   }
   neg_too_small <- (j < -length(x))
   if (any(neg_too_small)) {
-    stopc("Invalid negative column indexes: ", format_n(j[neg_too_small]))
+    stopc(pluralise_msg(
+      paste0("Column index(es) must be at least ", -length(x), " if negative, not "),
+      j[neg_too_small]
+    ))
   }
   pos_too_large <- (j > length(x))
   if (any(pos_too_large)) {
-    stopc("Invalid column indexes: ", format_n(j[pos_too_large]))
+    stopc(pluralise_msg(
+      paste0("Column index(es) must be at most ", length(x), " if positive, not "),
+      j[pos_too_large]
+    ))
   }
 
   seq_along(x)[j]
@@ -48,9 +45,12 @@ check_names_df.logical <- function(j, x) {
   check_needs_no_dim(j)
 
   if (!(length(j) %in% c(1L, length(x)))) {
-    stopc("Length of logical index vector must be 1 or ", length(x), ", got: ", length(j))
+    stopc(
+      "Length of logical index vector must be 1 or ", length(x),
+      " (the number of rows), not ", length(j)
+    )
   }
-  if (any(is.na(j))) {
+  if (anyNA(j)) {
     stopc("NA column indexes not supported")
   }
   seq_along(x)[j]
@@ -58,11 +58,10 @@ check_names_df.logical <- function(j, x) {
 
 check_needs_no_dim <- function(j) {
   if (needs_dim(j)) {
-    stopc("Unsupported use of matrix or array for column indexing")
+    stopc("Can't use matrix or array for column indexing")
   }
 }
 
-
 # check_names_before_after ------------------------------------------------
 
 check_names_before_after <- function(j, ...) UseMethod("check_names_before_after")
@@ -77,9 +76,9 @@ check_names_before_after.character <- function(j, names) {
   check_needs_no_dim(j)
 
   pos <- safe_match(j, names)
-  if(any(is.na(pos))){
+  if(anyNA(pos)) {
     unknown_names <- j[is.na(pos)]
-    stopc("Unknown columns ", format_n(unknown_names))
+    stopc(pluralise_msg("Column(s) ", unknown_names), " not found")
   }
   pos
 }
diff --git a/R/compat-lazyeval.R b/R/compat-lazyeval.R
new file mode 100644
index 0000000..878acf4
--- /dev/null
+++ b/R/compat-lazyeval.R
@@ -0,0 +1,90 @@
+# nocov - compat-lazyeval (last updated: rlang 0.0.0.9018)
+
+# This file serves as a reference for compatibility functions for lazyeval.
+# Please find the most recent version in rlang's repository.
+
+
+warn_underscored <- function() {
+  return(NULL)
+  warn(paste(
+    "The underscored versions are deprecated in favour of",
+    "tidy evaluation idioms. Please see the documentation",
+    "for `quo()` in rlang"
+  ))
+}
+warn_text_se <- function() {
+  return(NULL)
+  warn("Text parsing is deprecated, please supply an expression or formula")
+}
+
+compat_lazy <- function(lazy, env = caller_env(), warn = TRUE) {
+  if (warn) warn_underscored()
+
+  if (missing(lazy)) {
+    return(quo())
+  }
+
+  coerce_type(lazy, "quosure",
+    formula = as_quosure(lazy, env),
+    symbol = ,
+    language = new_quosure(lazy, env),
+    string = ,
+    character = {
+      if (warn) warn_text_se()
+      parse_quosure(lazy[[1]], env)
+    },
+    logical = ,
+    integer = ,
+    double = {
+      if (length(lazy) > 1) {
+        warn("Truncating vector to length 1")
+        lazy <- lazy[[1]]
+      }
+      new_quosure(lazy, env)
+    },
+    list =
+      coerce_class(lazy, "quosure",
+        lazy = new_quosure(lazy$expr, lazy$env)
+      )
+  )
+}
+
+compat_lazy_dots <- function(dots, env, ..., .named = FALSE) {
+  if (missing(dots)) {
+    dots <- list()
+  }
+  if (inherits(dots, "lazy")) {
+    dots <- list(dots)
+  } else {
+    dots <- unclass(dots)
+  }
+  dots <- c(dots, list(...))
+
+  warn <- TRUE
+  for (i in seq_along(dots)) {
+    dots[[i]] <- compat_lazy(dots[[i]], env, warn)
+    warn <- FALSE
+  }
+
+  named <- have_name(dots)
+  if (.named && any(!named)) {
+    nms <- map_chr(dots[!named], f_text)
+    names(dots)[!named] <- nms
+  }
+
+  names(dots) <- names2(dots)
+  dots
+}
+
+compat_as_lazy <- function(quo) {
+  structure(class = "lazy", list(
+    expr = f_rhs(quo),
+    env = f_env(quo)
+  ))
+}
+compat_as_lazy_dots <- function(...) {
+  structure(class = "lazy_dots", map(quos(...), compat_as_lazy))
+}
+
+
+# nocov end
diff --git a/R/compat-purrr.R b/R/compat-purrr.R
new file mode 100644
index 0000000..ac276a4
--- /dev/null
+++ b/R/compat-purrr.R
@@ -0,0 +1,161 @@
+# nocov - compat-purrr (last updated: rlang 0.0.0.9007)
+
+# This file serves as a reference for compatibility functions for
+# purrr. They are not drop-in replacements but allow a similar style
+# of programming. This is useful in cases where purrr is too heavy a
+# package to depend on. Please find the most recent version in rlang's
+# repository.
+
+map <- function(.x, .f, ...) {
+  lapply(.x, .f, ...)
+}
+map_mold <- function(.x, .f, .mold, ...) {
+  out <- vapply(.x, .f, .mold, ..., USE.NAMES = FALSE)
+  rlang::set_names(out, names(.x))
+}
+map_lgl <- function(.x, .f, ...) {
+  map_mold(.x, .f, logical(1), ...)
+}
+map_int <- function(.x, .f, ...) {
+  map_mold(.x, .f, integer(1), ...)
+}
+map_dbl <- function(.x, .f, ...) {
+  map_mold(.x, .f, double(1), ...)
+}
+map_chr <- function(.x, .f, ...) {
+  map_mold(.x, .f, character(1), ...)
+}
+map_cpl <- function(.x, .f, ...) {
+  map_mold(.x, .f, complex(1), ...)
+}
+
+pluck <- function(.x, .f) {
+  map(.x, `[[`, .f)
+}
+pluck_lgl <- function(.x, .f) {
+  map_lgl(.x, `[[`, .f)
+}
+pluck_int <- function(.x, .f) {
+  map_int(.x, `[[`, .f)
+}
+pluck_dbl <- function(.x, .f) {
+  map_dbl(.x, `[[`, .f)
+}
+pluck_chr <- function(.x, .f) {
+  map_chr(.x, `[[`, .f)
+}
+pluck_cpl <- function(.x, .f) {
+  map_cpl(.x, `[[`, .f)
+}
+
+map2 <- function(.x, .y, .f, ...) {
+  Map(.f, .x, .y, ...)
+}
+map2_lgl <- function(.x, .y, .f, ...) {
+  as.vector(map2(.x, .y, .f, ...), "logical")
+}
+map2_int <- function(.x, .y, .f, ...) {
+  as.vector(map2(.x, .y, .f, ...), "integer")
+}
+map2_dbl <- function(.x, .y, .f, ...) {
+  as.vector(map2(.x, .y, .f, ...), "double")
+}
+map2_chr <- function(.x, .y, .f, ...) {
+  as.vector(map2(.x, .y, .f, ...), "character")
+}
+map2_cpl <- function(.x, .y, .f, ...) {
+  as.vector(map2(.x, .y, .f, ...), "complex")
+}
+
+args_recycle <- function(args) {
+  lengths <- map_int(args, length)
+  n <- max(lengths)
+
+  stopifnot(all(lengths == 1L | lengths == n))
+  to_recycle <- lengths == 1L
+  args[to_recycle] <- map(args[to_recycle], function(x) rep.int(x, n))
+
+  args
+}
+pmap <- function(.l, .f, ...) {
+  args <- args_recycle(.l)
+  do.call("mapply", c(
+    FUN = list(quote(.f)),
+    args, MoreArgs = quote(list(...)),
+    SIMPLIFY = FALSE, USE.NAMES = FALSE
+  ))
+}
+
+probe <- function(.x, .p, ...) {
+  if (is_logical(.p)) {
+    stopifnot(length(.p) == length(.x))
+    .p
+  } else {
+    map_lgl(.x, .p, ...)
+  }
+}
+
+keep <- function(.x, .f, ...) {
+  .x[probe(.x, .f, ...)]
+}
+discard <- function(.x, .p, ...) {
+  sel <- probe(.x, .p, ...)
+  .x[is.na(sel) | !sel]
+}
+map_if <- function(.x, .p, .f, ...) {
+  matches <- probe(.x, .p)
+  .x[matches] <- map(.x[matches], .f, ...)
+  .x
+}
+
+compact <- function(.x) {
+  Filter(length, .x)
+}
+
+transpose <- function(.l) {
+  inner_names <- names(.l[[1]])
+  if (is.null(inner_names)) {
+    fields <- seq_along(.l[[1]])
+  } else {
+    fields <- set_names(inner_names)
+  }
+
+  map(fields, function(i) {
+    map(.l, .subset2, i)
+  })
+}
+
+every <- function(.x, .p, ...) {
+  for (i in seq_along(.x)) {
+    if (!rlang::is_true(.p(.x[[i]], ...))) return(FALSE)
+  }
+  TRUE
+}
+some <- function(.x, .p, ...) {
+  for (i in seq_along(.x)) {
+    if (rlang::is_true(.p(.x[[i]], ...))) return(TRUE)
+  }
+  FALSE
+}
+negate <- function(.p) {
+  function(...) !.p(...)
+}
+
+reduce <- function(.x, .f, ..., .init) {
+  f <- function(x, y) .f(x, y, ...)
+  Reduce(f, .x, init = .init)
+}
+reduce_right <- function(.x, .f, ..., .init) {
+  f <- function(x, y) .f(y, x, ...)
+  Reduce(f, .x, init = .init, right = TRUE)
+}
+accumulate <- function(.x, .f, ..., .init) {
+  f <- function(x, y) .f(x, y, ...)
+  Reduce(f, .x, init = .init, accumulate = TRUE)
+}
+accumulate_right <- function(.x, .f, ..., .init) {
+  f <- function(x, y) .f(y, x, ...)
+  Reduce(f, .x, init = .init, right = TRUE, accumulate = TRUE)
+}
+
+# nocov end
diff --git a/R/dataframe.R b/R/dataframe.R
deleted file mode 100644
index 60aaa5c..0000000
--- a/R/dataframe.R
+++ /dev/null
@@ -1,333 +0,0 @@
-#' Build a data frame or list.
-#'
-#' \code{tibble} is a trimmed down version of \code{\link{data.frame}} that:
-#' \enumerate{
-#' \item Never coerces inputs (i.e. strings stay as strings!).
-#' \item Never adds \code{row.names}.
-#' \item Never munges column names.
-#' \item Only recycles length 1 inputs.
-#' \item Evaluates its arguments lazily and in order.
-#' \item Adds \code{tbl_df} class to output.
-#' \item Automatically adds column names.
-#' }
-#'
-#' \code{lst} is similar to \code{\link{list}}, but like \code{tibble}, it
-#' evaluates its arguments lazily and in order, and automatically adds names.
-#'
-#' \code{data_frame} is an alias to \code{tibble}.
-#'
-#' @param ... A set of name-value pairs. Arguments are evaluated sequentially,
-#'   so you can refer to previously created variables.
-#' @param xs  A list of unevaluated expressions created with \code{~},
-#'   \code{quote()}, or \code{\link[lazyeval]{lazy}}.
-#' @seealso \code{\link{as_tibble}} to turn an existing list into
-#'   a data frame.
-#' @export
-#' @examples
-#' a <- 1:5
-#' tibble(a, b = a * 2)
-#' tibble(a, b = a * 2, c = 1)
-#' tibble(x = runif(10), y = x * 2)
-#'
-#' lst(n = 5, x = runif(n))
-#'
-#' # tibble never coerces its inputs
-#' str(tibble(letters))
-#' str(tibble(x = list(diag(1), diag(2))))
-#'
-#' # or munges column names
-#' tibble(`a + b` = 1:5)
-#'
-#' # With the SE version, you give it a list of formulas/expressions
-#' tibble_(list(x = ~1:10, y = quote(x * 2)))
-#'
-#' # data frames can only contain 1d atomic vectors and lists
-#' # and can not contain POSIXlt
-#' \dontrun{
-#' tibble(x = tibble(1, 2, 3))
-#' tibble(y = strptime("2000/01/01", "%x"))
-#' }
-tibble <- function(...) {
-  as_tibble(lst(...))
-}
-
-#' @export
-#' @rdname tibble
-tibble_ <- function(xs) {
-  as_tibble(lst_(xs))
-}
-
-#' @export
-#' @rdname tibble
-data_frame <- tibble
-
-#' @export
-#' @rdname tibble
-data_frame_ <- tibble_
-
-#' @export
-#' @rdname tibble
-lst <- function(...) {
-  lst_(lazyeval::lazy_dots(...))
-}
-
-#' @export
-#' @rdname tibble
-lst_ <- function(xs) {
-  n <- length(xs)
-  if (n == 0) return(list())
-
-  # If named not supplied, used deparsed expression
-  col_names <- names2(xs)
-  missing_names <- col_names == ""
-  if (any(missing_names)) {
-    deparse2 <- function(x) paste(deparse(x$expr, 500L), collapse = "")
-    defaults <- vapply(xs[missing_names], deparse2, character(1),
-      USE.NAMES = FALSE)
-    col_names[missing_names] <- defaults
-  }
-
-  # Evaluate each column in turn
-  output <- vector("list", n)
-  names(output) <- character(n)
-
-  for (i in seq_len(n)) {
-    res <- lazyeval::lazy_eval(xs[[i]], output)
-    if (!is.null(res)) {
-      output[[i]] <-  res
-    }
-    names(output)[i] <- col_names[[i]]
-  }
-
-  output
-}
-
-
-#' Coerce lists and matrices to data frames.
-#'
-#' \code{as.data.frame} is effectively a thin wrapper around \code{data.frame},
-#' and hence is rather slow (because it calls \code{data.frame} on each element
-#' before \code{cbind}ing together). \code{as_tibble} is a new S3 generic
-#' with more efficient methods for matrices and data frames.
-#'
-#' This is an S3 generic. tibble includes methods for data frames (adds tbl_df
-#' classes), tibbles (returns unchanged input), lists, matrices, and tables.
-#' Other types are first coerced via \code{\link[base]{as.data.frame}} with
-#' \code{stringsAsFactors = FALSE}.
-#'
-#' \code{as_data_frame} is an alias.
-#'
-#' @param x A list. Each element of the list must have the same length.
-#' @param ... Other arguments passed on to individual methods.
-#' @param validate When \code{TRUE}, verifies that the input is a valid data
-#'   frame (i.e. all columns are named, and are 1d vectors or lists). You may
-#'   want to suppress this when you know that you already have a valid data
-#'   frame and you want to save some time.
-#' @export
-#' @examples
-#' l <- list(x = 1:500, y = runif(500), z = 500:1)
-#' df <- as_tibble(l)
-#'
-#' m <- matrix(rnorm(50), ncol = 5)
-#' colnames(m) <- c("a", "b", "c", "d", "e")
-#' df <- as_tibble(m)
-#'
-#' # as_tibble is considerably simpler than as.data.frame
-#' # making it more suitable for use when you have things that are
-#' # lists
-#' \dontrun{
-#' l2 <- replicate(26, sample(letters), simplify = FALSE)
-#' names(l2) <- letters
-#' microbenchmark::microbenchmark(
-#'   as_tibble(l2, validate = FALSE),
-#'   as_tibble(l2),
-#'   as.data.frame(l2)
-#' )
-#'
-#' m <- matrix(runif(26 * 100), ncol = 26)
-#' colnames(m) <- letters
-#' microbenchmark::microbenchmark(
-#'   as_tibble(m),
-#'   as.data.frame(m)
-#' )
-#' }
-as_tibble <- function(x, ...) {
-  UseMethod("as_tibble")
-}
-
-#' @export
-#' @rdname as_tibble
-as_tibble.tbl_df <- function(x, ...) {
-  x
-}
-
-#' @export
-#' @rdname as_tibble
-as_tibble.data.frame <- function(x, validate = TRUE, ...) {
-  list_to_tibble(x, validate, raw_rownames(x))
-}
-
-#' @export
-#' @rdname as_tibble
-as_tibble.list <- function(x, validate = TRUE, ...) {
-  if (length(x) == 0) {
-    list_to_tibble(repair_names(list()), validate = FALSE, .set_row_names(0L))
-  } else {
-    list_to_tibble(x, validate)
-  }
-}
-
-list_to_tibble <- function(x, validate, rownames = NULL) {
-  # this is to avoid any method dispatch that may happen when processing x
-  x <- unclass(x)
-
-  if (validate) {
-    x <- check_tibble(x)
-  }
-  x <- recycle_columns(x)
-
-  if (is.null(rownames)) {
-    rownames <- .set_row_names(NROW(x[[1L]]))
-  }
-
-  class(x) <- c("tbl_df", "tbl", "data.frame")
-  attr(x, "row.names") <- rownames
-
-  x
-}
-
-#' @export
-#' @rdname as_tibble
-as_tibble.matrix <- function(x, ...) {
-  matrixToDataFrame(x)
-}
-
-#' @export
-as_tibble.poly <- function(x, ...) {
-  as_tibble(unclass(x))
-}
-
-#' @export
-#' @param n Name for count column, default: \code{"n"}.
-#' @rdname as_tibble
-as_tibble.table <- function(x, n = "n", ...) {
-  as_tibble(as.data.frame(x, responseName = n, stringsAsFactors = FALSE))
-}
-
-#' @export
-#' @rdname as_tibble
-as_tibble.NULL <- function(x, ...) {
-  as_tibble(list())
-}
-
-#' @export
-#' @rdname as_tibble
-as_tibble.default <- function(x, ...) {
-  value <- x
-  as_tibble(as.data.frame(value, stringsAsFactors = FALSE, ...))
-}
-
-#' @export
-#' @rdname as_tibble
-as_data_frame <- function(x, ...) {
-  UseMethod("as_data_frame")
-}
-
-#' @export
-as_data_frame.tbl_df <- as_tibble.tbl_df
-
-#' @export
-as_data_frame.data.frame <- as_tibble.data.frame
-
-#' @export
-as_data_frame.list <- as_tibble.list
-
-#' @export
-as_data_frame.matrix <- as_tibble.matrix
-
-#' @export
-as_data_frame.table <- as_tibble.table
-
-#' @export
-as_data_frame.NULL <- as_tibble.NULL
-
-#' @export
-as_data_frame.default <- as_tibble.default
-
-#' Test if the object is a tibble.
-#'
-#' @param x An object
-#' @return \code{TRUE} if the object inherits from the \code{tbl_df} class.
-#' @export
-is.tibble <- function(x) {
-  "tbl_df" %in% class(x)
-}
-
-#' @rdname is.tibble
-#' @export
-is_tibble <- is.tibble
-
-
-# Validity checks --------------------------------------------------------------
-
-check_tibble <- function(x) {
-  # Names
-  names_x <- names2(x)
-  bad_name <- is.na(names_x) | names_x == ""
-  if (any(bad_name)) {
-    invalid_df("Each variable must be named", x, which(bad_name))
-  }
-
-  dups <- duplicated(names_x)
-  if (any(dups)) {
-    invalid_df("Each variable must have a unique name", x, dups)
-  }
-
-  # Types
-  is_1d <- vapply(x, is_1d, logical(1))
-  if (any(!is_1d)) {
-    invalid_df("Each variable must be a 1d atomic vector or list", x, !is_1d)
-  }
-
-  x[] <- lapply(x, strip_dim)
-
-  posixlt <- vapply(x, inherits, "POSIXlt", FUN.VALUE = logical(1))
-  if (any(posixlt)) {
-    invalid_df("Date/times must be stored as POSIXct, not POSIXlt", x, posixlt)
-  }
-
-  x
-}
-
-recycle_columns <- function(x) {
-  if (length(x) == 0) {
-    return(x)
-  }
-
-  # Validate column lengths
-  lengths <- vapply(x, NROW, integer(1))
-  max <- max(lengths)
-
-  bad_len <- lengths != 1L & lengths != max
-  if (any(bad_len)) {
-    invalid_df(paste0("Variables must be length 1 or ", max), x, bad_len)
-  }
-
-  short <- lengths == 1
-  if (max != 1L && any(short)) {
-    x[short] <- lapply(x[short], rep, max)
-  }
-
-  x
-}
-
-invalid_df <- function(problem, df, vars) {
-  if (is.logical(vars)) {
-    vars <- names(df)[vars]
-  }
-  stopc(
-    problem, ".\n",
-    "Problem variables: ", format_n(vars)
-  )
-}
-
diff --git a/R/enframe.R b/R/enframe.R
index 5872a7e..6f69bbc 100644
--- a/R/enframe.R
+++ b/R/enframe.R
@@ -1,20 +1,20 @@
-#' Converting atomic vectors to data frames
+#' Converting atomic vectors to data frames, and vice versa
 #'
-#' A helper function that converts named atomic vectors or lists to two-column
+#' `enframe()` converts named atomic vectors or lists to two-column
 #' data frames.
 #' For unnamed vectors, the natural sequence is used as name column.
 #'
-#' @param x An atomic vector
+#' @param x An atomic vector (for `enframe()`) or a data frame (for `deframe()`)
 #' @param name,value Names of the columns that store the names and values
 #'
-#' @return A \code{\link{tibble}}
+#' @return A [tibble]
 #' @export
 #'
 #' @examples
 #' enframe(1:3)
 #' enframe(c(a = 5, b = 7))
 enframe <- function(x, name = "name", value = "value") {
-  if (is.null(names(x))) {
+  if (is_null(names(x))) {
     df <- tibble(seq_along(x), x)
   } else {
     df <- tibble(names(x), unname(x))
@@ -22,3 +22,15 @@ enframe <- function(x, name = "name", value = "value") {
   names(df) <- c(name, value)
   df
 }
+
+#' @rdname enframe
+#' @description
+#' `deframe()` converts two-column data frames to a named vector or list,
+#' using the first column as name and the second column as value.
+#' @export
+deframe <- function(x) {
+  value <- x[[2L]]
+  name <- x[[1L]]
+  names(value) <- name
+  value
+}
diff --git a/R/glimpse.R b/R/glimpse.R
index 82ef854..8e93529 100644
--- a/R/glimpse.R
+++ b/R/glimpse.R
@@ -2,20 +2,20 @@
 #'
 #' This is like a transposed version of print: columns run down the page,
 #' and data runs across. This makes it possible to see every column in
-#' a data frame. It's a little like \code{\link{str}} applied to a data frame
+#' a data frame. It's a little like [str()] applied to a data frame
 #' but it tries to show you as much data as possible. (And it always shows
 #' the underlying data, even when applied to a remote data source.)
 #'
 #' @section S3 methods:
-#' \code{glimpse} is an S3 generic with a customised method for \code{tbl}s and
-#' \code{data.frames}, and a default method that calls \code{\link{str}}.
+#' `glimpse` is an S3 generic with a customised method for `tbl`s and
+#' `data.frames`, and a default method that calls [str()].
 #'
 #' @param x An object to glimpse at.
 #' @param width Width of output: defaults to the setting of the option
-#'   \code{tibble.width} (if finite) or the width of the console.
+#'   `tibble.width` (if finite) or the width of the console.
 #' @param ... Other arguments passed onto individual methods.
-#' @return x original x is (invisibly) returned, allowing \code{glimpse} to be
-#' used within a data pipe line.
+#' @return x original x is (invisibly) returned, allowing `glimpse()` to be
+#'   used within a data pipe line.
 #' @export
 #' @examples
 #' glimpse(mtcars)
@@ -31,28 +31,30 @@ glimpse <- function(x, width = NULL, ...) {
 #' @export
 glimpse.tbl <- function(x, width = NULL, ...) {
   width <- tibble_glimpse_width(width)
-  stopifnot(is.finite(width))
+  if (!is.finite(width)) {
+    stopc("`width` must be finite")
+  }
 
-  cat("Observations: ", big_mark(nrow(x)), "\n", sep = "")
+  cat_line("Observations: ", big_mark(nrow(x)))
   if (ncol(x) == 0) return(invisible())
 
-  cat("Variables: ", big_mark(ncol(x)), "\n", sep = "")
+  cat_line("Variables: ", big_mark(ncol(x)))
 
   # this is an overestimate, but shouldn't be too expensive.
   # every type needs at least three characters: "x, "
   rows <- as.integer(width / 3)
   df <- as.data.frame(head(x, rows))
 
-  var_types <- vapply(df, type_sum, character(1))
-  var_names <- paste0("$ ", format(names(df)), " <", var_types, "> ")
+  var_types <- map_chr(x, type_sum)
+  ticked_names <- tick_non_syntactic(names(x))
+  var_names <- paste0("$ ", justify(ticked_names, right = FALSE), " <", var_types, "> ")
 
   data_width <- width - nchar(var_names) - 2
 
-  formatted <- vapply(df, function(x) paste0(format_v(x), collapse = ", "),
-    character(1), USE.NAMES = FALSE)
+  formatted <- map_chr(df, function(x) collapse(format_v(x)))
   truncated <- str_trunc(formatted, data_width)
 
-  cat(paste0(var_names, truncated, collapse = "\n"), "\n", sep = "")
+  cat_line(var_names, truncated)
   invisible(x)
 }
 
@@ -85,14 +87,11 @@ format_v.default <- function(x) format(x, trim = TRUE, justify = "none")
 
 #' @export
 format_v.list <- function(x) {
-  x <- lapply(x, format_v)
-  atomic <- vapply(x, length, integer(1L)) == 1L
-  x <- vapply(x, function(x) paste(x, collapse = ", "), character(1L))
-  x[!atomic] <- paste0("<", x[!atomic], ">")
-  if (length(x) == 1L)
-    x
-  else
-    paste0("[", paste(x, collapse = ", "), "]")
+  out <- map(x, format_v)
+  atomic <- map_int(out, length) == 1L
+  out <- map_chr(out, collapse)
+  out[!atomic] <- paste0("<", out[!atomic], ">")
+  paste0("[", collapse(out), "]")
 }
 
 #' @export
diff --git a/R/has-name.R b/R/has-name.R
index 60a17b4..7cdad32 100644
--- a/R/has-name.R
+++ b/R/has-name.R
@@ -1,18 +1,3 @@
-#' Convenience function to check presence of a named element
-#'
-#' This function returns a logical value that indicates if a data frame or
-#' another named object contains an element with a specific name.
-#'
-#' Unnamed objects are treated as if all names are empty strings.
-#' \code{NA} input gives \code{FALSE} as output.
-#'
-#' @param x A data frame or another named object
-#' @param name Element name(s) to check
-#' @return A logical vector of the same length as \code{name}
-#' @examples
-#' has_name(iris, "Species")
-#' has_name(mtcars, "gears")
+#' @inherit rlang::has_name
 #' @export
-has_name <- function(x, name) {
-  name %in% names2(x)
-}
+has_name <- function(x, name) rlang::has_name(x, name)
diff --git a/R/lst.R b/R/lst.R
new file mode 100644
index 0000000..4412a80
--- /dev/null
+++ b/R/lst.R
@@ -0,0 +1,69 @@
+#' Build a list
+#'
+#' `lst()` is similar to [list()], but like `tibble()`, it
+#' evaluates its arguments lazily and in order, and automatically adds names.
+#'
+#' @export
+#' @examples
+#' lst(n = 5, x = runif(n))
+#'
+#' # You can splice-unquote a list of quotes and formulas
+#' lst(!!! list(n = rlang::quo(2 + 3), y = quote(runif(n))))
+#'
+#' @export
+#' @rdname tibble
+lst <- function(...) {
+  xs <- quos(..., .named = 500L)
+  lst_quos(xs)
+}
+
+lst_quos <- function(xs, expand = FALSE) {
+  n <- length(xs)
+  if (n == 0) {
+    return(list())
+  }
+
+  # Evaluate each column in turn
+  col_names <- names2(xs)
+  output <- list_len(n)
+  names(output) <- character(n)
+
+  for (i in seq_len(n)) {
+    unique_output <- output[!duplicated(names(output)[seq_len(i)], fromLast = TRUE)]
+    res <- eval_tidy(xs[[i]], unique_output)
+    if (!is_null(res)) {
+      output[[i]] <-  res
+      if (expand) output <- expand_lst(output, i)
+    }
+    names(output)[i] <- col_names[[i]]
+  }
+
+  output
+}
+
+expand_lst <- function(output, i) {
+  idx_to_fix <- integer()
+  if (i > 1L) {
+    if (length(output[[i]]) == 1L && length(output[[1L]]) != 1L) {
+      idx_to_fix <- i
+      idx_boilerplate <- 1L
+    } else if (length(output[[i]]) != 1L && all(map(output[seq2(1L, i - 1L)], length) == 1L)) {
+      idx_to_fix <- seq2(1L, i - 1L)
+      idx_boilerplate <- i
+    }
+  }
+
+  if (length(idx_to_fix) > 0L) {
+    ones <- rep(1L, length(output[[idx_boilerplate]]))
+    output[idx_to_fix] <- map(output[idx_to_fix], `[`, ones)
+  }
+
+  output
+}
+
+#' @export
+#' @rdname tibble
+lst_ <- function(xs) {
+  xs <- compat_lazy_dots(xs, caller_env())
+  lst(!!! xs)
+}
diff --git a/R/repair-names.R b/R/repair-names.R
index 828ca68..ef58c27 100644
--- a/R/repair-names.R
+++ b/R/repair-names.R
@@ -1,21 +1,96 @@
 #' Repair object names.
 #'
-#' \code{repair_names} ensures its input has non-missing and
-#' unique names (duplicated names get a numeric suffix). Valid names are
-#' left as is.
-#'
+#' `tidy_names()` ensures its input has non-missing and
+#' unique names (duplicated names get a suffix of the format `..#`
+#' where `#` is the position in the vector).
+#' Valid names are left unchanged, with the exception that existing suffixes
+#' are reorganized.
 #' @param x A named vector.
+#' @param syntactic Should all names be made syntactically valid via [make.names()]?
+#' @param quiet If `TRUE` suppresses output from this function
+#' @return `x` with valid names.
+#' @examples
+#' # Works for lists and vectors, too:
+#' set_tidy_names(3:5)
+#' set_tidy_names(list(3, 4, 5))
+#'
+#' # Clean data frames are left unchanged:
+#' set_tidy_names(mtcars)
+#'
+#' # By default, all rename operations are printed to the console:
+#' tbl <- as_tibble(structure(list(3, 4, 5), class = "data.frame"),
+#'                  validate = FALSE)
+#' set_tidy_names(tbl)
+#'
+#' # Optionally, names can be made syntactic:
+#' tidy_names("a b", syntactic = TRUE)
+#' @export
+#' @rdname tidy_names
+set_tidy_names <- function(x, syntactic = FALSE, quiet = FALSE) {
+  new_names <- tidy_names(names2(x), syntactic, quiet)
+  set_names(x, new_names)
+}
+
+#' @description
+#' `tidy_names()` is the workhorse behind `set_tidy_names()`, it treats the
+#' argument as a string to be used to name a data frame or a vector.
+#' @param name A character vector representing names.
+#' @export
+tidy_names <- function(name, syntactic = FALSE, quiet = FALSE) {
+  name[is.na(name)] <- ""
+  orig_name <- name
+
+  name <- make_syntactic(name, syntactic)
+  name <- append_pos(name)
+
+  describe_tidying(orig_name, name, quiet)
+  name
+}
+
+make_syntactic <- function(name, syntactic) {
+  if (!syntactic) return(name)
+
+  blank <- name == ""
+  fix_syntactic <- (name != "") & !is_syntactic(name)
+  name[fix_syntactic] <- make.names(name[fix_syntactic])
+  name
+}
+
+append_pos <- function(name) {
+  need_append_pos <- duplicated(name) | duplicated(name, fromLast = TRUE) | name == ""
+  if (any(need_append_pos)) {
+    rx <- "[.][.][1-9][0-9]*$"
+    has_suffix <- grepl(rx, name)
+    name[has_suffix] <- gsub(rx, "", name[has_suffix])
+    need_append_pos <- need_append_pos | has_suffix
+  }
+
+  need_append_pos <- which(need_append_pos)
+  name[need_append_pos] <- paste0(name[need_append_pos], "..", need_append_pos)
+  name
+}
+
+describe_tidying <- function(orig_name, name, quiet) {
+  stopifnot(length(orig_name) == length(name))
+  if (quiet) return()
+  new_names <- name != orig_name
+  if (any(new_names)) {
+    message(
+      "New names:\n",
+      paste0(orig_name[new_names], " -> ", name[new_names], collapse = "\n")
+    )
+  }
+}
+
+#' @rdname tidy_names
+#' @description
+#' `repair_names()` is an older version with different renaming heuristics,
+#' kept for backward compatibility.  New code should prefer `tidy_names()`.
+#'
 #' @param prefix A string, the prefix to use for new column names.
 #' @param sep A string inserted between the column name and de-duplicating
 #'    number.
-#' @return \code{x} with valid names.
 #' @export
-#' @examples
-#' repair_names(list(3, 4, 5)) # works for lists, too
-#' repair_names(mtcars) # a no-op
-#' tbl <- as_tibble(structure(list(3, 4, 5), class = "data.frame"),
-#'                  validate = FALSE)
-#' repair_names(tbl)
 repair_names <- function(x, prefix = "V", sep = "") {
   if (length(x) == 0) {
     names(x) <- character()
@@ -23,7 +98,7 @@ repair_names <- function(x, prefix = "V", sep = "") {
   }
 
   new_names <- make_unique(names2(x), prefix = prefix, sep = sep)
-  setNames(x, new_names)
+  set_names(x, new_names)
 }
 
 make_unique <- function(x, prefix = "V", sep = "") {
diff --git a/R/rownames.R b/R/rownames.R
index 05fd7c9..a3d12ff 100644
--- a/R/rownames.R
+++ b/R/rownames.R
@@ -1,15 +1,18 @@
 #' Tools for working with row names
 #'
 #' While a tibble can have row names (e.g., when converting from a regular data
-#' frame), they are removed when subsetting with the \code{[} operator.
-#' A warning will be raised when attempting to assign non-\code{NULL} row names
+#' frame), they are removed when subsetting with the `[` operator.
+#' A warning will be raised when attempting to assign non-`NULL` row names
 #' to a tibble.
 #' Generally, it is best to avoid row names, because they are basically a
 #' character column with different semantics to every other column. These
 #' functions allow to you detect if a data frame has row names
-#' (\code{has_rownames}), remove them (\code{remove_rownames}), or convert
-#' them back-and-forth between an explicit column (\code{rownames_to_column}
-#' and \code{column_to_rownames}).
+#' (`has_rownames()`), remove them (`remove_rownames()`), or convert
+#' them back-and-forth between an explicit column (`rownames_to_column()`
+#' and `column_to_rownames()`).
+#' Also included is `rowid_to_column()` which
+#' adds a column at the start of the dataframe of ascending sequential row
+#' ids starting at 1. Note that this will remove any existing row names.
 #'
 #' In the printed output, the presence of row names is indicated by a star just
 #' above the row numbers.
@@ -49,19 +52,24 @@ remove_rownames <- function(df) {
 rownames_to_column <- function(df, var = "rowname") {
   stopifnot(is.data.frame(df))
 
-  if (has_name(df, var))
-    stopc("There is a column named ", var, " already!")
+  if (has_name(df, var)) {
+    stopc("Column `", var, "` already exists")
+  }
 
-  rn <- tibble(rownames(df))
-  names(rn) <- var
+  new_df <- add_column(df, !!(var) := rownames(df), .before = 1)
+  new_df
+}
 
-  attribs <- attributes(df)
+#' @export
+#' @rdname rownames
+rowid_to_column <- function(df, var = "rowid") {
+  stopifnot(is.data.frame(df))
 
-  new_df <- c(rn, df)
-  attribs[["names"]] <- names(new_df)
+  if (has_name(df, var)) {
+    stopc("Column `", var, "` already exists")
+  }
 
-  attributes(new_df) <- attribs[names(attribs) != "row.names"]
-  attr(new_df, "row.names") <- .set_row_names(nrow(df))
+  new_df <- add_column(df, !!(var) := seq_len(nrow(df)), .before = 1)
   new_df
 }
 
@@ -70,11 +78,13 @@ rownames_to_column <- function(df, var = "rowname") {
 column_to_rownames <- function(df, var = "rowname") {
   stopifnot(is.data.frame(df))
 
-  if (has_rownames(df))
-    stopc("This data frame already has row names.")
+  if (has_rownames(df)) {
+    stopc("`df` already has row names")
+  }
 
-  if (!has_name(df, var))
-    stopc("This data frame has no column named ", var, ".")
+  if (!has_name(df, var)) {
+    stopc("Column `num2` not found")
+  }
 
   rownames(df) <- df[[var]]
   df[[var]] <- NULL
@@ -83,7 +93,7 @@ column_to_rownames <- function(df, var = "rowname") {
 
 #' @export
 `row.names<-.tbl_df` <- function(x, value) {
-  if (!is.null(value)) {
+  if (!is_null(value)) {
     warningc("Setting row names on a tibble is deprecated.")
   }
   NextMethod()
diff --git a/R/tbl-df.r b/R/tbl-df.r
index 62a4bf0..bdba92d 100644
--- a/R/tbl-df.r
+++ b/R/tbl-df.r
@@ -10,11 +10,25 @@ as.data.frame.tbl_df <- function(x, row.names = NULL, optional = FALSE, ...) {
 
 #' @rdname formatting
 #' @export
-print.tbl_df <- function(x, ..., n = NULL, width = NULL, n_extra = NULL) {
-  print(trunc_mat(x, n = n, width = width, n_extra = n_extra))
+format.tbl <- function(x, ..., n = NULL, width = NULL, n_extra = NULL) {
+  format(trunc_mat(x, n = n, width = width, n_extra = n_extra))
+}
+
+#' @rdname formatting
+#' @export
+format.tbl_df <- format.tbl
+
+#' @rdname formatting
+#' @export
+print.tbl <- function(x, ..., n = NULL, width = NULL, n_extra = NULL) {
+  cat_line(format(x, ..., n = n, width = width, n_extra = n_extra))
   invisible(x)
 }
 
+#' @rdname formatting
+#' @export
+print.tbl_df <- print.tbl
+
 #' @export
 `[[.tbl_df` <- function(x, i, j, ..., exact = TRUE) {
   if (missing(j))
@@ -31,7 +45,7 @@ print.tbl_df <- function(x, ..., n = NULL, width = NULL, n_extra = NULL) {
 #' @export
 `$.tbl_df` <- function(x, i) {
   if (is.character(i) && !has_name(x, i)) {
-    warningc("Unknown column '", i, "'")
+    warningc("Unknown or uninitialised column: '", i, "'.")
   }
   .subset2(x, i)
 }
@@ -57,19 +71,21 @@ print.tbl_df <- function(x, ..., n = NULL, width = NULL, n_extra = NULL) {
   # First, subset columns
   if (!missing(j)) {
     j <- check_names_df(j, x)
-    x <- .subset(x, j)
+    result <- .subset(x, j)
+  } else {
+    result <- x
   }
 
   # Next, subset rows
   if (!missing(i)) {
-    if (length(x) == 0) {
+    if (length(result) == 0) {
       nr <- length(attr(x, "row.names")[i])
     } else {
-      x <- lapply(x, `[`, i)
-      nr <- length(x[[1]])
+      result <- map(result, `[`, i)
+      nr <- length(result[[1]])
     }
   }
 
-  attr(x, "row.names") <- .set_row_names(nr)
-  as_tibble.data.frame(x, validate = FALSE)
+  attr(result, "row.names") <- .set_row_names(nr)
+  as_tibble.data.frame(result, validate = FALSE)
 }
diff --git a/R/tibble-pkg.R b/R/tibble-pkg.R
new file mode 100644
index 0000000..d6863f0
--- /dev/null
+++ b/R/tibble-pkg.R
@@ -0,0 +1,65 @@
+#' @useDynLib tibble, .registration = TRUE
+#' @importFrom Rcpp sourceCpp
+#' @importFrom utils head tail
+#' @import rlang
+#' @aliases NULL
+#' @details The S3 class `tbl_df` wraps a local data frame. The main
+#' advantage to using a `tbl_df` over a regular data frame is the printing:
+#' tbl objects only print a few rows and all the columns that fit on one screen,
+#' describing the rest of it as text.
+#'
+#' @section Methods:
+#'
+#' `tbl_df` implements four important base methods:
+#'
+#' \describe{
+#' \item{print}{By default only prints the first 10 rows (at most 20), and the
+#'   columns that fit on screen; see [print.tbl()]}
+#' \item{\code{[}}{Never simplifies (drops), so always returns data.frame}
+#' \item{\code{[[}, `$`}{Calls [.subset2()] directly,
+#'   so is considerably faster. Returns `NULL` if column does not exist,
+#'   `$` warns.}
+#' }
+#' @section Important functions:
+#' [tibble()] and [tribble()] for construction,
+#' [as_tibble()] for coercion,
+#' and [print.tbl()] and [glimpse()] for display.
+"_PACKAGE"
+
+#' @name tibble-package
+#' @section Package options:
+#' Display options for `tbl_df`, used by [trunc_mat()] and
+#' (indirectly) by [print.tbl()].
+#' \describe{
+(op.tibble <- list(
+  #' \item{`tibble.print_max`}{Row number threshold: Maximum number of rows
+  #'   printed. Set to `Inf` to always print all rows.  Default: 20.}
+  tibble.print_max = 20L,
+
+  #' \item{`tibble.print_min`}{Number of rows printed if row number
+  #'   threshold is exceeded. Default: 10.}
+  tibble.print_min = 10L,
+
+  #' \item{`tibble.width`}{Output width. Default: `NULL` (use
+  #'   `width` option).}
+  tibble.width = NULL,
+
+  #' \item{`tibble.max_extra_cols`}{Number of extra columns
+  #'   printed in reduced form. Default: 100.}
+  tibble.max_extra_cols = 100L
+  #' }
+))
+
+tibble_opt <- function(x) {
+  x_tibble <- paste0("tibble.", x)
+  res <- getOption(x_tibble)
+  if (!is.null(res))
+    return(res)
+
+  x_dplyr <- paste0("dplyr.", x)
+  res <- getOption(x_dplyr)
+  if (!is.null(res))
+    return(res)
+
+  op.tibble[[x_tibble]]
+}
diff --git a/R/tibble.R b/R/tibble.R
index 2071144..b148b6b 100644
--- a/R/tibble.R
+++ b/R/tibble.R
@@ -1,65 +1,157 @@
-#' @useDynLib tibble
-#' @importFrom Rcpp sourceCpp
-#' @import assertthat
-#' @importFrom utils head tail
-#' @aliases NULL
-#' @details The S3 class \code{tbl_df} wraps a local data frame. The main
-#' advantage to using a \code{tbl_df} over a regular data frame is the printing:
-#' tbl objects only print a few rows and all the columns that fit on one screen,
-#' describing the rest of it as text.
+#' Build a data frame or list.
 #'
-#' @section Methods:
+#' @description
+#' `tibble()` is a trimmed down version of [data.frame()] that:
 #'
-#' \code{tbl_df} implements four important base methods:
+#' * Never coerces inputs (i.e. strings stay as strings!).
+#' * Never adds `row.names`.
+#' * Never munges column names.
+#' * Only recycles length 1 inputs.
+#' * Evaluates its arguments lazily and in order.
+#' * Adds `tbl_df` class to output.
+#' * Automatically adds column names.
 #'
-#' \describe{
-#' \item{print}{By default only prints the first 10 rows (at most 20), and the
-#'   columns that fit on screen; see \code{\link{print.tbl_df}}}
-#' \item{\code{[}}{Never simplifies (drops), so always returns data.frame}
-#' \item{\code{[[}, \code{$}}{Calls \code{\link{.subset2}} directly,
-#'   so is considerably faster. Returns \code{NULL} if column does not exist,
-#'   \code{$} warns.}
+#'
+#' @param ... A set of name-value pairs. Arguments are evaluated sequentially,
+#'   so you can refer to previously created variables.
+#' @param xs  A list of unevaluated expressions created with `~`,
+#'   [quote()], or (deprecated) [lazyeval::lazy()].
+#' @seealso [as_tibble()] to turn an existing list into
+#'   a data frame.
+#' @export
+#' @examples
+#' a <- 1:5
+#' tibble(a, b = a * 2)
+#' tibble(a, b = a * 2, c = 1)
+#' tibble(x = runif(10), y = x * 2)
+#'
+#' lst(n = 5, x = runif(n))
+#'
+#' # tibble never coerces its inputs
+#' str(tibble(letters))
+#' str(tibble(x = list(diag(1), diag(2))))
+#'
+#' # or munges column names
+#' tibble(`a + b` = 1:5)
+#'
+#' # You can splice-unquote a list of quotes and formulas
+#' tibble(!!! list(x = rlang::quo(1:10), y = quote(x * 2)))
+#'
+#' # data frames can only contain 1d atomic vectors and lists
+#' # and can not contain POSIXlt
+#' \dontrun{
+#' tibble(x = tibble(1, 2, 3))
+#' tibble(y = strptime("2000/01/01", "%x"))
 #' }
-#' @section Important functions:
-#' \code{\link{tibble}} and \code{\link{tribble}} for construction,
-#' \code{\link{as_tibble}} for coercion,
-#' and \code{\link{print.tbl_df}} and \code{\link{glimpse}} for display.
-"_PACKAGE"
-
-#' @name tibble-package
-#' @section Package options:
-#' Display options for \code{tbl_df}, used by \code{\link{trunc_mat}} and
-#' (indirectly) by \code{\link{print.tbl_df}}.
-#' \describe{
-(op.tibble <- list(
-  #' \item{\code{tibble.print_max}}{Row number threshold: Maximum number of rows
-  #'   printed. Set to \code{Inf} to always print all rows.  Default: 20.}
-  tibble.print_max = 20L,
-
-  #' \item{\code{tibble.print_min}}{Number of rows printed if row number
-  #'   threshold is exceeded. Default: 10.}
-  tibble.print_min = 10L,
-
-  #' \item{\code{tibble.width}}{Output width. Default: \code{NULL} (use
-  #'   \code{width} option).}
-  tibble.width = NULL,
-
-  #' \item{\code{tibble.max_extra_cols}}{Number of extra columns
-  #'   printed in reduced form. Default: 100.}
-  tibble.max_extra_cols = 100L
-  #' }
-))
-
-tibble_opt <- function(x) {
-  x_tibble <- paste0("tibble.", x)
-  res <- getOption(x_tibble)
-  if (!is.null(res))
-    return(res)
-
-  x_dplyr <- paste0("dplyr.", x)
-  res <- getOption(x_dplyr)
-  if (!is.null(res))
-    return(res)
-
-  op.tibble[[x_tibble]]
+tibble <- function(...) {
+  xs <- quos(..., .named = TRUE)
+  as_tibble(lst_quos(xs, expand = TRUE))
+}
+
+#' @export
+#' @rdname tibble
+tibble_ <- function(xs) {
+  xs <- compat_lazy_dots(xs, caller_env())
+  tibble(!!! xs)
+}
+
+#' @export
+#' @rdname tibble
+#' @usage NULL
+data_frame <- tibble
+
+#' @export
+#' @rdname tibble
+#' @usage NULL
+data_frame_ <- tibble_
+
+
+#' Test if the object is a tibble.
+#'
+#' @param x An object
+#' @return `TRUE` if the object inherits from the `tbl_df` class.
+#' @export
+is.tibble <- function(x) {
+  "tbl_df" %in% class(x)
 }
+
+#' @rdname is.tibble
+#' @export
+is_tibble <- is.tibble
+
+
+# Validity checks --------------------------------------------------------------
+
+check_tibble <- function(x) {
+  # Names
+  names_x <- names2(x)
+  bad_name <- is.na(names_x) | names_x == ""
+  if (any(bad_name)) {
+    invalid_df("must be named", x, which(bad_name))
+  }
+
+  dups <- duplicated(names_x)
+  if (any(dups)) {
+    invalid_df("must have [a] unique name(s)", x, dups)
+  }
+
+  # Types
+  is_1d <- map_lgl(x, is_1d)
+  if (any(!is_1d)) {
+    invalid_df("must be [a] 1d atomic vector(s) or [a] list(s)", x, !is_1d)
+  }
+
+  x[] <- map(x, strip_dim)
+
+  posixlt <- map_lgl(x, inherits, "POSIXlt")
+  if (any(posixlt)) {
+    invalid_df("[is](are) [a] date(s)/time(s) and must be stored as POSIXct, not POSIXlt", x, posixlt)
+  }
+
+  x
+}
+
+recycle_columns <- function(x) {
+  if (length(x) == 0) {
+    return(x)
+  }
+
+  # Validate column lengths
+  lengths <- map_int(x, NROW)
+  max <- max(c(lengths[lengths != 1L], 0L))
+
+  bad_len <- lengths != 1L & lengths != max
+  if (any(bad_len)) {
+    invalid_df_msg(
+      paste0("must be length 1 or ", max, ", not "), x, bad_len, lengths[bad_len]
+    )
+  }
+
+  short <- lengths == 1
+  if (max > 1L && any(short)) {
+    x[short] <- map(x[short], rep, max)
+  }
+
+  x
+}
+
+invalid_df <- function(problem, df, vars) {
+  if (is.logical(vars)) {
+    vars <- names(df)[vars]
+  }
+  stopc(
+    pluralise_msg("Column(s) ", vars), " ",
+    pluralise(problem, vars)
+  )
+}
+
+invalid_df_msg <- function(problem, df, vars, extra) {
+  if (is.logical(vars)) {
+    vars <- names(df)[vars]
+  }
+  stopc(
+    pluralise_msg("Column(s) ", vars), " ",
+    pluralise_msg(problem, extra)
+  )
+}
+
diff --git a/R/tribble.R b/R/tribble.R
index e3e3ea7..4560681 100644
--- a/R/tribble.R
+++ b/R/tribble.R
@@ -1,15 +1,15 @@
 #' Row-wise tibble creation
 #'
-#' Create \code{\link{tibble}}s laying out the data in rows, rather than
-#' in columns. This is useful for small tables of data where readability is
+#' Create [tibble]s using an easier to read row-by-row layout.
+#' This is useful for small tables of data where readability is
 #' important.  Please see \link{tibble-package} for a general introduction.
 #'
-#' \code{frame_data()} is an older name for \code{tribble()}. It will eventually
+#' `frame_data()` is an older name for `tribble()`. It will eventually
 #' be phased out.
 #'
-#' @param ... Arguments specifying the structure of a \code{tibble}.
+#' @param ... Arguments specifying the structure of a `tibble`.
 #'   Variable names should be formulas, and may only appear before the data.
-#' @return A \code{\link{tibble}}.
+#' @return A [tibble].
 #' @export
 #' @examples
 #' tribble(
@@ -27,19 +27,63 @@
 #'   "b", 4:6
 #' )
 tribble <- function(...) {
+  data <- extract_frame_data_from_dots(...)
+  turn_frame_data_into_tibble(data$frame_names, data$frame_rest)
+}
+
+#' @export
+#' @rdname tribble
+#' @usage NULL
+frame_data <- tribble
 
+#' Row-wise matrix creation
+#'
+#' Create matrices laying out the data in rows, similar to
+#' `matrix(..., byrow = TRUE)`, with a nicer-to-read syntax.
+#' This is useful for small matrices, e.g. covariance matrices, where readability
+#' is important. The syntax is inspired by [tribble()].
+#'
+#' @param ... Arguments specifying the structure of a `frame_matrix`.
+#'   Column names should be formulas, and may only appear before the data.
+#' @return A [matrix].
+#' @export
+#' @examples
+#' frame_matrix(
+#'   ~col1, ~col2,
+#'   1,     3,
+#'   5,     2
+#' )
+frame_matrix <- function(...) {
+  data <- extract_frame_data_from_dots(...)
+  turn_frame_data_into_frame_matrix(data$frame_names, data$frame_rest)
+}
+
+extract_frame_data_from_dots <- function(...) {
   dots <- list(...)
 
   # Extract the names.
+  frame_names <- extract_frame_names_from_dots(dots)
+
+  # Extract the data
+  if (length(frame_names) == 0 && length(dots) != 0) {
+    stopc("Expected at least one column name; e.g. `~name`")
+  }
+  frame_rest <- dots[-seq_along(frame_names)]
+  if (length(frame_rest) == 0L) {
+    # Can't decide on type in absence of data -- use logical which is
+    # coercible to all types
+    frame_rest <- logical()
+  }
+
+  validate_rectangular_shape(frame_names, frame_rest)
+
+  list(frame_names = frame_names, frame_rest = frame_rest)
+}
+
+extract_frame_names_from_dots <- function(dots) {
   frame_names <- character()
-  i <- 1
-  while (TRUE) {
-    if (i > length(dots)) {
-      out <- rep(list(logical()), length(frame_names))
-      names(out) <- frame_names
-      return(as_tibble(out))
-    }
 
+  for (i in seq_along(dots)) {
     el <- dots[[i]]
     if (!is.call(el))
       break
@@ -48,55 +92,71 @@ tribble <- function(...) {
       break
 
     if (length(el) != 2) {
-      stopc("expected a column name with a single argument; e.g. '~ name'")
+      stopc("Expected a column name with a single argument; e.g. `~name`")
     }
 
     candidate <- el[[2]]
     if (!(is.symbol(candidate) || is.character(candidate))) {
-      stopc("expected a symbol or string denoting a column name")
+      stopc(
+        "Expected a symbol or string denoting a column name, not ",
+        friendly_type(type_of(candidate))
+      )
     }
 
     frame_names <- c(frame_names, as.character(el[[2]]))
-
-    i <- i + 1
   }
 
-  if (!length(frame_names)) {
-    stopc("no column names detected in 'tribble()' call")
-  }
+  frame_names
+}
 
-  frame_rest <- dots[i:length(dots)]
-  n_elements <- length(frame_rest)
+validate_rectangular_shape <- function(frame_names, frame_rest) {
+  if (length(frame_names) == 0 && length(frame_rest) == 0) return();
 
   # Figure out the associated number of rows and number of columns,
   # and validate that the supplied formula produces a rectangular
   # structure.
-  frame_ncol <- length(frame_names)
-  if (n_elements %% frame_ncol != 0) {
+  if (length(frame_rest) %% length(frame_names) != 0) {
     stopc(
       sprintf(
-        "invalid 'tribble()' specification: had %s elements and %s columns",
-        n_elements,
-        frame_ncol
+        "invalid specification: had %s elements and %s columns",
+        length(frame_rest),
+        length(frame_names)
       )
     )
   }
+}
 
-  frame_mat <- matrix(frame_rest, ncol = frame_ncol, byrow = TRUE)
-  frame_col <- lapply(seq_len(ncol(frame_mat)), function(i) {
-    col <- frame_mat[, i]
-    if (any(vapply(col, needs_list_col, logical(1L)))) {
-      col
-    } else {
-      unlist(col)
-    }
-  })
+turn_frame_data_into_tibble <- function(names, rest) {
+  frame_mat <- matrix(rest, ncol = length(names), byrow = TRUE)
+  frame_col <- turn_matrix_into_column_list(frame_mat)
 
   # Create a tbl_df and return it
-  names(frame_col) <- frame_names
+  names(frame_col) <- names
   as_tibble(frame_col)
 }
 
-#' @export
-#' @rdname tribble
-frame_data <- tribble
+turn_matrix_into_column_list <- function(frame_mat) {
+  frame_col <- vector("list", length = ncol(frame_mat))
+  # if a frame_mat's col is a list column, keep it unchanged (does not unlist)
+  for (i in seq_len(ncol(frame_mat))) {
+    col <- frame_mat[, i]
+    if (some(col, needs_list_col) || !inherits(col, "list")) {
+      frame_col[[i]] <- col
+    } else {
+      frame_col[[i]] <- invoke(c, col)
+    }
+  }
+  return(frame_col)
+}
+
+turn_frame_data_into_frame_matrix <- function(names, rest) {
+  if (some(rest, needs_list_col)) {
+    stopc("Can't use list columns in `frame_matrix()`")
+  }
+
+  frame_ncol <- length(names)
+  frame_mat <- matrix(unlist(rest), ncol = frame_ncol, byrow = TRUE)
+
+  colnames(frame_mat) <- names
+  frame_mat
+}
diff --git a/R/type-sum.r b/R/type-sum.r
index 7016bdb..3a7bfa4 100644
--- a/R/type-sum.r
+++ b/R/type-sum.r
@@ -1,15 +1,8 @@
 #' Provide a succinct summary of an object
 #'
 #' @description
-#' \code{type_sum} gives a brief summary of object type. Objects that commonly
-#' occur in a data frame should return a string with four or less characters.
-#'
-#' \code{obj_sum} also includes the size of the object if \code{is_s3_vector}
-#' is \code{TRUE}.
-#'
-#' \code{tbl_sum} gives a brief textual description of a table-like object,
-#' which should include the dimensions, the data source, and possible grouping
-#' (for dplyr).  The default implementation forwards to \code{obj_sum}
+#' `obj_sum()` also includes the size of the object if `is_vector_s3()`
+#' is `TRUE`.
 #'
 #' @param x an object to summarise. Generally only methods of atomic vectors
 #'   and variants have been implemented.
@@ -30,7 +23,7 @@ obj_sum.default <- function(x) {
 
 #' @export
 obj_sum.list <- function(x) {
-  vapply(x, obj_sum.default, character(1L))
+  map_chr(x, obj_sum.default)
 }
 
 #' @export
@@ -39,6 +32,9 @@ obj_sum.POSIXlt <- function(x) {
 }
 
 #' @export
+#' @description
+#' `type_sum()` gives a brief summary of object type. Objects that commonly
+#' occur in a data frame should return a string with four or less characters.
 #' @rdname obj_sum
 type_sum <- function(x) UseMethod("type_sum")
 
@@ -77,6 +73,10 @@ type_sum.default <- function(x) {
 }
 
 #' @rdname obj_sum
+#' @description
+#' `tbl_sum()` gives a brief textual description of a table-like object,
+#' which should include the dimensions, the data source, and possible grouping
+#' (for `dplyr`).  The default implementation forwards to `obj_sum()`.
 #' @export
 tbl_sum <- function(x) UseMethod("tbl_sum", x)
 
@@ -84,29 +84,15 @@ tbl_sum <- function(x) UseMethod("tbl_sum", x)
 tbl_sum.default <- function(x) obj_sum(x)
 
 #' @export
-tbl_sum.tbl_df <- function(x) {
-  paste0("A tibble: ", dim_desc(x))
-}
-
-# FIXME: This belongs in dplyr, but can only be added there once tibble has been
-# updated.
-# nocov start
-#' @export
-tbl_sum.grouped_df <- function(x) {
-  NULL
-}
-
-#' @export
-tbl_sum.tbl_sql <- function(x) {
-  NULL
+tbl_sum.tbl <- function(x) {
+  c("A tibble" = dim_desc(x))
 }
-# nocov end
 
 dim_desc <- function(x) {
   dim <- dim(x) %||% length(x)
-  format_dim <- vapply(dim, big_mark, character(1))
+  format_dim <- map_chr(dim, big_mark)
   format_dim[is.na(dim)] <- "??"
-  paste0(format_dim, collapse = " \u00d7 ")
+  paste0(format_dim, collapse = spaces_around(mult_sign()))
 }
 
 size_sum <- function(x) {
diff --git a/R/utils-format.r b/R/utils-format.r
index 2360ea5..4f76b59 100644
--- a/R/utils-format.r
+++ b/R/utils-format.r
@@ -1,17 +1,17 @@
 #' Tools for describing matrices
 #'
 #' @param x Object to show.
-#' @param n Number of rows to show. If \code{NULL}, the default, will print
-#'   all rows if less than option \code{tibble.print_max}. Otherwise, will
-#'   print \code{tibble.print_min} rows.
+#' @param n Number of rows to show. If `NULL`, the default, will print
+#'   all rows if less than option `tibble.print_max`. Otherwise, will
+#'   print `tibble.print_min` rows.
 #' @param width Width of text output to generate. This defaults to NULL, which
-#'   means use \code{getOption("tibble.width")} or (if also NULL)
-#'   \code{getOption("width")}; the latter displays only the columns that
-#'   fit on one screen. You can also set \code{options(tibble.width = Inf)} to
+#'   means use `getOption("tibble.width")` or (if also NULL)
+#'   `getOption("width")`; the latter displays only the columns that
+#'   fit on one screen. You can also set `options(tibble.width = Inf)` to
 #'   override this default and always print all columns.
 #' @param n_extra Number of extra columns to print abbreviated information for,
-#'   if the width is too small for the entire tibble. If \code{NULL}, the
-#'   default, will print information about at most \code{tibble.max_extra_cols}
+#'   if the width is too small for the entire tibble. If `NULL`, the
+#'   default, will print information about at most `tibble.max_extra_cols`
 #'   extra columns.
 #' @seealso \link{tibble-package}
 #' @keywords internal
@@ -33,11 +33,10 @@ NULL
 
 #' @export
 #' @rdname formatting
-#' @importFrom stats setNames
 trunc_mat <- function(x, n = NULL, width = NULL, n_extra = NULL) {
   rows <- nrow(x)
 
-  if (is.null(n)) {
+  if (is_null(n)) {
     if (is.na(rows) || rows > tibble_opt("print_max")) {
       n <- tibble_opt("print_min")
     } else {
@@ -50,22 +49,26 @@ trunc_mat <- function(x, n = NULL, width = NULL, n_extra = NULL) {
   width <- tibble_width(width)
 
   shrunk <- shrink_mat(df, width, rows, n, star = has_rownames(x))
-  trunc_info <- list(width = width, rows_total = rows, rows_min = nrow(df),
-                     n_extra = n_extra, summary = tbl_sum(x))
+  trunc_info <- list(
+    width = width, rows_total = rows, rows_min = nrow(df),
+    n_extra = n_extra, summary = tbl_sum(x)
+  )
 
-  structure(c(shrunk, trunc_info), class = "trunc_mat")
+  structure(
+    c(shrunk, trunc_info),
+    class = c(paste0("trunc_mat_", class(x)), "trunc_mat")
+  )
 }
 
-#' @importFrom stats setNames
 shrink_mat <- function(df, width, rows, n, star) {
-  var_types <- vapply(df, type_sum, character(1))
+  var_types <- map_chr(df, type_sum)
 
   if (ncol(df) == 0 || nrow(df) == 0) {
     return(new_shrunk_mat(NULL, var_types))
   }
 
   df <- remove_rownames(df)
-  col_names <- tickit(colnames(df))
+  col_names <- tick_non_syntactic(colnames(df))
   names(var_types) <- col_names
 
   # Minimum width of each column is 5 "<int>", so we can make a quick first
@@ -74,17 +77,23 @@ shrink_mat <- function(df, width, rows, n, star) {
   extra_wide <- (seq_along(df) > max_cols)
   df[] <- df[!extra_wide]
 
-  # List columns need special treatment because format can't be trusted
-  classes <- paste0("<", vapply(df, type_sum, character(1)), ">")
-  is_list <- vapply(df, is.list, logical(1))
-  df[is_list] <- lapply(df[is_list], function(x) {
+  classes <- paste0("<", map_chr(df, type_sum), ">")
+
+  # List columns need special treatment because format() can't be trusted
+  is_list <- map_lgl(df, is.list)
+
+  df[is_list] <- map(df[is_list], function(x) {
     summary <- obj_sum(x)
     paste0("<", summary, ">")
   })
 
-  # Character columns need special treatment because of NA
-  is_character <- vapply(df, is.character, logical(1))
-  df[is_character] <- lapply(df[is_character], format_character)
+  # Character columns need special treatment because of NA and escapes
+  is_character <- map_lgl(df, is.character)
+  df[is_character] <- map(df[is_character], format_character)
+
+  # Factor columns need special treatment because of NA and escapes
+  is_factor <- map_lgl(df, is.factor)
+  df[is_factor] <- map(df[is_factor], format_factor)
 
   mat <- format(df, justify = "left")
   values <- c(format(rownames(mat))[[1]], unlist(mat[1, ]))
@@ -93,8 +102,8 @@ shrink_mat <- function(df, width, rows, n, star) {
   # Column needs to be as wide as widest of name, values, and class
   w <- pmax(
     pmax(
-      nchar_width(encodeString(values)),
-      nchar_width(encodeString(names))
+      nchar_width(values),
+      nchar_width(names)
     ),
     nchar_width(encodeString(c("", classes)))
   )
@@ -133,55 +142,65 @@ new_shrunk_mat <- function(table, extra, rows_missing = NULL) {
 }
 
 #' @export
-print.trunc_mat <- function(x, ...) {
-  print_summary(x)
-  print_table(x)
-  print_extra(x)
-  invisible(x)
-}
-
-print_summary <- function(x) {
-  summary <- format_summary(x)
-  if (length(summary) > 0) {
-    print_comment(summary, width = x$width)
+format.trunc_mat <- function(x, ...) {
+  named_header <- format_header(x)
+  if (all(names2(named_header) == "")) {
+    header <- named_header
+  } else {
+    header <- paste0(
+      justify(
+        paste0(names2(named_header), ":"), right = FALSE, space = "\u00a0"
+      ),
+      # We add a space after the NBSP inserted by justify()
+      # so that wrapping occurs at the right location for very narrow outputs
+      " ",
+      named_header
+    )
   }
+  c(
+    format_comment(header, width = x$width),
+    format_body(x),
+    format_comment(pre_dots(format_footer(x)), width = x$width)
+  )
 }
 
-print_table <- function(x) {
-  if (!is.null(x$table)) {
-    print(x$table)
-  }
+#' @export
+print.trunc_mat <- function(x, ...) {
+  cat_line(format(x, ...))
+  invisible(x)
 }
 
-print_extra <- function(x) {
-  extra <- format_extra(x)
-  if (length(extra) > 0) {
-    print_comment("... ", collapse(extra), width = x$width)
-  }
+format_header <- function(x) {
+  x$summary
 }
 
-print_comment <- function(..., width) {
-  cat_line(wrap(..., prefix = "# ", width = min(width, getOption("width"))))
-}
+format_body <- function(x) {
+  table <- x$table
+  if (is_null(table)) return()
 
-format_summary <- function(x) {
-  x$summary
+  table_with_row_names <- c(list(row.names(table)), table)
+  table_with_names <- map2(as.list(names(table_with_row_names)), table_with_row_names, c)
+  same_width_table <- map(table_with_names, justify)
+  rows <- invoke(paste, same_width_table)
+  rows
 }
 
-format_extra <- function(x) {
-  extra_rows <- format_extra_rows(x)
-  extra_cols <- format_extra_cols(x)
+format_footer <- function(x) {
+  extra_rows <- format_footer_rows(x)
+  extra_cols <- format_footer_cols(x)
 
   extra <- c(extra_rows, extra_cols)
   if (length(extra) >= 1) {
     extra[[1]] <- paste0("with ", extra[[1]])
-    extra[-1] <- vapply(extra[-1], function(ex) paste0("and ", ex), character(1))
+    extra[-1] <- map_chr(extra[-1], function(ex) paste0("and ", ex))
+    collapse(extra)
+  } else {
+    character()
   }
-  extra
 }
 
-format_extra_rows <- function(x) {
-  if (!is.null(x$table)) {
+format_footer_rows <- function(x) {
+  if (!is_null(x$table)) {
     if (is.na(x$rows_missing)) {
       "more rows"
     } else if (x$rows_missing > 0) {
@@ -192,7 +211,7 @@ format_extra_rows <- function(x) {
   }
 }
 
-format_extra_cols <- function(x) {
+format_footer_cols <- function(x) {
   if (length(x$extra) > 0) {
     var_types <- paste0(names(x$extra), NBSP, "<", x$extra, ">")
     if (x$n_extra > 0) {
@@ -209,15 +228,47 @@ format_extra_cols <- function(x) {
   }
 }
 
+format_comment <- function(x, width) {
+  if (length(x) == 0L) return(character())
+  map_chr(x, wrap, prefix = "# ", width = min(width, getOption("width")))
+}
+
+pre_dots <- function(x) {
+  if (length(x) > 0) {
+    paste0("... ", x)
+  } else {
+    character()
+  }
+}
+
+justify <- function(x, right = TRUE, space = " ") {
+  if (length(x) == 0L) return(character())
+  width <- nchar_width(x)
+  max_width <- max(width)
+  spaces_template <- paste(rep(space, max_width), collapse = "")
+  spaces <- map_chr(max_width - width, substr, x = spaces_template, start = 1L)
+  if (right) {
+    paste0(spaces, x)
+  } else {
+    paste0(x, spaces)
+  }
+}
+
 #' knit_print method for trunc mat
 #' @keywords internal
 #' @export
 knit_print.trunc_mat <- function(x, options) {
-  summary <- format_summary(x)
+  header <- format_header(x)
+  if (length(header) > 0L) {
+    header[names2(header) != ""] <- paste0(names2(header), ": ", header)
+    summary <- header
+  } else {
+    summary <- character()
+  }
 
   kable <- knitr::kable(x$table, row.names = FALSE)
 
-  extra <- format_extra(x)
+  extra <- format_footer(x)
 
   if (length(extra) > 0) {
     extra <- wrap("(", collapse(extra), ")", width = x$width)
@@ -243,9 +294,21 @@ wrap <- function(..., indent = 0, prefix = "", width) {
 
 
 
+format_factor <- function(x) {
+  format_character(as.character(x))
+}
+
 format_character <- function(x) {
-  x[is.na(x)] <- "<NA>"
-  x
+  res <- quote_escaped(x)
+  res[is.na(x)] <- "<NA>"
+  res
+}
+
+quote_escaped <- function(x) {
+  res <- encodeString(x, quote = '"')
+  plain <- which(res == paste0('"', x, '"'))
+  res[plain] <- x[plain]
+  res
 }
 
 # function for the thousand separator,
@@ -256,33 +319,66 @@ big_mark <- function(x, ...) {
 }
 
 tibble_width <- function(width) {
-  if (!is.null(width))
+  if (!is_null(width))
     return(width)
 
   width <- tibble_opt("width")
-  if (!is.null(width))
+  if (!is_null(width))
     return(width)
 
   getOption("width")
 }
 
 tibble_glimpse_width <- function(width) {
-  if (!is.null(width))
+  if (!is_null(width))
     return(width)
 
   width <- tibble_opt("width")
-  if (!is.null(width) && is.finite(width))
+  if (!is_null(width) && is.finite(width))
     return(width)
 
   getOption("width")
 }
 
+pluralise_msg <- function(message, objects) {
+  paste0(pluralise(message, objects), format_n(objects))
+}
+
+pluralise <- function(message, objects) {
+  pluralise_n(message, length(objects))
+}
+
+pluralise_n <- function(message, n) {
+  stopifnot(n > 0)
+  if (n == 1) {
+    # strip [, unless there is space in between
+    message <- gsub("\\[([^\\] ]+)\\]", "\\1", message, perl = TRUE)
+    # remove ( and its content, unless there is space in between
+    message <- gsub("\\([^\\) ]+\\)", "", message, perl = TRUE)
+  } else {
+    # strip (, unless there is space in between
+    message <- gsub("\\(([^\\) ]+)\\)", "\\1", message, perl = TRUE)
+    # remove [ and its content, unless there is space in between
+    message <- gsub("\\[[^\\] ]+\\]\\s*", "", message, perl = TRUE)
+  }
+
+  message
+}
+
+mult_sign <- function() {
+  "x"
+}
+
+spaces_around <- function(x) {
+  paste0(" ", x, " ")
+}
+
 format_n <- function(x) collapse(quote_n(x))
 
 quote_n <- function(x) UseMethod("quote_n")
 #' @export
 quote_n.default <- function(x) as.character(x)
 #' @export
-quote_n.character <- function(x) encodeString(x, quote = "'")
+quote_n.character <- function(x) tick(x)
 
 collapse <- function(x) paste(x, collapse = ", ")
diff --git a/R/utils.r b/R/utils.r
index d5589d3..384eeb5 100644
--- a/R/utils.r
+++ b/R/utils.r
@@ -1,31 +1,6 @@
-names2 <- function(x) {
-  xnames <- names(x)
-  if (is.null(xnames)) {
-    rep("", length(x))
-  } else {
-    ifelse(is.na(xnames), "", xnames)
-  }
-}
-
-"%||%" <- function(x, y) {
-  if(is.null(x)) y else x
-}
-
-# is.atomic() is TRUE for atomic vectors AND NULL!
-is_atomic <- function(x) {
-  is.atomic(x) && !is.null(x)
-}
-
-is_vector <- function(x) {
-  is_atomic(x) || is.list(x)
-}
-
-has_names <- function(x) {
-  !is.null(names(x))
-}
 
 has_dim <- function(x) {
-  length(dim(x)) > 0L || has_names(x)
+  length(dim(x)) > 0L || is_named(x)
 }
 
 needs_dim <- function(x) {
@@ -42,31 +17,33 @@ is_1d <- function(x) {
 strip_dim <- function(x) {
   # Careful update only if necessary, to avoid copying which is checked by
   # the "copying" test in dplyr
-  if (is_atomic(x) && has_dim(x))
+  if (is_atomic(x) && has_dim(x)) {
     dim(x) <- NULL
+  }
   x
 }
 
 needs_list_col <- function(x) {
-  is.list(x) || length(x) != 1L
+  is_list(x) || length(x) != 1L
 }
 
 # Work around bug in R 3.3.0
 safe_match <- function(x, table) {
   # nocov start
-  if (getRversion() == "3.3.0")
+  if (getRversion() == "3.3.0") {
     match(x, table, incomparables = character())
-  else
+  } else {
     match(x, table)
+  }
   # nocov end
 }
 
 stopc <- function(...) {
-  stop(..., call. = FALSE, domain = NA)
+  abort(paste0(...))
 }
 
 warningc <- function(...) {
-  warning(..., call. = FALSE, domain = NA)
+  warn(paste0(...))
 }
 
 nchar_width <- function(x) {
@@ -74,13 +51,22 @@ nchar_width <- function(x) {
 }
 
 cat_line <- function(...) {
-  cat(..., "\n", sep = "")
+  cat(paste0(..., "\n"), sep = "")
 }
 
-is_syntactic <- function(x) make.names(x) == x
+is_syntactic <- function(x) {
+  ret <- make.names(x) == x
+  ret[is.na(x)] <- FALSE
+  ret
+}
 
-tickit <- function(x) {
+tick_non_syntactic <- function(x) {
   needs_ticks <- !is_syntactic(x)
-  x[needs_ticks] <- paste0("`", gsub("`", "\\\\`", x[needs_ticks]), "`")
+  x[needs_ticks] <- tick(x[needs_ticks])
   x
 }
+
+tick <- function(x) {
+  x[is.na(x)] <- "NA"
+  encodeString(x, quote = "`")
+}
diff --git a/README.md b/README.md
index 551489b..75ca911 100644
--- a/README.md
+++ b/README.md
@@ -1,43 +1,63 @@
 
 <!-- README.md is generated from README.Rmd. Please edit that file -->
-tibble
-======
+tibble <img src="man/figures/logo.png" align="right" />
+=======================================================
 
-[![Build Status](https://travis-ci.org/hadley/tibble.svg?branch=master)](https://travis-ci.org/hadley/tibble) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/hadley/tibble?branch=master&svg=true)](https://ci.appveyor.com/project/hadley/tibble) [![Coverage Status](https://img.shields.io/codecov/c/github/hadley/tibble/master.svg)](https://codecov.io/github/hadley/tibble?branch=master) [![CRAN\_Status\_Badge](http://www.r-pkg.org/badges/version/tibble)](https:// [...]
+[![Build Status](https://travis-ci.org/tidyverse/tibble.svg?branch=master)](https://travis-ci.org/tidyverse/tibble) [![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/github/tidyverse/tibble?branch=master&svg=true)](https://ci.appveyor.com/project/tidyverse/tibble) [![codecov](https://codecov.io/gh/tidyverse/tibble/branch/master/graph/badge.svg)](https://codecov.io/gh/tidyverse/tibble) [![CRAN\_Status\_Badge](http://www.r-pkg.org/badges/version/tibble)](https://cran.r- [...]
 
-tibble implements a modern reimagining of the data.frame, keeping what time has proven to be effective, and throwing out what is not. It extracts these basic ideas out of [dplyr](https://cran.r-project.org/package=dplyr), which is now more clearly focused on data manipulation. tibble provides a lighter-weight package for the basic care and feeding of `tbl_df`'s, aka "tibble diffs" or just "tibbles". Tibbles are data.frames with nicer behavior around printing, subsetting, and factor handling.
+Overview
+--------
 
-Creating tibbles
-----------------
+A **tibble**, or `tbl_df`, is a modern reimagining of the data.frame, keeping what time has proven to be effective, and throwing out what is not. Tibbles are data.frames that are lazy and surly: they do less (i.e. they don't change variable names or types, and don't do partial matching) and complain more (e.g. when a variable does not exist). This forces you to confront problems earlier, typically leading to cleaner, more expressive code. Tibbles also have an enhanced `print method()` wh [...]
 
-You can create a tibble from an existing object with `as_tibble()`:
+If you are new to tibbles, the best place to start is the [tibbles](http://r4ds.had.co.nz/tibbles.html) in R for data science.
+
+Installation
+------------
+
+``` r
+# The easiest way to get tibble is to install the whole tidyverse:
+install.packages("tidyverse")
+
+# Alternatively, install just tibble:
+install.packages("tibble")
+
+# Or the the development version from GitHub:
+# install.packages("devtools")
+devtools::install_github("tidyverse/tibble")
+```
+
+Usage
+-----
+
+Create a tibble from an existing object with `as_tibble()`:
 
 ``` r
 library(tibble)
 as_tibble(iris)
-#> # A tibble: 150 × 5
+#> # A tibble: 150 x 5
 #>    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
 #>           <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
-#> 1           5.1         3.5          1.4         0.2  setosa
-#> 2           4.9         3.0          1.4         0.2  setosa
-#> 3           4.7         3.2          1.3         0.2  setosa
-#> 4           4.6         3.1          1.5         0.2  setosa
-#> 5           5.0         3.6          1.4         0.2  setosa
-#> 6           5.4         3.9          1.7         0.4  setosa
-#> 7           4.6         3.4          1.4         0.3  setosa
-#> 8           5.0         3.4          1.5         0.2  setosa
-#> 9           4.4         2.9          1.4         0.2  setosa
+#>  1          5.1         3.5          1.4         0.2  setosa
+#>  2          4.9         3.0          1.4         0.2  setosa
+#>  3          4.7         3.2          1.3         0.2  setosa
+#>  4          4.6         3.1          1.5         0.2  setosa
+#>  5          5.0         3.6          1.4         0.2  setosa
+#>  6          5.4         3.9          1.7         0.4  setosa
+#>  7          4.6         3.4          1.4         0.3  setosa
+#>  8          5.0         3.4          1.5         0.2  setosa
+#>  9          4.4         2.9          1.4         0.2  setosa
 #> 10          4.9         3.1          1.5         0.1  setosa
 #> # ... with 140 more rows
 ```
 
-This will work for reasonable inputs that are already data.frame, list, matrix, or table.
+This will work for reasonable inputs that are already data.frames, lists, matrices, or tables.
 
-You can also create a new tibble from vectors that represent the columns with `tibble()`:
+You can also create a new tibble from column vectors with `tibble()`:
 
 ``` r
 tibble(x = 1:5, y = 1, z = x ^ 2 + y)
-#> # A tibble: 5 × 3
+#> # A tibble: 5 x 3
 #>       x     y     z
 #>   <int> <dbl> <dbl>
 #> 1     1     1     2
@@ -47,7 +67,7 @@ tibble(x = 1:5, y = 1, z = x ^ 2 + y)
 #> 5     5     1    26
 ```
 
-`tibble()` does much less than `data.frame()`: it never changes the type of the inputs (e.g. it never converts strings to factors!), it never changes the names of variables, and it never creates `row.names()`. You can read more about these features in the vignette, `vignette("tibble")`.
+`tibble()` does much less than `data.frame()`: it never changes the type of the inputs (e.g. it never converts strings to factors!), it never changes the names of variables, it only recycles inputs of length 1, and it never creates `row.names()`. You can read more about these features in the vignette, `vignette("tibble")`.
 
 You can define a tibble row-by-row with `tribble()`:
 
@@ -57,80 +77,9 @@ tribble(
   "a", 2,  3.6,
   "b", 1,  8.5
 )
-#> # A tibble: 2 × 3
+#> # A tibble: 2 x 3
 #>       x     y     z
 #>   <chr> <dbl> <dbl>
 #> 1     a     2   3.6
 #> 2     b     1   8.5
 ```
-
-You can see why this variant of the data.frame is called a "tibble diff" from its class:
-
-``` r
-class(as_tibble(iris))
-#> [1] "tbl_df"     "tbl"        "data.frame"
-```
-
-Tibbles vs data frames
-----------------------
-
-There are two main differences in the usage of a data frame vs a tibble: printing, and subsetting.
-
-Tibbles have a refined print method that shows only the first 10 rows, and all the columns that fit on screen. This makes it much easier to work with large data. In addition to its name, each column reports its type, a nice feature borrowed from `str()`:
-
-``` r
-library(nycflights13)
-flights
-#> # A tibble: 336,776 × 19
-#>     year month   day dep_time sched_dep_time dep_delay arr_time
-#>    <int> <int> <int>    <int>          <int>     <dbl>    <int>
-#> 1   2013     1     1      517            515         2      830
-#> 2   2013     1     1      533            529         4      850
-#> 3   2013     1     1      542            540         2      923
-#> 4   2013     1     1      544            545        -1     1004
-#> 5   2013     1     1      554            600        -6      812
-#> 6   2013     1     1      554            558        -4      740
-#> 7   2013     1     1      555            600        -5      913
-#> 8   2013     1     1      557            600        -3      709
-#> 9   2013     1     1      557            600        -3      838
-#> 10  2013     1     1      558            600        -2      753
-#> # ... with 336,766 more rows, and 12 more variables: sched_arr_time <int>,
-#> #   arr_delay <dbl>, carrier <chr>, flight <int>, tailnum <chr>,
-#> #   origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>, hour <dbl>,
-#> #   minute <dbl>, time_hour <dttm>
-```
-
-Tibbles are strict about subsetting. If you try to access a variable that does not exist via `$`, you'll get a warning:
-
-``` r
-flights$yea
-#> Warning: Unknown column 'yea'
-#> NULL
-```
-
-Tibbles also clearly delineate `[` and `[[`: `[` always returns another tibble, `[[` always returns a vector. No more `drop = FALSE`!
-
-``` r
-class(iris[ , 1])
-#> [1] "numeric"
-class(iris[ , 1, drop = FALSE])
-#> [1] "data.frame"
-class(as_tibble(iris)[ , 1])
-#> [1] "tbl_df"     "tbl"        "data.frame"
-```
-
-Installation
-------------
-
-tibble is on CRAN, install using:
-
-``` r
-install.packages("tibble")
-```
-
-You can try out the dev version with:
-
-``` r
-# install.packages("devtools")
-devtools::install_github("hadley/tibble")
-```
diff --git a/build/vignette.rds b/build/vignette.rds
index 6306fda..243b773 100644
Binary files a/build/vignette.rds and b/build/vignette.rds differ
diff --git a/inst/doc/formatting.R b/inst/doc/extending.R
similarity index 100%
rename from inst/doc/formatting.R
rename to inst/doc/extending.R
diff --git a/inst/doc/formatting.Rmd b/inst/doc/extending.Rmd
similarity index 91%
rename from inst/doc/formatting.Rmd
rename to inst/doc/extending.Rmd
index d74bc9c..ff89d7c 100644
--- a/inst/doc/formatting.Rmd
+++ b/inst/doc/extending.Rmd
@@ -1,10 +1,8 @@
 ---
-title: "Formatting of column data"
-author: "Kirill Müller, Hadley Wickham"
-date: "`r Sys.Date()`"
+title: "Extending tibble"
 output: rmarkdown::html_vignette
 vignette: >
-  %\VignetteIndexEntry{Formatting of column data}
+  %\VignetteIndexEntry{Extending tibble}
   %\VignetteEngine{knitr::rmarkdown}
   %\VignetteEncoding{UTF-8}
 ---
@@ -15,7 +13,7 @@ options(tibble.print_min = 4L, tibble.print_max = 4L)
 library(tibble)
 ```
 
-The presentation of a column in a tibble is powered by two S3 generics:
+To extend the tibble package for new types of columnar data, you need to understand how printing works. The presentation of a column in a tibble is powered by two S3 generics:
 
 * `type_sum()` determines what goes into the column header.
 * `obj_sum()` is used when rendering list columns.
diff --git a/inst/doc/formatting.html b/inst/doc/extending.html
similarity index 85%
rename from inst/doc/formatting.html
rename to inst/doc/extending.html
index 6938143..b883a69 100644
--- a/inst/doc/formatting.html
+++ b/inst/doc/extending.html
@@ -4,17 +4,15 @@
 
 <head>
 
-<meta charset="utf-8">
+<meta charset="utf-8" />
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <meta name="generator" content="pandoc" />
 
 <meta name="viewport" content="width=device-width, initial-scale=1">
 
-<meta name="author" content="Kirill Müller, Hadley Wickham" />
 
-<meta name="date" content="2016-08-26" />
 
-<title>Formatting of column data</title>
+<title>Extending tibble</title>
 
 
 
@@ -68,22 +66,20 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
 
 
 
-<h1 class="title toc-ignore">Formatting of column data</h1>
-<h4 class="author"><em>Kirill Müller, Hadley Wickham</em></h4>
-<h4 class="date"><em>2016-08-26</em></h4>
+<h1 class="title toc-ignore">Extending tibble</h1>
 
 
 
-<p>The presentation of a column in a tibble is powered by two S3 generics:</p>
+<p>To extend the tibble package for new types of columnar data, you need to understand how printing works. The presentation of a column in a tibble is powered by two S3 generics:</p>
 <ul>
 <li><code>type_sum()</code> determines what goes into the column header.</li>
 <li><code>obj_sum()</code> is used when rendering list columns.</li>
 </ul>
 <p>If you have written an S3 or S4 class that can be used as a column, you can override these generics to make sure your data prints well in a tibble. To start, you must import the <code>tibble</code> package. Either add <code>tibble</code> to the <code>Imports:</code> section of your <code>DESCRIPTION</code>, or simply call:</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">devtools::<span class="kw">use_package</span>(<span class="st">"tibble"</span>)</code></pre></div>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">devtools<span class="op">::</span><span class="kw">use_package</span>(<span class="st">"tibble"</span>)</code></pre></div>
 <p>This vignette assumes a package that implements an S3 class <code>"foo"</code> and uses <code>roxygen2</code> to create documentation and the <code>NAMESPACE</code> file:</p>
 <div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="co">#' @export</span>
-foo <-<span class="st"> </span>function(x) {
+foo <-<span class="st"> </span><span class="cf">function</span>(x) {
   <span class="kw">stopifnot</span>(<span class="kw">is.numeric</span>(x))
   <span class="kw">structure</span>(x, <span class="dt">class =</span> <span class="st">"foo"</span>)
 }</code></pre></div>
@@ -92,20 +88,20 @@ foo <-<span class="st"> </span>function(x) {
 <p>This method should return a length-1 character vector that can be used in a column header. Strive for an evocative abbreviation that’s under 6 characters.</p>
 <div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">type_sum</span>(<span class="dv">1</span>)
 <span class="co">#> [1] "dbl"</span>
-<span class="kw">type_sum</span>(<span class="dv">1</span>:<span class="dv">10</span>)
+<span class="kw">type_sum</span>(<span class="dv">1</span><span class="op">:</span><span class="dv">10</span>)
 <span class="co">#> [1] "int"</span>
 <span class="kw">type_sum</span>(<span class="kw">Sys.time</span>())
 <span class="co">#> [1] "dttm"</span></code></pre></div>
 <p>The default implementation works reasonably well for any kind of object, but the generated output may be too wide and waste precious space when displaying the tibble:</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">type_sum</span>(<span class="kw">foo</span>(<span class="dv">1</span>:<span class="dv">10</span>))
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">type_sum</span>(<span class="kw">foo</span>(<span class="dv">1</span><span class="op">:</span><span class="dv">10</span>))
 <span class="co">#> [1] "S3: foo"</span></code></pre></div>
 <p>To avoid this for provide a method for <code>type_sum()</code>:</p>
 <div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="co">#' @export</span>
-type_sum.foo <-<span class="st"> </span>function(x, ...) {
+type_sum.foo <-<span class="st"> </span><span class="cf">function</span>(x, ...) {
   <span class="st">"foo"</span>
 }
 
-<span class="kw">type_sum</span>(<span class="kw">foo</span>(<span class="dv">1</span>:<span class="dv">10</span>))
+<span class="kw">type_sum</span>(<span class="kw">foo</span>(<span class="dv">1</span><span class="op">:</span><span class="dv">10</span>))
 <span class="co">#> [1] "foo"</span></code></pre></div>
 </div>
 <div id="obj_sum" class="section level2">
@@ -114,42 +110,42 @@ type_sum.foo <-<span class="st"> </span>function(x, ...) {
 <p>Examples:</p>
 <div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">obj_sum</span>(<span class="dv">1</span>)
 <span class="co">#> [1] "dbl [1]"</span>
-<span class="kw">obj_sum</span>(<span class="dv">1</span>:<span class="dv">10</span>)
+<span class="kw">obj_sum</span>(<span class="dv">1</span><span class="op">:</span><span class="dv">10</span>)
 <span class="co">#> [1] "int [10]"</span>
 <span class="kw">obj_sum</span>(<span class="kw">Sys.time</span>())
 <span class="co">#> [1] "dttm [1]"</span>
-<span class="kw">obj_sum</span>(<span class="kw">list</span>(<span class="dv">1</span>:<span class="dv">5</span>))
+<span class="kw">obj_sum</span>(<span class="kw">list</span>(<span class="dv">1</span><span class="op">:</span><span class="dv">5</span>))
 <span class="co">#> [1] "int [5]"</span>
 <span class="kw">obj_sum</span>(<span class="kw">list</span>(<span class="st">"a"</span>, <span class="st">"b"</span>, <span class="st">"c"</span>))
 <span class="co">#> [1] "chr [1]" "chr [1]" "chr [1]"</span></code></pre></div>
 <p>The default implementation calls <code>type_sum()</code> and appends the size of the object in brackets. If your object is built on top of an atomic vector the default will be adequate. You, will, however, need to provide a method if your object is vectorised and built on top of a list.</p>
 <p>An example of an object of this type in base R <code>POSIXlt</code>: it is a list with 9 components.</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">x <-<span class="st"> </span><span class="kw">as.POSIXlt</span>(<span class="kw">Sys.time</span>() +<span class="st"> </span><span class="kw">c</span>(<span class="dv">0</span>, <span class="dv">60</span>, <span class="dv">3600</span>)) 
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">x <-<span class="st"> </span><span class="kw">as.POSIXlt</span>(<span class="kw">Sys.time</span>() <span class="op">+</span><span class="st"> </span><span class="kw">c</span>(<span class="dv">0</span>, <span class="dv">60</span>, <span class="dv">3600</span>)) 
 <span class="kw">str</span>(<span class="kw">unclass</span>(x))
 <span class="co">#> List of 11</span>
-<span class="co">#>  $ sec   : num [1:3] 39.8 39.8 39.8</span>
-<span class="co">#>  $ min   : int [1:3] 42 43 42</span>
-<span class="co">#>  $ hour  : int [1:3] 13 13 14</span>
-<span class="co">#>  $ mday  : int [1:3] 26 26 26</span>
+<span class="co">#>  $ sec   : num [1:3] 35.5 35.5 35.5</span>
+<span class="co">#>  $ min   : int [1:3] 4 5 4</span>
+<span class="co">#>  $ hour  : int [1:3] 18 18 19</span>
+<span class="co">#>  $ mday  : int [1:3] 21 21 21</span>
 <span class="co">#>  $ mon   : int [1:3] 7 7 7</span>
-<span class="co">#>  $ year  : int [1:3] 116 116 116</span>
-<span class="co">#>  $ wday  : int [1:3] 5 5 5</span>
-<span class="co">#>  $ yday  : int [1:3] 238 238 238</span>
+<span class="co">#>  $ year  : int [1:3] 117 117 117</span>
+<span class="co">#>  $ wday  : int [1:3] 1 1 1</span>
+<span class="co">#>  $ yday  : int [1:3] 232 232 232</span>
 <span class="co">#>  $ isdst : int [1:3] 1 1 1</span>
 <span class="co">#>  $ zone  : chr [1:3] "CEST" "CEST" "CEST"</span>
 <span class="co">#>  $ gmtoff: int [1:3] 7200 7200 7200</span>
 <span class="co">#>  - attr(*, "tzone")= chr [1:3] "" "CET" "CEST"</span></code></pre></div>
 <p>But it pretends to be a vector with 3 elements:</p>
 <div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">x
-<span class="co">#> [1] "2016-08-26 13:42:39 CEST" "2016-08-26 13:43:39 CEST"</span>
-<span class="co">#> [3] "2016-08-26 14:42:39 CEST"</span>
+<span class="co">#> [1] "2017-08-21 18:04:35 CEST" "2017-08-21 18:05:35 CEST"</span>
+<span class="co">#> [3] "2017-08-21 19:04:35 CEST"</span>
 <span class="kw">length</span>(x)
 <span class="co">#> [1] 3</span>
 <span class="kw">str</span>(x)
-<span class="co">#>  POSIXlt[1:3], format: "2016-08-26 13:42:39" "2016-08-26 13:43:39" ...</span></code></pre></div>
+<span class="co">#>  POSIXlt[1:3], format: "2017-08-21 18:04:35" "2017-08-21 18:05:35" ...</span></code></pre></div>
 <p>So we need to define a method that returns a character vector the same length as <code>x</code>:</p>
 <div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="co">#' @export</span>
-obj_sum.POSIXlt <-<span class="st"> </span>function(x) {
+obj_sum.POSIXlt <-<span class="st"> </span><span class="cf">function</span>(x) {
   <span class="kw">rep</span>(<span class="st">"POSIXlt"</span>, <span class="kw">length</span>(x))
 }</code></pre></div>
 </div>
@@ -161,7 +157,7 @@ obj_sum.POSIXlt <-<span class="st"> </span>function(x) {
   (function () {
     var script = document.createElement("script");
     script.type = "text/javascript";
-    script.src  = "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
+    script.src  = "https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
     document.getElementsByTagName("head")[0].appendChild(script);
   })();
 </script>
diff --git a/inst/doc/tibble.R b/inst/doc/tibble.R
index a6b3fee..5fa0f98 100644
--- a/inst/doc/tibble.R
+++ b/inst/doc/tibble.R
@@ -2,6 +2,7 @@
 knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
 options(tibble.print_min = 4L, tibble.print_max = 4L)
 library(tibble)
+set.seed(1014)
 
 ## ------------------------------------------------------------------------
 tibble(x = letters)
@@ -17,13 +18,15 @@ names(tibble(`crazy name` = 1))
 tibble(x = 1:5, y = x ^ 2)
 
 ## ------------------------------------------------------------------------
-l <- replicate(26, sample(100), simplify = FALSE)
-names(l) <- letters
+if (requireNamespace("microbenchmark", quiet = TRUE)) {
+  l <- replicate(26, sample(100), simplify = FALSE)
+  names(l) <- letters
 
-microbenchmark::microbenchmark(
-  as_tibble(l),
-  as.data.frame(l)
-)
+  microbenchmark::microbenchmark(
+    as_tibble(l),
+    as.data.frame(l)
+  )
+}
 
 ## ------------------------------------------------------------------------
 tibble(x = 1:1000)
@@ -48,3 +51,13 @@ df$a
 df2 <- tibble(abc = 1)
 df2$a
 
+## ------------------------------------------------------------------------
+tibble(a = 1:3)[, "a", drop = TRUE]
+
+## ---- error = TRUE-------------------------------------------------------
+tibble(a = 1, b = 1:3)
+tibble(a = 1:3, b = 1)
+tibble(a = 1:3, c = 1:2)
+tibble(a = 1, b = integer())
+tibble(a = integer(), b = 1)
+
diff --git a/inst/doc/tibble.Rmd b/inst/doc/tibble.Rmd
index cfadfc5..14f890d 100644
--- a/inst/doc/tibble.Rmd
+++ b/inst/doc/tibble.Rmd
@@ -1,6 +1,5 @@
 ---
 title: "Tibbles"
-date: "`r Sys.Date()`"
 output: rmarkdown::html_vignette
 vignette: >
   %\VignetteIndexEntry{Tibbles}
@@ -12,6 +11,7 @@ vignette: >
 knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
 options(tibble.print_min = 4L, tibble.print_max = 4L)
 library(tibble)
+set.seed(1014)
 ```
 
 Tibbles are a modern take on data frames. They keep the features that have stood the test of time, and drop the features that used to be convenient but are now frustrating (i.e. converting character vectors to factors).
@@ -62,20 +62,22 @@ To complement `tibble()`, tibble provides `as_tibble()` to coerce objects into t
 `as_tibble()` has been written with an eye for performance:
 
 ```{r}
-l <- replicate(26, sample(100), simplify = FALSE)
-names(l) <- letters
-
-microbenchmark::microbenchmark(
-  as_tibble(l),
-  as.data.frame(l)
-)
+if (requireNamespace("microbenchmark", quiet = TRUE)) {
+  l <- replicate(26, sample(100), simplify = FALSE)
+  names(l) <- letters
+
+  microbenchmark::microbenchmark(
+    as_tibble(l),
+    as.data.frame(l)
+  )
+}
 ```
 
 The speed of `as.data.frame()` is not usually a bottleneck when used interactively, but can be a problem when combining thousands of messy inputs into one tidy data frame.
 
 ## Tibbles vs data frames
 
-There are two key differences between tibbles and data frames: printing and subsetting.
+There are three key differences between tibbles and data frames: printing, subsetting, and recycling rules.
 
 ### Printing
 
@@ -125,4 +127,21 @@ df2 <- tibble(abc = 1)
 df2$a
 ```
 
-Also, tibbles ignore the `drop` argument.
+tibbles also ignore the `drop` argument:
+
+```{r}
+tibble(a = 1:3)[, "a", drop = TRUE]
+```
+
+
+### Recycling
+
+When constructing a tibble, only values of length 1 are recycled.  The first column with length different to one determines the number of rows in the tibble, conflicts lead to an error.  This also extends to tibbles with *zero* rows, which is sometimes important for programming:
+
+```{r, error = TRUE}
+tibble(a = 1, b = 1:3)
+tibble(a = 1:3, b = 1)
+tibble(a = 1:3, c = 1:2)
+tibble(a = 1, b = integer())
+tibble(a = integer(), b = 1)
+```
diff --git a/inst/doc/tibble.html b/inst/doc/tibble.html
index 32e3cf5..d0e9389 100644
--- a/inst/doc/tibble.html
+++ b/inst/doc/tibble.html
@@ -4,14 +4,13 @@
 
 <head>
 
-<meta charset="utf-8">
+<meta charset="utf-8" />
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <meta name="generator" content="pandoc" />
 
 <meta name="viewport" content="width=device-width, initial-scale=1">
 
 
-<meta name="date" content="2016-08-26" />
 
 <title>Tibbles</title>
 
@@ -68,7 +67,6 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
 
 
 <h1 class="title toc-ignore">Tibbles</h1>
-<h4 class="date"><em>2016-08-26</em></h4>
 
 
 
@@ -79,7 +77,7 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
 <ul>
 <li><p>It never changes an input’s type (i.e., no more <code>stringsAsFactors = FALSE</code>!).</p>
 <div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">tibble</span>(<span class="dt">x =</span> letters)
-<span class="co">#> # A tibble: 26 × 1</span>
+<span class="co">#> # A tibble: 26 x 1</span>
 <span class="co">#>       x</span>
 <span class="co">#>   <chr></span>
 <span class="co">#> 1     a</span>
@@ -88,8 +86,8 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
 <span class="co">#> 4     d</span>
 <span class="co">#> # ... with 22 more rows</span></code></pre></div>
 <p>This makes it easier to use with list-columns:</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">tibble</span>(<span class="dt">x =</span> <span class="dv">1</span>:<span class="dv">3</span>, <span class="dt">y =</span> <span class="kw">list</span>(<span class="dv">1</span>:<span class="dv">5</span>, <span class="dv">1</span>:<span class="dv">10</span>, <span class="dv">1</span>:<span class="dv">20</span>))
-<span class="co">#> # A tibble: 3 × 2</span>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">tibble</span>(<span class="dt">x =</span> <span class="dv">1</span><span class="op">:</span><span class="dv">3</span>, <span class="dt">y =</span> <span class="kw">list</span>(<span class="dv">1</span><span class="op">:</span><span class="dv">5</span>, <span class="dv">1</span><span class="op">:</span><span class="dv">10</span>, <span class="dv">1</span><span class="op">:</span><span class="dv"> [...]
+<span class="co">#> # A tibble: 3 x 2</span>
 <span class="co">#>       x          y</span>
 <span class="co">#>   <int>     <list></span>
 <span class="co">#> 1     1  <int [5]></span>
@@ -102,8 +100,8 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
 <span class="kw">names</span>(<span class="kw">tibble</span>(<span class="st">`</span><span class="dt">crazy name</span><span class="st">`</span> =<span class="st"> </span><span class="dv">1</span>))
 <span class="co">#> [1] "crazy name"</span></code></pre></div></li>
 <li><p>It evaluates its arguments lazily and sequentially:</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">tibble</span>(<span class="dt">x =</span> <span class="dv">1</span>:<span class="dv">5</span>, <span class="dt">y =</span> x ^<span class="st"> </span><span class="dv">2</span>)
-<span class="co">#> # A tibble: 5 × 2</span>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">tibble</span>(<span class="dt">x =</span> <span class="dv">1</span><span class="op">:</span><span class="dv">5</span>, <span class="dt">y =</span> x <span class="op">^</span><span class="st"> </span><span class="dv">2</span>)
+<span class="co">#> # A tibble: 5 x 2</span>
 <span class="co">#>       x     y</span>
 <span class="co">#>   <int> <dbl></span>
 <span class="co">#> 1     1     1</span>
@@ -119,30 +117,26 @@ code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Inf
 <h2>Coercion</h2>
 <p>To complement <code>tibble()</code>, tibble provides <code>as_tibble()</code> to coerce objects into tibbles. Generally, <code>as_tibble()</code> methods are much simpler than <code>as.data.frame()</code> methods, and in fact, it’s precisely what <code>as.data.frame()</code> does, but it’s similar to <code>do.call(cbind, lapply(x, data.frame))</code> - i.e. it coerces each component to a data frame and then <code>cbinds()</code> them all together.</p>
 <p><code>as_tibble()</code> has been written with an eye for performance:</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">l <-<span class="st"> </span><span class="kw">replicate</span>(<span class="dv">26</span>, <span class="kw">sample</span>(<span class="dv">100</span>), <span class="dt">simplify =</span> <span class="ot">FALSE</span>)
-<span class="kw">names</span>(l) <-<span class="st"> </span>letters
-
-microbenchmark::<span class="kw">microbenchmark</span>(
-  <span class="kw">as_tibble</span>(l),
-  <span class="kw">as.data.frame</span>(l)
-)
-<span class="co">#> Unit: microseconds</span>
-<span class="co">#>              expr      min        lq      mean   median        uq      max</span>
-<span class="co">#>      as_tibble(l)  520.511  555.2405  596.2015  580.798  612.1915 1116.805</span>
-<span class="co">#>  as.data.frame(l) 2932.765 3060.7465 3429.1367 3113.495 3253.2700 8814.272</span>
-<span class="co">#>  neval cld</span>
-<span class="co">#>    100  a </span>
-<span class="co">#>    100   b</span></code></pre></div>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="cf">if</span> (<span class="kw">requireNamespace</span>(<span class="st">"microbenchmark"</span>, <span class="dt">quiet =</span> <span class="ot">TRUE</span>)) {
+  l <-<span class="st"> </span><span class="kw">replicate</span>(<span class="dv">26</span>, <span class="kw">sample</span>(<span class="dv">100</span>), <span class="dt">simplify =</span> <span class="ot">FALSE</span>)
+  <span class="kw">names</span>(l) <-<span class="st"> </span>letters
+
+  microbenchmark<span class="op">::</span><span class="kw">microbenchmark</span>(
+    <span class="kw">as_tibble</span>(l),
+    <span class="kw">as.data.frame</span>(l)
+  )
+}
+<span class="co">#> Loading required namespace: microbenchmark</span></code></pre></div>
 <p>The speed of <code>as.data.frame()</code> is not usually a bottleneck when used interactively, but can be a problem when combining thousands of messy inputs into one tidy data frame.</p>
 </div>
 <div id="tibbles-vs-data-frames" class="section level2">
 <h2>Tibbles vs data frames</h2>
-<p>There are two key differences between tibbles and data frames: printing and subsetting.</p>
+<p>There are three key differences between tibbles and data frames: printing, subsetting, and recycling rules.</p>
 <div id="printing" class="section level3">
 <h3>Printing</h3>
 <p>When you print a tibble, it only shows the first ten rows and all the columns that fit on one screen. It also prints an abbreviated description of the column type:</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">tibble</span>(<span class="dt">x =</span> <span class="dv">1</span>:<span class="dv">1000</span>)
-<span class="co">#> # A tibble: 1,000 × 1</span>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">tibble</span>(<span class="dt">x =</span> <span class="dv">1</span><span class="op">:</span><span class="dv">1000</span>)
+<span class="co">#> # A tibble: 1,000 x 1</span>
 <span class="co">#>       x</span>
 <span class="co">#>   <int></span>
 <span class="co">#> 1     1</span>
@@ -159,32 +153,66 @@ microbenchmark::<span class="kw">microbenchmark</span>(
 <div id="subsetting" class="section level3">
 <h3>Subsetting</h3>
 <p>Tibbles are quite strict about subsetting. <code>[</code> always returns another tibble. Contrast this with a data frame: sometimes <code>[</code> returns a data frame and sometimes it just returns a vector:</p>
-<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">df1 <-<span class="st"> </span><span class="kw">data.frame</span>(<span class="dt">x =</span> <span class="dv">1</span>:<span class="dv">3</span>, <span class="dt">y =</span> <span class="dv">3</span>:<span class="dv">1</span>)
-<span class="kw">class</span>(df1[, <span class="dv">1</span>:<span class="dv">2</span>])
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">df1 <-<span class="st"> </span><span class="kw">data.frame</span>(<span class="dt">x =</span> <span class="dv">1</span><span class="op">:</span><span class="dv">3</span>, <span class="dt">y =</span> <span class="dv">3</span><span class="op">:</span><span class="dv">1</span>)
+<span class="kw">class</span>(df1[, <span class="dv">1</span><span class="op">:</span><span class="dv">2</span>])
 <span class="co">#> [1] "data.frame"</span>
 <span class="kw">class</span>(df1[, <span class="dv">1</span>])
 <span class="co">#> [1] "integer"</span>
 
-df2 <-<span class="st"> </span><span class="kw">tibble</span>(<span class="dt">x =</span> <span class="dv">1</span>:<span class="dv">3</span>, <span class="dt">y =</span> <span class="dv">3</span>:<span class="dv">1</span>)
-<span class="kw">class</span>(df2[, <span class="dv">1</span>:<span class="dv">2</span>])
+df2 <-<span class="st"> </span><span class="kw">tibble</span>(<span class="dt">x =</span> <span class="dv">1</span><span class="op">:</span><span class="dv">3</span>, <span class="dt">y =</span> <span class="dv">3</span><span class="op">:</span><span class="dv">1</span>)
+<span class="kw">class</span>(df2[, <span class="dv">1</span><span class="op">:</span><span class="dv">2</span>])
 <span class="co">#> [1] "tbl_df"     "tbl"        "data.frame"</span>
 <span class="kw">class</span>(df2[, <span class="dv">1</span>])
 <span class="co">#> [1] "tbl_df"     "tbl"        "data.frame"</span></code></pre></div>
 <p>To extract a single column use <code>[[</code> or <code>$</code>:</p>
 <div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">class</span>(df2[[<span class="dv">1</span>]])
 <span class="co">#> [1] "integer"</span>
-<span class="kw">class</span>(df2$x)
+<span class="kw">class</span>(df2<span class="op">$</span>x)
 <span class="co">#> [1] "integer"</span></code></pre></div>
 <p>Tibbles are also stricter with <code>$</code>. Tibbles never do partial matching, and will throw a warning and return <code>NULL</code> if the column does not exist:</p>
 <div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r">df <-<span class="st"> </span><span class="kw">data.frame</span>(<span class="dt">abc =</span> <span class="dv">1</span>)
-df$a
+df<span class="op">$</span>a
 <span class="co">#> [1] 1</span>
 
 df2 <-<span class="st"> </span><span class="kw">tibble</span>(<span class="dt">abc =</span> <span class="dv">1</span>)
-df2$a
-<span class="co">#> Warning: Unknown column 'a'</span>
+df2<span class="op">$</span>a
+<span class="co">#> Warning: Unknown or uninitialised column: 'a'.</span>
 <span class="co">#> NULL</span></code></pre></div>
-<p>Also, tibbles ignore the <code>drop</code> argument.</p>
+<p>tibbles also ignore the <code>drop</code> argument:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">tibble</span>(<span class="dt">a =</span> <span class="dv">1</span><span class="op">:</span><span class="dv">3</span>)[, <span class="st">"a"</span>, drop =<span class="st"> </span><span class="ot">TRUE</span>]
+<span class="co">#> Warning: drop ignored</span>
+<span class="co">#> # A tibble: 3 x 1</span>
+<span class="co">#>       a</span>
+<span class="co">#>   <int></span>
+<span class="co">#> 1     1</span>
+<span class="co">#> 2     2</span>
+<span class="co">#> 3     3</span></code></pre></div>
+</div>
+<div id="recycling" class="section level3">
+<h3>Recycling</h3>
+<p>When constructing a tibble, only values of length 1 are recycled. The first column with length different to one determines the number of rows in the tibble, conflicts lead to an error. This also extends to tibbles with <em>zero</em> rows, which is sometimes important for programming:</p>
+<div class="sourceCode"><pre class="sourceCode r"><code class="sourceCode r"><span class="kw">tibble</span>(<span class="dt">a =</span> <span class="dv">1</span>, <span class="dt">b =</span> <span class="dv">1</span><span class="op">:</span><span class="dv">3</span>)
+<span class="co">#> # A tibble: 3 x 2</span>
+<span class="co">#>       a     b</span>
+<span class="co">#>   <dbl> <int></span>
+<span class="co">#> 1     1     1</span>
+<span class="co">#> 2     1     2</span>
+<span class="co">#> 3     1     3</span>
+<span class="kw">tibble</span>(<span class="dt">a =</span> <span class="dv">1</span><span class="op">:</span><span class="dv">3</span>, <span class="dt">b =</span> <span class="dv">1</span>)
+<span class="co">#> # A tibble: 3 x 2</span>
+<span class="co">#>       a     b</span>
+<span class="co">#>   <int> <dbl></span>
+<span class="co">#> 1     1     1</span>
+<span class="co">#> 2     2     1</span>
+<span class="co">#> 3     3     1</span>
+<span class="kw">tibble</span>(<span class="dt">a =</span> <span class="dv">1</span><span class="op">:</span><span class="dv">3</span>, <span class="dt">c =</span> <span class="dv">1</span><span class="op">:</span><span class="dv">2</span>)
+<span class="co">#> Error: Column `c` must be length 1 or 3, not 2</span>
+<span class="kw">tibble</span>(<span class="dt">a =</span> <span class="dv">1</span>, <span class="dt">b =</span> <span class="kw">integer</span>())
+<span class="co">#> # A tibble: 0 x 2</span>
+<span class="co">#> # ... with 2 variables: a <dbl>, b <int></span>
+<span class="kw">tibble</span>(<span class="dt">a =</span> <span class="kw">integer</span>(), <span class="dt">b =</span> <span class="dv">1</span>)
+<span class="co">#> # A tibble: 0 x 2</span>
+<span class="co">#> # ... with 2 variables: a <int>, b <dbl></span></code></pre></div>
 </div>
 </div>
 
@@ -195,7 +223,7 @@ df2$a
   (function () {
     var script = document.createElement("script");
     script.type = "text/javascript";
-    script.src  = "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
+    script.src  = "https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
     document.getElementsByTagName("head")[0].appendChild(script);
   })();
 </script>
diff --git a/man/add_column.Rd b/man/add_column.Rd
index 216e89b..1d7dc08 100644
--- a/man/add_column.Rd
+++ b/man/add_column.Rd
@@ -37,4 +37,3 @@ add_column(df, z = 1:5)
 \seealso{
 Other addition: \code{\link{add_row}}
 }
-
diff --git a/man/add_row.Rd b/man/add_row.Rd
index b86daf7..ccc0185 100644
--- a/man/add_row.Rd
+++ b/man/add_row.Rd
@@ -17,7 +17,7 @@ default: after last row}
 }
 \description{
 This is a convenient way to add one or more rows of data to an existing data
-frame. See \code{\link{tribble}} for an easy way to create an complete
+frame. See \code{\link[=tribble]{tribble()}} for an easy way to create an complete
 data frame row-by-row.
 }
 \examples{
@@ -44,4 +44,3 @@ add_row(df, z = 10)
 \seealso{
 Other addition: \code{\link{add_column}}
 }
-
diff --git a/man/all_equal.Rd b/man/all_equal.Rd
deleted file mode 100644
index ff135a8..0000000
--- a/man/all_equal.Rd
+++ /dev/null
@@ -1,54 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/all-equal.r
-\name{all_equal}
-\alias{all.equal.tbl_df}
-\alias{all_equal}
-\title{Flexible equality comparison for data frames.}
-\usage{
-all_equal(target, current, ignore_col_order = TRUE, ignore_row_order = TRUE,
-  convert = FALSE, ...)
-
-\method{all.equal}{tbl_df}(target, current, ignore_col_order = TRUE,
-  ignore_row_order = TRUE, convert = FALSE, ...)
-}
-\arguments{
-\item{target, current}{Two data frames to compare.}
-
-\item{ignore_col_order}{Should order of columns be ignored?}
-
-\item{ignore_row_order}{Should order of rows be ignored?}
-
-\item{convert}{Should similar classes be converted? Currently this will
-convert factor to character and integer to double.}
-
-\item{...}{Ignored. Needed for compatibility with \code{all.equal}.}
-}
-\value{
-\code{TRUE} if equal, otherwise a character vector describing
-  the reasons why they're not equal. Use \code{\link{isTRUE}} if using the
-  result in an \code{if} expression.
-}
-\description{
-When comparing two \code{tbl_df} using \code{\link{all.equal}}, column and
-row order is ignored by default, and types are not coerced.  The \code{dplyr}
-package provides a much more efficient implementation for this functionality.
-}
-\examples{
-scramble <- function(x) x[sample(nrow(x)), sample(ncol(x))]
-mtcars_df <- as_tibble(mtcars)
-
-# By default, ordering of rows and columns ignored
-all.equal(mtcars_df, scramble(mtcars_df))
-
-# But those can be overriden if desired
-all.equal(mtcars_df, scramble(mtcars_df), ignore_col_order = FALSE)
-all.equal(mtcars_df, scramble(mtcars_df), ignore_row_order = FALSE)
-
-# By default all.equal is sensitive to variable differences
-df1 <- tibble(x = "a")
-df2 <- tibble(x = factor("a"))
-all.equal(df1, df2)
-# But you can request to convert similar types
-all.equal(df1, df2, convert = TRUE)
-}
-
diff --git a/man/as_tibble.Rd b/man/as_tibble.Rd
index 00ea35a..3f65bad 100644
--- a/man/as_tibble.Rd
+++ b/man/as_tibble.Rd
@@ -1,20 +1,21 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/dataframe.R
+% Please edit documentation in R/as_tibble.R
 \name{as_tibble}
-\alias{as_data_frame}
 \alias{as_tibble}
-\alias{as_tibble.NULL}
+\alias{as_tibble.tbl_df}
 \alias{as_tibble.data.frame}
-\alias{as_tibble.default}
 \alias{as_tibble.list}
 \alias{as_tibble.matrix}
 \alias{as_tibble.table}
-\alias{as_tibble.tbl_df}
+\alias{as_tibble.NULL}
+\alias{as_tibble.default}
+\alias{as.tibble}
+\alias{as_data_frame}
 \title{Coerce lists and matrices to data frames.}
 \usage{
 as_tibble(x, ...)
 
-\method{as_tibble}{tbl_df}(x, ...)
+\method{as_tibble}{tbl_df}(x, ..., validate = FALSE)
 
 \method{as_tibble}{data.frame}(x, validate = TRUE, ...)
 
@@ -27,8 +28,6 @@ as_tibble(x, ...)
 \method{as_tibble}{NULL}(x, ...)
 
 \method{as_tibble}{default}(x, ...)
-
-as_data_frame(x, ...)
 }
 \arguments{
 \item{x}{A list. Each element of the list must have the same length.}
@@ -38,23 +37,24 @@ as_data_frame(x, ...)
 \item{validate}{When \code{TRUE}, verifies that the input is a valid data
 frame (i.e. all columns are named, and are 1d vectors or lists). You may
 want to suppress this when you know that you already have a valid data
-frame and you want to save some time.}
+frame and you want to save some time, or to explicitly enable it
+if you have a tibble that you want to re-check.}
 
 \item{n}{Name for count column, default: \code{"n"}.}
 }
 \description{
-\code{as.data.frame} is effectively a thin wrapper around \code{data.frame},
-and hence is rather slow (because it calls \code{data.frame} on each element
-before \code{cbind}ing together). \code{as_tibble} is a new S3 generic
+\code{\link[=as.data.frame]{as.data.frame()}} is effectively a thin wrapper around \code{data.frame},
+and hence is rather slow (because it calls \code{\link[=data.frame]{data.frame()}} on each element
+before \link{cbind}ing together). \code{as_tibble} is a new S3 generic
 with more efficient methods for matrices and data frames.
 }
 \details{
 This is an S3 generic. tibble includes methods for data frames (adds tbl_df
 classes), tibbles (returns unchanged input), lists, matrices, and tables.
-Other types are first coerced via \code{\link[base]{as.data.frame}} with
+Other types are first coerced via \code{as.data.frame()} with
 \code{stringsAsFactors = FALSE}.
 
-\code{as_data_frame} is an alias.
+\code{as_data_frame} and \code{as.tibble} are aliases.
 }
 \examples{
 l <- list(x = 1:500, y = runif(500), z = 500:1)
@@ -68,20 +68,23 @@ df <- as_tibble(m)
 # making it more suitable for use when you have things that are
 # lists
 \dontrun{
-l2 <- replicate(26, sample(letters), simplify = FALSE)
-names(l2) <- letters
-microbenchmark::microbenchmark(
-  as_tibble(l2, validate = FALSE),
-  as_tibble(l2),
-  as.data.frame(l2)
-)
+if (requireNamespace("microbenchmark", quiet = TRUE)) {
+  l2 <- replicate(26, sample(letters), simplify = FALSE)
+  names(l2) <- letters
+  microbenchmark::microbenchmark(
+    as_tibble(l2, validate = FALSE),
+    as_tibble(l2),
+    as.data.frame(l2)
+  )
+}
 
-m <- matrix(runif(26 * 100), ncol = 26)
-colnames(m) <- letters
-microbenchmark::microbenchmark(
-  as_tibble(m),
-  as.data.frame(m)
-)
+if (requireNamespace("microbenchmark", quiet = TRUE)) {
+  m <- matrix(runif(26 * 100), ncol = 26)
+  colnames(m) <- letters
+  microbenchmark::microbenchmark(
+    as_tibble(m),
+    as.data.frame(m)
+  )
+}
 }
 }
-
diff --git a/man/enframe.Rd b/man/enframe.Rd
index c127a39..e9ad6c0 100644
--- a/man/enframe.Rd
+++ b/man/enframe.Rd
@@ -2,25 +2,30 @@
 % Please edit documentation in R/enframe.R
 \name{enframe}
 \alias{enframe}
-\title{Converting atomic vectors to data frames}
+\alias{deframe}
+\title{Converting atomic vectors to data frames, and vice versa}
 \usage{
 enframe(x, name = "name", value = "value")
+
+deframe(x)
 }
 \arguments{
-\item{x}{An atomic vector}
+\item{x}{An atomic vector (for \code{enframe()}) or a data frame (for \code{deframe()})}
 
 \item{name, value}{Names of the columns that store the names and values}
 }
 \value{
-A \code{\link{tibble}}
+A \link{tibble}
 }
 \description{
-A helper function that converts named atomic vectors or lists to two-column
+\code{enframe()} converts named atomic vectors or lists to two-column
 data frames.
 For unnamed vectors, the natural sequence is used as name column.
+
+\code{deframe()} converts two-column data frames to a named vector or list,
+using the first column as name and the second column as value.
 }
 \examples{
 enframe(1:3)
 enframe(c(a = 5, b = 7))
 }
-
diff --git a/man/figures/logo.png b/man/figures/logo.png
new file mode 100644
index 0000000..a31e3d3
Binary files /dev/null and b/man/figures/logo.png differ
diff --git a/man/formatting.Rd b/man/formatting.Rd
index 54317a3..de923e2 100644
--- a/man/formatting.Rd
+++ b/man/formatting.Rd
@@ -1,11 +1,20 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/tbl-df.r, R/utils-format.r
-\name{print.tbl_df}
-\alias{formatting}
+\name{format.tbl}
+\alias{format.tbl}
+\alias{format.tbl_df}
+\alias{print.tbl}
 \alias{print.tbl_df}
+\alias{formatting}
 \alias{trunc_mat}
 \title{Tools for describing matrices}
 \usage{
+\method{format}{tbl}(x, ..., n = NULL, width = NULL, n_extra = NULL)
+
+\method{format}{tbl_df}(x, ..., n = NULL, width = NULL, n_extra = NULL)
+
+\method{print}{tbl}(x, ..., n = NULL, width = NULL, n_extra = NULL)
+
 \method{print}{tbl_df}(x, ..., n = NULL, width = NULL, n_extra = NULL)
 
 trunc_mat(x, n = NULL, width = NULL, n_extra = NULL)
@@ -49,4 +58,3 @@ print(nycflights13::flights, width = Inf)
 \link{tibble-package}
 }
 \keyword{internal}
-
diff --git a/man/frame_matrix.Rd b/man/frame_matrix.Rd
new file mode 100644
index 0000000..a583f6f
--- /dev/null
+++ b/man/frame_matrix.Rd
@@ -0,0 +1,28 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/tribble.R
+\name{frame_matrix}
+\alias{frame_matrix}
+\title{Row-wise matrix creation}
+\usage{
+frame_matrix(...)
+}
+\arguments{
+\item{...}{Arguments specifying the structure of a \code{frame_matrix}.
+Column names should be formulas, and may only appear before the data.}
+}
+\value{
+A \link{matrix}.
+}
+\description{
+Create matrices laying out the data in rows, similar to
+\code{matrix(..., byrow = TRUE)}, with a nicer-to-read syntax.
+This is useful for small matrices, e.g. covariance matrices, where readability
+is important. The syntax is inspired by \code{\link[=tribble]{tribble()}}.
+}
+\examples{
+frame_matrix(
+  ~col1, ~col2,
+  1,     3,
+  5,     2
+)
+}
diff --git a/man/glimpse.Rd b/man/glimpse.Rd
index cd224e4..b2f010e 100644
--- a/man/glimpse.Rd
+++ b/man/glimpse.Rd
@@ -15,21 +15,22 @@ glimpse(x, width = NULL, ...)
 \item{...}{Other arguments passed onto individual methods.}
 }
 \value{
-x original x is (invisibly) returned, allowing \code{glimpse} to be
+x original x is (invisibly) returned, allowing \code{glimpse()} to be
 used within a data pipe line.
 }
 \description{
 This is like a transposed version of print: columns run down the page,
 and data runs across. This makes it possible to see every column in
-a data frame. It's a little like \code{\link{str}} applied to a data frame
+a data frame. It's a little like \code{\link[=str]{str()}} applied to a data frame
 but it tries to show you as much data as possible. (And it always shows
 the underlying data, even when applied to a remote data source.)
 }
 \section{S3 methods}{
 
 \code{glimpse} is an S3 generic with a customised method for \code{tbl}s and
-\code{data.frames}, and a default method that calls \code{\link{str}}.
+\code{data.frames}, and a default method that calls \code{\link[=str]{str()}}.
 }
+
 \examples{
 glimpse(mtcars)
 
@@ -38,4 +39,3 @@ if (!requireNamespace("nycflights13", quietly = TRUE))
 
 glimpse(nycflights13::flights)
 }
-
diff --git a/man/has_name.Rd b/man/has_name.Rd
index 69112e1..943c28c 100644
--- a/man/has_name.Rd
+++ b/man/has_name.Rd
@@ -2,7 +2,7 @@
 % Please edit documentation in R/has-name.R
 \name{has_name}
 \alias{has_name}
-\title{Convenience function to check presence of a named element}
+\title{Does an object have an element with this name?}
 \usage{
 has_name(x, name)
 }
@@ -19,11 +19,6 @@ This function returns a logical value that indicates if a data frame or
 another named object contains an element with a specific name.
 }
 \details{
-Unnamed objects are treated as if all names are empty strings.
-\code{NA} input gives \code{FALSE} as output.
+Unnamed objects are treated as if all names are empty strings. \code{NA}
+input gives \code{FALSE} as output.
 }
-\examples{
-has_name(iris, "Species")
-has_name(mtcars, "gears")
-}
-
diff --git a/man/is.tibble.Rd b/man/is.tibble.Rd
index 50938b6..efd2dbf 100644
--- a/man/is.tibble.Rd
+++ b/man/is.tibble.Rd
@@ -1,5 +1,5 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/dataframe.R
+% Please edit documentation in R/tibble.R
 \name{is.tibble}
 \alias{is.tibble}
 \alias{is_tibble}
@@ -18,4 +18,3 @@ is_tibble(x)
 \description{
 Test if the object is a tibble.
 }
-
diff --git a/man/knit_print.trunc_mat.Rd b/man/knit_print.trunc_mat.Rd
index c8ee78c..f132b19 100644
--- a/man/knit_print.trunc_mat.Rd
+++ b/man/knit_print.trunc_mat.Rd
@@ -10,4 +10,3 @@ knit_print.trunc_mat(x, options)
 knit_print method for trunc mat
 }
 \keyword{internal}
-
diff --git a/man/obj_sum.Rd b/man/obj_sum.Rd
index 131de59..fcc4ca5 100644
--- a/man/obj_sum.Rd
+++ b/man/obj_sum.Rd
@@ -1,10 +1,10 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/type-sum.r
 \name{obj_sum}
-\alias{is_vector_s3}
 \alias{obj_sum}
-\alias{tbl_sum}
 \alias{type_sum}
+\alias{tbl_sum}
+\alias{is_vector_s3}
 \title{Provide a succinct summary of an object}
 \usage{
 obj_sum(x)
@@ -20,15 +20,15 @@ is_vector_s3(x)
 and variants have been implemented.}
 }
 \description{
-\code{type_sum} gives a brief summary of object type. Objects that commonly
-occur in a data frame should return a string with four or less characters.
-
-\code{obj_sum} also includes the size of the object if \code{is_s3_vector}
+\code{obj_sum()} also includes the size of the object if \code{is_vector_s3()}
 is \code{TRUE}.
 
-\code{tbl_sum} gives a brief textual description of a table-like object,
+\code{type_sum()} gives a brief summary of object type. Objects that commonly
+occur in a data frame should return a string with four or less characters.
+
+\code{tbl_sum()} gives a brief textual description of a table-like object,
 which should include the dimensions, the data source, and possible grouping
-(for dplyr).  The default implementation forwards to \code{obj_sum}
+(for \code{dplyr}).  The default implementation forwards to \code{obj_sum()}.
 }
 \examples{
 obj_sum(1:10)
@@ -38,4 +38,3 @@ obj_sum(Sys.time())
 obj_sum(mean)
 }
 \keyword{internal}
-
diff --git a/man/repair_names.Rd b/man/repair_names.Rd
deleted file mode 100644
index 6511168..0000000
--- a/man/repair_names.Rd
+++ /dev/null
@@ -1,32 +0,0 @@
-% Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/repair-names.R
-\name{repair_names}
-\alias{repair_names}
-\title{Repair object names.}
-\usage{
-repair_names(x, prefix = "V", sep = "")
-}
-\arguments{
-\item{x}{A named vector.}
-
-\item{prefix}{A string, the prefix to use for new column names.}
-
-\item{sep}{A string inserted between the column name and de-duplicating
-number.}
-}
-\value{
-\code{x} with valid names.
-}
-\description{
-\code{repair_names} ensures its input has non-missing and
-unique names (duplicated names get a numeric suffix). Valid names are
-left as is.
-}
-\examples{
-repair_names(list(3, 4, 5)) # works for lists, too
-repair_names(mtcars) # a no-op
-tbl <- as_tibble(structure(list(3, 4, 5), class = "data.frame"),
-                 validate = FALSE)
-repair_names(tbl)
-}
-
diff --git a/man/rownames.Rd b/man/rownames.Rd
index c1a6804..cf948d6 100644
--- a/man/rownames.Rd
+++ b/man/rownames.Rd
@@ -1,11 +1,12 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/rownames.R
 \name{rownames}
-\alias{column_to_rownames}
+\alias{rownames}
 \alias{has_rownames}
 \alias{remove_rownames}
-\alias{rownames}
 \alias{rownames_to_column}
+\alias{rowid_to_column}
+\alias{column_to_rownames}
 \title{Tools for working with row names}
 \usage{
 has_rownames(df)
@@ -14,6 +15,8 @@ remove_rownames(df)
 
 rownames_to_column(df, var = "rowname")
 
+rowid_to_column(df, var = "rowid")
+
 column_to_rownames(df, var = "rowname")
 }
 \arguments{
@@ -29,9 +32,12 @@ to a tibble.
 Generally, it is best to avoid row names, because they are basically a
 character column with different semantics to every other column. These
 functions allow to you detect if a data frame has row names
-(\code{has_rownames}), remove them (\code{remove_rownames}), or convert
-them back-and-forth between an explicit column (\code{rownames_to_column}
-and \code{column_to_rownames}).
+(\code{has_rownames()}), remove them (\code{remove_rownames()}), or convert
+them back-and-forth between an explicit column (\code{rownames_to_column()}
+and \code{column_to_rownames()}).
+Also included is \code{rowid_to_column()} which
+adds a column at the start of the dataframe of ascending sequential row
+ids starting at 1. Note that this will remove any existing row names.
 }
 \details{
 In the printed output, the presence of row names is indicated by a star just
@@ -48,4 +54,3 @@ mtcars_tbl <- as_tibble(rownames_to_column(mtcars))
 mtcars_tbl
 column_to_rownames(as.data.frame(mtcars_tbl))
 }
-
diff --git a/man/tibble-package.Rd b/man/tibble-package.Rd
index 65c1a60..2521967 100644
--- a/man/tibble-package.Rd
+++ b/man/tibble-package.Rd
@@ -1,12 +1,12 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/tibble.R
+% Please edit documentation in R/tibble-pkg.R
 \docType{package}
 \name{tibble-package}
 \alias{tibble-package}
-\title{Simple Data Frames}
+\title{tibble: Simple Data Frames}
 \description{
-Provides a 'tbl_df' class that offers better checking and
-printing capabilities than traditional data frames.
+Provides a 'tbl_df' class (the 'tibble') that provides
+stricter checking and better formatting than the traditional data frame.
 }
 \details{
 The S3 class \code{tbl_df} wraps a local data frame. The main
@@ -21,34 +21,58 @@ describing the rest of it as text.
 
 \describe{
 \item{print}{By default only prints the first 10 rows (at most 20), and the
-  columns that fit on screen; see \code{\link{print.tbl_df}}}
+columns that fit on screen; see \code{\link[=print.tbl]{print.tbl()}}}
 \item{\code{[}}{Never simplifies (drops), so always returns data.frame}
-\item{\code{[[}, \code{$}}{Calls \code{\link{.subset2}} directly,
-  so is considerably faster. Returns \code{NULL} if column does not exist,
-  \code{$} warns.}
+\item{\code{[[}, \code{$}}{Calls \code{\link[=.subset2]{.subset2()}} directly,
+so is considerably faster. Returns \code{NULL} if column does not exist,
+\code{$} warns.}
 }
 }
 
 \section{Important functions}{
 
-\code{\link{tibble}} and \code{\link{tribble}} for construction,
-\code{\link{as_tibble}} for coercion,
-and \code{\link{print.tbl_df}} and \code{\link{glimpse}} for display.
+\code{\link[=tibble]{tibble()}} and \code{\link[=tribble]{tribble()}} for construction,
+\code{\link[=as_tibble]{as_tibble()}} for coercion,
+and \code{\link[=print.tbl]{print.tbl()}} and \code{\link[=glimpse]{glimpse()}} for display.
 }
 
 \section{Package options}{
 
-Display options for \code{tbl_df}, used by \code{\link{trunc_mat}} and
-(indirectly) by \code{\link{print.tbl_df}}.
+Display options for \code{tbl_df}, used by \code{\link[=trunc_mat]{trunc_mat()}} and
+(indirectly) by \code{\link[=print.tbl]{print.tbl()}}.
 \describe{
 \item{\code{tibble.print_max}}{Row number threshold: Maximum number of rows
-  printed. Set to \code{Inf} to always print all rows.  Default: 20.}
+printed. Set to \code{Inf} to always print all rows.  Default: 20.}
 \item{\code{tibble.print_min}}{Number of rows printed if row number
-  threshold is exceeded. Default: 10.}
+threshold is exceeded. Default: 10.}
 \item{\code{tibble.width}}{Output width. Default: \code{NULL} (use
-  \code{width} option).}
+\code{width} option).}
 \item{\code{tibble.max_extra_cols}}{Number of extra columns
-  printed in reduced form. Default: 100.}
+printed in reduced form. Default: 100.}
 }
 }
 
+\seealso{
+Useful links:
+\itemize{
+  \item \url{http://tibble.tidyverse.org/}
+  \item \url{https://github.com/tidyverse/tibble}
+  \item Report bugs at \url{https://github.com/tidyverse/tibble/issues}
+}
+
+}
+\author{
+\strong{Maintainer}: Kirill Müller \email{krlmlr+r at mailbox.org}
+
+Authors:
+\itemize{
+  \item Hadley Wickham \email{hadley at rstudio.com}
+}
+
+Other contributors:
+\itemize{
+  \item Romain Francois \email{romain at r-enthusiasts.com} [contributor]
+  \item RStudio [copyright holder]
+}
+
+}
diff --git a/man/tibble.Rd b/man/tibble.Rd
index fc26185..e93389d 100644
--- a/man/tibble.Rd
+++ b/man/tibble.Rd
@@ -1,36 +1,35 @@
 % Generated by roxygen2: do not edit by hand
-% Please edit documentation in R/dataframe.R
-\name{tibble}
-\alias{data_frame}
-\alias{data_frame_}
+% Please edit documentation in R/lst.R, R/tibble.R
+\name{lst}
 \alias{lst}
 \alias{lst_}
 \alias{tibble}
 \alias{tibble_}
-\title{Build a data frame or list.}
+\alias{data_frame}
+\alias{data_frame_}
+\title{Build a list}
 \usage{
-tibble(...)
-
-tibble_(xs)
-
-data_frame(...)
-
-data_frame_(xs)
-
 lst(...)
 
 lst_(xs)
+
+tibble(...)
+
+tibble_(xs)
 }
 \arguments{
 \item{...}{A set of name-value pairs. Arguments are evaluated sequentially,
 so you can refer to previously created variables.}
 
 \item{xs}{A list of unevaluated expressions created with \code{~},
-\code{quote()}, or \code{\link[lazyeval]{lazy}}.}
+\code{\link[=quote]{quote()}}, or (deprecated) \code{\link[lazyeval:lazy]{lazyeval::lazy()}}.}
 }
 \description{
-\code{tibble} is a trimmed down version of \code{\link{data.frame}} that:
-\enumerate{
+\code{lst()} is similar to \code{\link[=list]{list()}}, but like \code{tibble()}, it
+evaluates its arguments lazily and in order, and automatically adds names.
+
+\code{tibble()} is a trimmed down version of \code{\link[=data.frame]{data.frame()}} that:
+\itemize{
 \item Never coerces inputs (i.e. strings stay as strings!).
 \item Never adds \code{row.names}.
 \item Never munges column names.
@@ -40,13 +39,12 @@ so you can refer to previously created variables.}
 \item Automatically adds column names.
 }
 }
-\details{
-\code{lst} is similar to \code{\link{list}}, but like \code{tibble}, it
-evaluates its arguments lazily and in order, and automatically adds names.
-
-\code{data_frame} is an alias to \code{tibble}.
-}
 \examples{
+lst(n = 5, x = runif(n))
+
+# You can splice-unquote a list of quotes and formulas
+lst(!!! list(n = rlang::quo(2 + 3), y = quote(runif(n))))
+
 a <- 1:5
 tibble(a, b = a * 2)
 tibble(a, b = a * 2, c = 1)
@@ -61,8 +59,8 @@ str(tibble(x = list(diag(1), diag(2))))
 # or munges column names
 tibble(`a + b` = 1:5)
 
-# With the SE version, you give it a list of formulas/expressions
-tibble_(list(x = ~1:10, y = quote(x * 2)))
+# You can splice-unquote a list of quotes and formulas
+tibble(!!! list(x = rlang::quo(1:10), y = quote(x * 2)))
 
 # data frames can only contain 1d atomic vectors and lists
 # and can not contain POSIXlt
@@ -72,7 +70,6 @@ tibble(y = strptime("2000/01/01", "\%x"))
 }
 }
 \seealso{
-\code{\link{as_tibble}} to turn an existing list into
-  a data frame.
+\code{\link[=as_tibble]{as_tibble()}} to turn an existing list into
+a data frame.
 }
-
diff --git a/man/tidy_names.Rd b/man/tidy_names.Rd
new file mode 100644
index 0000000..677e792
--- /dev/null
+++ b/man/tidy_names.Rd
@@ -0,0 +1,60 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/repair-names.R
+\name{set_tidy_names}
+\alias{set_tidy_names}
+\alias{tidy_names}
+\alias{repair_names}
+\title{Repair object names.}
+\usage{
+set_tidy_names(x, syntactic = FALSE, quiet = FALSE)
+
+tidy_names(name, syntactic = FALSE, quiet = FALSE)
+
+repair_names(x, prefix = "V", sep = "")
+}
+\arguments{
+\item{x}{A named vector.}
+
+\item{syntactic}{Should all names be made syntactically valid via \code{\link[=make.names]{make.names()}}?}
+
+\item{quiet}{If \code{TRUE} suppresses output from this function}
+
+\item{name}{A character vector representing names.}
+
+\item{prefix}{A string, the prefix to use for new column names.}
+
+\item{sep}{A string inserted between the column name and de-duplicating
+number.}
+}
+\value{
+\code{x} with valid names.
+}
+\description{
+\code{tidy_names()} ensures its input has non-missing and
+unique names (duplicated names get a suffix of the format \code{..#}
+where \code{#} is the position in the vector).
+Valid names are left unchanged, with the exception that existing suffixes
+are reorganized.
+
+\code{tidy_names()} is the workhorse behind \code{set_tidy_names()}, it treats the
+argument as a string to be used to name a data frame or a vector.
+
+\code{repair_names()} is an older version with different renaming heuristics,
+kept for backward compatibility.  New code should prefer \code{tidy_names()}.
+}
+\examples{
+# Works for lists and vectors, too:
+set_tidy_names(3:5)
+set_tidy_names(list(3, 4, 5))
+
+# Clean data frames are left unchanged:
+set_tidy_names(mtcars)
+
+# By default, all rename operations are printed to the console:
+tbl <- as_tibble(structure(list(3, 4, 5), class = "data.frame"),
+                 validate = FALSE)
+set_tidy_names(tbl)
+
+# Optionally, names can be made syntactic:
+tidy_names("a b", syntactic = TRUE)
+}
diff --git a/man/tribble.Rd b/man/tribble.Rd
index 67a4552..122f9d6 100644
--- a/man/tribble.Rd
+++ b/man/tribble.Rd
@@ -1,24 +1,22 @@
 % Generated by roxygen2: do not edit by hand
 % Please edit documentation in R/tribble.R
 \name{tribble}
-\alias{frame_data}
 \alias{tribble}
+\alias{frame_data}
 \title{Row-wise tibble creation}
 \usage{
 tribble(...)
-
-frame_data(...)
 }
 \arguments{
 \item{...}{Arguments specifying the structure of a \code{tibble}.
 Variable names should be formulas, and may only appear before the data.}
 }
 \value{
-A \code{\link{tibble}}.
+A \link{tibble}.
 }
 \description{
-Create \code{\link{tibble}}s laying out the data in rows, rather than
-in columns. This is useful for small tables of data where readability is
+Create \link{tibble}s using an easier to read row-by-row layout.
+This is useful for small tables of data where readability is
 important.  Please see \link{tibble-package} for a general introduction.
 }
 \details{
@@ -41,4 +39,3 @@ tribble(
   "b", 4:6
 )
 }
-
diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp
index 73400a7..9c39e9e 100644
--- a/src/RcppExports.cpp
+++ b/src/RcppExports.cpp
@@ -1,4 +1,4 @@
-// This file was generated by Rcpp::compileAttributes
+// Generated by using Rcpp::compileAttributes() -> do not edit by hand
 // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
 
 #include <Rcpp.h>
@@ -7,12 +7,22 @@ using namespace Rcpp;
 
 // matrixToDataFrame
 List matrixToDataFrame(SEXP x);
-RcppExport SEXP tibble_matrixToDataFrame(SEXP xSEXP) {
+RcppExport SEXP _tibble_matrixToDataFrame(SEXP xSEXP) {
 BEGIN_RCPP
-    Rcpp::RObject __result;
-    Rcpp::RNGScope __rngScope;
+    Rcpp::RObject rcpp_result_gen;
+    Rcpp::RNGScope rcpp_rngScope_gen;
     Rcpp::traits::input_parameter< SEXP >::type x(xSEXP);
-    __result = Rcpp::wrap(matrixToDataFrame(x));
-    return __result;
+    rcpp_result_gen = Rcpp::wrap(matrixToDataFrame(x));
+    return rcpp_result_gen;
 END_RCPP
 }
+
+static const R_CallMethodDef CallEntries[] = {
+    {"_tibble_matrixToDataFrame", (DL_FUNC) &_tibble_matrixToDataFrame, 1},
+    {NULL, NULL, 0}
+};
+
+RcppExport void R_init_tibble(DllInfo *dll) {
+    R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
+    R_useDynamicSymbols(dll, FALSE);
+}
diff --git a/tests/testthat/helper-data.R b/tests/testthat/helper-data.R
index e0eb913..ea553e6 100644
--- a/tests/testthat/helper-data.R
+++ b/tests/testthat/helper-data.R
@@ -10,3 +10,17 @@ df_all <- tibble(
   h = as.list(c(1:2, NA)),
   i = list(list(1, 2:3), list(4:6), list(NA))
 )
+
+# An empty data frame with all major types
+df_empty = tibble(
+  a = integer(0),
+  b = double(0),
+  c = logical(0),
+  d = character(0),
+  e = factor(integer(0)),
+  f = as.Date(character(0)),
+  g = as.POSIXct(character(0)),
+  h = as.list(double(0)),
+  # i = list(list(integer(0)), list(character(0))),
+  to_be_added = double(0)
+)
diff --git a/tests/testthat/helper-encoding.R b/tests/testthat/helper-encoding.R
new file mode 100644
index 0000000..73a5da3
--- /dev/null
+++ b/tests/testthat/helper-encoding.R
@@ -0,0 +1,53 @@
+get_lang_strings <- function() {
+  lang_strings <- c(
+    de = "Gl\u00fcck",
+    cn = "\u5e78\u798f",
+    ru = "\u0441\u0447\u0430\u0441\u0442\u044c\u0435",
+    ko = "\ud589\ubcf5"
+  )
+
+  native_lang_strings <- enc2native(lang_strings)
+
+  same <- (lang_strings == native_lang_strings)
+
+  list(
+    same = lang_strings[same],
+    different = lang_strings[!same]
+  )
+}
+
+get_native_lang_string <- function() {
+  lang_strings <- get_lang_strings()
+  if (length(lang_strings$same) == 0) testthat::skip("No native language string available")
+  lang_strings$same[[1L]]
+}
+
+get_alien_lang_string <- function() {
+  lang_strings <- get_lang_strings()
+  if (length(lang_strings$different) == 0) testthat::skip("No alien language string available")
+  lang_strings$different[[1L]]
+}
+
+with_non_utf8_locale <- function(code) {
+  old_locale <- set_non_utf8_locale()
+  on.exit(set_locale(old_locale), add = TRUE)
+  code
+}
+
+set_non_utf8_locale <- function() {
+  if (.Platform$OS.type == "windows") return(NULL)
+  tryCatch(
+    locale <- set_locale("en_US.ISO88591"),
+    warning = function(e) {
+      testthat::skip("Cannot set latin-1 locale")
+    }
+  )
+  locale
+}
+
+set_locale <- function(locale) {
+  if (is.null(locale)) return(NULL)
+  locale <- Sys.getlocale("LC_CTYPE")
+  Sys.setlocale("LC_CTYPE", locale)
+  locale
+}
diff --git a/tests/testthat/helper-output.R b/tests/testthat/helper-output.R
index 93f1d4f..9c78e76 100644
--- a/tests/testthat/helper-output.R
+++ b/tests/testthat/helper-output.R
@@ -1,18 +1,11 @@
 output_file <- function(filename) file.path("output", filename)
 
 expect_output_file_rel <- function(x, filename) {
-  expect_output_file(x, output_file(filename), update = TRUE)
+  withr::with_options(list(digits = 4, width = 80),
+    expect_output_file(x, output_file(filename), update = TRUE)
+  )
 }
 
 expect_output_knit <- function(knit, filename, envir = parent.frame()) {
-  expect_asis_output_with_cacheable(substitute(knit), envir)
   expect_output_file_rel(cat(knit), filename)
 }
-
-expect_asis_output_with_cacheable <- function(knit_call, envir) {
-  asis_output_args <- with_mock(
-    `knitr::asis_output` = function(...) list(...),
-    eval(knit_call, envir = envir)
-  )
-  expect_true(asis_output_args[["cacheable"]])
-}
diff --git a/tests/testthat/helper-type-sum.R b/tests/testthat/helper-type-sum.R
new file mode 100644
index 0000000..6548ba6
--- /dev/null
+++ b/tests/testthat/helper-type-sum.R
@@ -0,0 +1,9 @@
+as_override_type_sum <- function(x) {
+  structure(x, class = "override_type_sum")
+}
+
+type_sum.override_type_sum <- function(x, ...) {
+  "SC"
+}
+
+registerS3method("type_sum", "override_type_sum", type_sum.override_type_sum)
diff --git a/tests/testthat/output/glimpse/all-35.txt b/tests/testthat/output/glimpse/all-35.txt
index 7b96f95..f3e268b 100644
--- a/tests/testthat/output/glimpse/all-35.txt
+++ b/tests/testthat/output/glimpse/all-35.txt
@@ -8,4 +8,4 @@ $ e <fctr> a, b, NA
 $ f <date> 2015-12-10, 2015-12...
 $ g <dttm> 2015-12-09 10:51:35...
 $ h <list> [1, 2, NA]
-$ i <list> [[1, <2, 3>], <4, 5...
+$ i <list> [[1, <2, 3>], [<4, ...
diff --git a/tests/testthat/output/glimpse/all-50.txt b/tests/testthat/output/glimpse/all-50.txt
index 9b17cc6..8308a0a 100644
--- a/tests/testthat/output/glimpse/all-50.txt
+++ b/tests/testthat/output/glimpse/all-50.txt
@@ -8,4 +8,4 @@ $ e <fctr> a, b, NA
 $ f <date> 2015-12-10, 2015-12-11, NA
 $ g <dttm> 2015-12-09 10:51:35, 2015-12-09 10...
 $ h <list> [1, 2, NA]
-$ i <list> [[1, <2, 3>], <4, 5, 6>, NA]
+$ i <list> [[1, <2, 3>], [<4, 5, 6>], [NA]]
diff --git a/tests/testthat/output/glimpse/all-70.txt b/tests/testthat/output/glimpse/all-70.txt
index 2a50498..5e9024f 100644
--- a/tests/testthat/output/glimpse/all-70.txt
+++ b/tests/testthat/output/glimpse/all-70.txt
@@ -8,4 +8,4 @@ $ e <fctr> a, b, NA
 $ f <date> 2015-12-10, 2015-12-11, NA
 $ g <dttm> 2015-12-09 10:51:35, 2015-12-09 10:51:36, NA
 $ h <list> [1, 2, NA]
-$ i <list> [[1, <2, 3>], <4, 5, 6>, NA]
+$ i <list> [[1, <2, 3>], [<4, 5, 6>], [NA]]
diff --git a/tests/testthat/output/glimpse/non-syntactic.txt b/tests/testthat/output/glimpse/non-syntactic.txt
new file mode 100644
index 0000000..1c2edbe
--- /dev/null
+++ b/tests/testthat/output/glimpse/non-syntactic.txt
@@ -0,0 +1,4 @@
+Observations: 1
+Variables: 2
+$ `mean(x)` <dbl> 5
+$ `var(x)`  <dbl> 3
diff --git a/tests/testthat/output/trunc_mat/POSIXlt-8-60.txt b/tests/testthat/output/trunc_mat/POSIXlt-8-60.txt
index bf6ae9a..d4d6f3a 100644
--- a/tests/testthat/output/trunc_mat/POSIXlt-8-60.txt
+++ b/tests/testthat/output/trunc_mat/POSIXlt-8-60.txt
@@ -1,4 +1,4 @@
-# A tibble: 12 × 2
+# A tibble: 12 x 2
                     x         y
                <dttm>    <dttm>
 1 2016-01-01 12:34:57 <POSIXlt>
diff --git a/tests/testthat/output/trunc_mat/all--30.txt b/tests/testthat/output/trunc_mat/all--30.txt
index b6a3cf9..b5f1c78 100644
--- a/tests/testthat/output/trunc_mat/all--30.txt
+++ b/tests/testthat/output/trunc_mat/all--30.txt
@@ -1,4 +1,4 @@
-# A tibble: 3 × 9
+# A tibble: 3 x 9
       a     b     c     d
   <dbl> <int> <lgl> <chr>
 1   1.0     1  TRUE     a
diff --git a/tests/testthat/output/trunc_mat/all--300.txt b/tests/testthat/output/trunc_mat/all--300.txt
index b93357c..931b71a 100644
--- a/tests/testthat/output/trunc_mat/all--300.txt
+++ b/tests/testthat/output/trunc_mat/all--300.txt
@@ -1,11 +1,6 @@
-# A tibble: 3 × 9
-      a     b     c     d      e          f                   g         h
-  <dbl> <int> <lgl> <chr> <fctr>     <date>              <dttm>    <list>
-1   1.0     1  TRUE     a      a 2015-12-10 2015-12-09 10:51:35 <int [1]>
-2   2.5     2 FALSE     b      b 2015-12-11 2015-12-09 10:51:36 <int [1]>
-3    NA    NA    NA  <NA>     NA       <NA>                <NA> <int [1]>
-           i
-      <list>
-1 <list [2]>
-2 <list [1]>
-3 <list [1]>
+# A tibble: 3 x 9
+      a     b     c     d      e          f                   g         h          i
+  <dbl> <int> <lgl> <chr> <fctr>     <date>              <dttm>    <list>     <list>
+1   1.0     1  TRUE     a      a 2015-12-10 2015-12-09 10:51:35 <int [1]> <list [2]>
+2   2.5     2 FALSE     b      b 2015-12-11 2015-12-09 10:51:36 <int [1]> <list [1]>
+3    NA    NA    NA  <NA>   <NA>         NA                  NA <int [1]> <list [1]>
diff --git a/tests/testthat/output/trunc_mat/all-1-30-0.txt b/tests/testthat/output/trunc_mat/all-1-30-0.txt
index 3c9ea8d..cc9d38f 100644
--- a/tests/testthat/output/trunc_mat/all-1-30-0.txt
+++ b/tests/testthat/output/trunc_mat/all-1-30-0.txt
@@ -1,4 +1,4 @@
-# A tibble: 3 × 9
+# A tibble: 3 x 9
       a     b     c     d
   <dbl> <int> <lgl> <chr>
 1     1     1  TRUE     a
diff --git a/tests/testthat/output/trunc_mat/all-1-30-2.txt b/tests/testthat/output/trunc_mat/all-1-30-2.txt
index 2e19d85..711260b 100644
--- a/tests/testthat/output/trunc_mat/all-1-30-2.txt
+++ b/tests/testthat/output/trunc_mat/all-1-30-2.txt
@@ -1,4 +1,4 @@
-# A tibble: 3 × 9
+# A tibble: 3 x 9
       a     b     c     d
   <dbl> <int> <lgl> <chr>
 1     1     1  TRUE     a
diff --git a/tests/testthat/output/trunc_mat/all-knit-120.txt b/tests/testthat/output/trunc_mat/all-knit-120.txt
index d73c346..7c0750a 100644
--- a/tests/testthat/output/trunc_mat/all-knit-120.txt
+++ b/tests/testthat/output/trunc_mat/all-knit-120.txt
@@ -1,12 +1,12 @@
 
 
-A tibble: 3 × 9
+A tibble: 3 x 9
 
 |a     |b     |c     |d     |e      |f          |g                   |h         |i          |
 |:-----|:-----|:-----|:-----|:------|:----------|:-------------------|:---------|:----------|
 |<dbl> |<int> |<lgl> |<chr> |<fctr> |<date>     |<dttm>              |<list>    |<list>     |
 |1.0   |1     |TRUE  |a     |a      |2015-12-10 |2015-12-09 10:51:35 |<int [1]> |<list [2]> |
 |2.5   |2     |FALSE |b     |b      |2015-12-11 |2015-12-09 10:51:36 |<int [1]> |<list [1]> |
-|NA    |NA    |NA    |<NA>  |NA     |NA         |NA                  |<int [1]> |<list [1]> |
+|NA    |NA    |NA    |<NA>  |<NA>   |NA         |NA                  |<int [1]> |<list [1]> |
 
 
diff --git a/tests/testthat/output/trunc_mat/all-knit-60.txt b/tests/testthat/output/trunc_mat/all-knit-60.txt
index 2846c03..e338194 100644
--- a/tests/testthat/output/trunc_mat/all-knit-60.txt
+++ b/tests/testthat/output/trunc_mat/all-knit-60.txt
@@ -1,12 +1,12 @@
 
 
-A tibble: 3 × 9
+A tibble: 3 x 9
 
 |a     |b     |c     |d     |e      |f          |
 |:-----|:-----|:-----|:-----|:------|:----------|
 |<dbl> |<int> |<lgl> |<chr> |<fctr> |<date>     |
 |1.0   |1     |TRUE  |a     |a      |2015-12-10 |
 |2.5   |2     |FALSE |b     |b      |2015-12-11 |
-|NA    |NA    |NA    |<NA>  |NA     |NA         |
+|NA    |NA    |NA    |<NA>  |<NA>   |NA         |
 
 (with 3 more variables: g <dttm>, h <list>, i <list>)
diff --git a/tests/testthat/output/trunc_mat/iris--70.txt b/tests/testthat/output/trunc_mat/iris--70.txt
index 95a5482..81757bf 100644
--- a/tests/testthat/output/trunc_mat/iris--70.txt
+++ b/tests/testthat/output/trunc_mat/iris--70.txt
@@ -1,14 +1,14 @@
-# A tibble: 150 × 5
+# A tibble: 150 x 5
    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
           <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
-1           5.1         3.5          1.4         0.2  setosa
-2           4.9         3.0          1.4         0.2  setosa
-3           4.7         3.2          1.3         0.2  setosa
-4           4.6         3.1          1.5         0.2  setosa
-5           5.0         3.6          1.4         0.2  setosa
-6           5.4         3.9          1.7         0.4  setosa
-7           4.6         3.4          1.4         0.3  setosa
-8           5.0         3.4          1.5         0.2  setosa
-9           4.4         2.9          1.4         0.2  setosa
+ 1          5.1         3.5          1.4         0.2  setosa
+ 2          4.9         3.0          1.4         0.2  setosa
+ 3          4.7         3.2          1.3         0.2  setosa
+ 4          4.6         3.1          1.5         0.2  setosa
+ 5          5.0         3.6          1.4         0.2  setosa
+ 6          5.4         3.9          1.7         0.4  setosa
+ 7          4.6         3.4          1.4         0.3  setosa
+ 8          5.0         3.4          1.5         0.2  setosa
+ 9          4.4         2.9          1.4         0.2  setosa
 10          4.9         3.1          1.5         0.1  setosa
 # ... with 140 more rows
diff --git a/tests/testthat/output/trunc_mat/iris-3-5.txt b/tests/testthat/output/trunc_mat/iris-3-5.txt
index d5ce8c0..2b9fe1c 100644
--- a/tests/testthat/output/trunc_mat/iris-3-5.txt
+++ b/tests/testthat/output/trunc_mat/iris-3-5.txt
@@ -1,7 +1,7 @@
 # A
 #   tibble:
 #   150
-#   ×
+#   x
 #   5
   Sepal.Length
          <dbl>
diff --git a/tests/testthat/output/trunc_mat/iris-5-30.txt b/tests/testthat/output/trunc_mat/iris-5-30.txt
index 9109a43..230438d 100644
--- a/tests/testthat/output/trunc_mat/iris-5-30.txt
+++ b/tests/testthat/output/trunc_mat/iris-5-30.txt
@@ -1,4 +1,4 @@
-# A tibble: 150 × 5
+# A tibble: 150 x 5
   Sepal.Length Sepal.Width
          <dbl>       <dbl>
 1          5.1         3.5
diff --git a/tests/testthat/output/trunc_mat/iris_unk-10-70.txt b/tests/testthat/output/trunc_mat/iris_unk-10-70.txt
index 1851eb9..21e41a5 100644
--- a/tests/testthat/output/trunc_mat/iris_unk-10-70.txt
+++ b/tests/testthat/output/trunc_mat/iris_unk-10-70.txt
@@ -1,13 +1,13 @@
    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
           <dbl>       <dbl>        <dbl>       <dbl>  <fctr>
-1           5.1         3.5          1.4         0.2  setosa
-2           4.9         3.0          1.4         0.2  setosa
-3           4.7         3.2          1.3         0.2  setosa
-4           4.6         3.1          1.5         0.2  setosa
-5           5.0         3.6          1.4         0.2  setosa
-6           5.4         3.9          1.7         0.4  setosa
-7           4.6         3.4          1.4         0.3  setosa
-8           5.0         3.4          1.5         0.2  setosa
-9           4.4         2.9          1.4         0.2  setosa
+ 1          5.1         3.5          1.4         0.2  setosa
+ 2          4.9         3.0          1.4         0.2  setosa
+ 3          4.7         3.2          1.3         0.2  setosa
+ 4          4.6         3.1          1.5         0.2  setosa
+ 5          5.0         3.6          1.4         0.2  setosa
+ 6          5.4         3.9          1.7         0.4  setosa
+ 7          4.6         3.4          1.4         0.3  setosa
+ 8          5.0         3.4          1.5         0.2  setosa
+ 9          4.4         2.9          1.4         0.2  setosa
 10          4.9         3.1          1.5         0.1  setosa
 # ... with more rows
diff --git a/tests/testthat/output/trunc_mat/long-5-30.txt b/tests/testthat/output/trunc_mat/long-5-30.txt
index b21cc49..3d9580e 100644
--- a/tests/testthat/output/trunc_mat/long-5-30.txt
+++ b/tests/testthat/output/trunc_mat/long-5-30.txt
@@ -1,4 +1,4 @@
-# A tibble: 10,000 × 1
+# A tibble: 10,000 x 1
       a
   <int>
 1     1
diff --git a/tests/testthat/output/trunc_mat/mtcars-8-30.txt b/tests/testthat/output/trunc_mat/mtcars-8-30.txt
index 086f64c..ba9d7e2 100644
--- a/tests/testthat/output/trunc_mat/mtcars-8-30.txt
+++ b/tests/testthat/output/trunc_mat/mtcars-8-30.txt
@@ -1,4 +1,4 @@
-# A tibble: 32 × 11
+# A tibble: 32 x 11
     mpg   cyl  disp    hp
 * <dbl> <dbl> <dbl> <dbl>
 1  21.0     6 160.0   110
diff --git a/tests/testthat/output/trunc_mat/mtcars-knit-60.txt b/tests/testthat/output/trunc_mat/mtcars-knit-60.txt
index 4ac2513..1583523 100644
--- a/tests/testthat/output/trunc_mat/mtcars-knit-60.txt
+++ b/tests/testthat/output/trunc_mat/mtcars-knit-60.txt
@@ -1,6 +1,6 @@
 
 
-data.frame [32 × 11]
+data.frame [32 x 11]
 
 |mpg   |cyl   |disp  |hp    |drat  |wt    |qsec  |vs    |am    |
 |:-----|:-----|:-----|:-----|:-----|:-----|:-----|:-----|:-----|
diff --git a/tests/testthat/output/trunc_mat/newline.txt b/tests/testthat/output/trunc_mat/newline.txt
new file mode 100644
index 0000000..b77799a
--- /dev/null
+++ b/tests/testthat/output/trunc_mat/newline.txt
@@ -0,0 +1,5 @@
+# A tibble: 2 x 2
+   `\n`   `\r`
+  <chr> <fctr>
+1  "\n"   "\n"
+2  "\""   "\""
diff --git a/tests/testthat/output/trunc_mat/non-syntactic.txt b/tests/testthat/output/trunc_mat/non-syntactic.txt
new file mode 100644
index 0000000..73fd64a
--- /dev/null
+++ b/tests/testthat/output/trunc_mat/non-syntactic.txt
@@ -0,0 +1,4 @@
+# A tibble: 1 x 2
+  `mean(x)` `var(x)`
+      <dbl>    <dbl>
+1         5        3
diff --git a/tests/testthat/output/trunc_mat/wide-8-60.txt b/tests/testthat/output/trunc_mat/wide-8-60.txt
index a7064f8..5ac413a 100644
--- a/tests/testthat/output/trunc_mat/wide-8-60.txt
+++ b/tests/testthat/output/trunc_mat/wide-8-60.txt
@@ -1,4 +1,4 @@
-# A tibble: 3 × 2
+# A tibble: 3 x 2
   成交日期 合同录入日期
      <int>        <int>
 1        1            4
diff --git a/tests/testthat/output/trunc_mat/zero_cols-5-30.txt b/tests/testthat/output/trunc_mat/zero_cols-5-30.txt
index c926475..e6e85e6 100644
--- a/tests/testthat/output/trunc_mat/zero_cols-5-30.txt
+++ b/tests/testthat/output/trunc_mat/zero_cols-5-30.txt
@@ -1 +1 @@
-# A tibble: 150 × 0
+# A tibble: 150 x 0
diff --git a/tests/testthat/output/trunc_mat/zero_rows--30.txt b/tests/testthat/output/trunc_mat/zero_rows--30.txt
index bcd6586..0a24b97 100644
--- a/tests/testthat/output/trunc_mat/zero_rows--30.txt
+++ b/tests/testthat/output/trunc_mat/zero_rows--30.txt
@@ -1,3 +1,3 @@
-# A tibble: 0 × 2
+# A tibble: 0 x 2
 # ... with 2 variables:
 #   a <chr>, b <lgl>
diff --git a/tests/testthat/test-add.R b/tests/testthat/test-add.R
index d3d6f82..d74f009 100644
--- a/tests/testthat/test-add.R
+++ b/tests/testthat/test-add.R
@@ -41,15 +41,23 @@ test_that("adds empty row if no arguments", {
 })
 
 test_that("error if adding row with unknown variables", {
-  expect_error(add_row(tibble(a = 3), xxyzy = "err"),
-               "would add new variables")
+  expect_error(
+    add_row(tibble(a = 3), xxyzy = "err"),
+    "Can't add row with new variable `xxyzy`",
+    fixed = TRUE
+  )
+
+  expect_error(
+    add_row(tibble(a = 3), b = "err", c = "oops"),
+    "Can't add row with new variables `b`, `c`",
+    fixed = TRUE
+  )
 })
 
 test_that("can add multiple rows", {
   df <- tibble(a = 3L)
   df_new <- add_row(df, a = 4:5)
-  expect_identical(nrow(df_new), nrow(df) + 2L)
-  expect_identical(df_new$a, 3:5)
+  expect_identical(df_new, tibble(a = 3:5))
 })
 
 test_that("can recycle when adding rows", {
@@ -63,27 +71,38 @@ test_that("can recycle when adding rows", {
 test_that("can add as first row via .before = 1", {
   df <- tibble(a = 3L)
   df_new <- add_row(df, a = 2L, .before = 1)
-  expect_identical(nrow(df_new), nrow(df) + 1L)
-  expect_identical(df_new$a, 2:3)
+  expect_identical(df_new, tibble(a = 2:3))
 })
 
 test_that("can add as first row via .after = 0", {
   df <- tibble(a = 3L)
   df_new <- add_row(df, a = 2L, .after = 0)
-  expect_identical(nrow(df_new), nrow(df) + 1L)
-  expect_identical(df_new$a, 2:3)
+  expect_identical(df_new, tibble(a = 2:3))
 })
 
 test_that("can add row inbetween", {
   df <- tibble(a = 1:3)
   df_new <- add_row(df, a = 4:5, .after = 2)
-  expect_identical(nrow(df_new), nrow(df) + 2L)
-  expect_identical(df_new$a, c(1:2, 4:5, 3L))
+  expect_identical(df_new, tibble(a = c(1:2, 4:5, 3L)))
+})
+
+test_that("can safely add to factor columns everywhere (#296)", {
+  df <- tibble(a = factor(letters[1:3]))
+  expect_identical(add_row(df), tibble(a = factor(c(letters[1:3], NA))))
+  expect_identical(add_row(df, .before = 1), tibble(a = factor(c(NA, letters[1:3]))))
+  expect_identical(add_row(df, .before = 2), tibble(a = factor(c("a", NA, letters[2:3]))))
+  expect_identical(add_row(df, a = "d"), tibble(a = factor(c(letters[1:4]))))
+  expect_identical(add_row(df, a = "d", .before = 1), tibble(a = factor(c("d", letters[1:3]))))
+  expect_identical(add_row(df, a = "d", .before = 2), tibble(a = factor(c("a", "d", letters[2:3]))))
 })
 
 test_that("error if both .before and .after are given", {
   df <- tibble(a = 1:3)
-  expect_error(add_row(df, a = 4:5, .after = 2, .before = 3))
+  expect_error(
+    add_row(df, a = 4:5, .after = 2, .before = 3),
+    "Can't specify both `.before` and `.after`",
+    fixed = TRUE
+  )
 })
 
 test_that("missing row names stay missing when adding row", {
@@ -100,6 +119,19 @@ test_that("adding to a list column adds a NULL value (#148)", {
   expect_null(add_row(data_frame(a = as.list(1:3), b = 1:3), b = 4:6)$a[[5]])
 })
 
+test_that("add_row() keeps the class of empty columns", {
+  new_tibble <- add_row(df_empty, to_be_added = 5)
+  expect_equal(sapply(df_empty, class), sapply(new_tibble, class))
+})
+
+test_that("add_row() fails nicely for grouped data frames (#179)", {
+  expect_error(
+    add_row(dplyr::group_by(iris, Species), Petal.Width = 3),
+    "Can't add rows to grouped data frames",
+    fixed = TRUE
+  )
+})
+
 # add_column ------------------------------------------------------------
 
 test_that("can add new column", {
@@ -139,73 +171,96 @@ test_that("add_column() keeps unchanged if no arguments", {
 })
 
 test_that("error if adding existing columns", {
-  expect_error(add_column(tibble(a = 3), a = 5),
-               "Columns already in data frame")
+  expect_error(
+    add_column(tibble(a = 3), a = 5),
+    "Column `a` already exists",
+    fixed = TRUE
+  )
 })
 
 test_that("error if adding wrong number of rows with add_column()", {
-  expect_error(add_column(tibble(a = 3), b = 4:5),
-               "Expected 1 rows, got 2")
+  expect_error(
+    add_column(tibble(a = 3), b = 4:5),
+    "`.data` must have 1 row, not 2",
+    fixed = TRUE
+  )
 })
 
 test_that("can add multiple columns", {
   df <- tibble(a = 1:3)
   df_new <- add_column(df, b = 4:6, c = 3:1)
-  expect_identical(ncol(df_new), ncol(df) + 2L)
-  expect_identical(df_new$b, 4:6)
-  expect_identical(df_new$c, 3:1)
+  expect_identical(df_new, tibble(a = 1:3, b = 4:6, c = 3:1))
 })
 
 test_that("can recycle when adding columns", {
   df <- tibble(a = 1:3)
   df_new <- add_column(df, b = 4, c = 3:1)
-  expect_identical(ncol(df_new), ncol(df) + 2L)
-  expect_identical(df_new$b, rep(4, 3))
-  expect_identical(df_new$c, 3:1)
+  expect_identical(df_new, tibble(a = 1:3, b = rep(4, 3), c = 3:1))
+})
+
+test_that("can recycle when adding a column of length 1", {
+  df <- tibble(a = 1:3)
+  df_new <- add_column(df, b = 4)
+  expect_identical(df_new, tibble(a = 1:3, b = rep(4, 3)))
+})
+
+test_that("can recyle when adding multiple columns of length 1", {
+  df <- tibble(a = 1:3)
+  df_new <- add_column(df, b = 4, c = 5)
+  expect_identical(df_new, tibble(a = 1:3, b = rep(4, 3), c = rep(5, 3)))
+})
+
+test_that("can recyle for zero-row data frame (#167)", {
+  df <- tibble(a = 1:3)[0, ]
+  df_new <- add_column(df, b = 4, c = character())
+  expect_identical(df_new, tibble(a = integer(), b = numeric(), c = character()))
 })
 
 test_that("can add as first column via .before = 1", {
   df <- tibble(a = 3L)
   df_new <- add_column(df, b = 2L, .before = 1)
-  expect_identical(ncol(df_new), ncol(df) + 1L)
-  expect_identical(names(df_new), c("b", "a"))
-  expect_identical(df_new$b, 2L)
+  expect_identical(df_new, tibble(b = 2L, a = 3L))
 })
 
 test_that("can add as first column via .after = 0", {
   df <- tibble(a = 3L)
   df_new <- add_column(df, b = 2L, .after = 0)
-  expect_identical(ncol(df_new), ncol(df) + 1L)
-  expect_identical(names(df_new), c("b", "a"))
-  expect_identical(df_new$b, 2L)
+  expect_identical(df_new, tibble(b = 2L, a = 3L))
 })
 
 test_that("can add column inbetween", {
   df <- tibble(a = 1:3, c = 4:6)
   df_new <- add_column(df, b = -1:1, .after = 1)
-  expect_identical(ncol(df_new), ncol(df) + 1L)
-  expect_identical(names(df_new), c("a", "b", "c"))
-  expect_identical(df_new$b, -1:1)
+  expect_identical(df_new, tibble(a = 1:3, b = -1:1, c = 4:6))
 })
 
 test_that("can add column relative to named column", {
   df <- tibble(a = 1:3, c = 4:6)
   df_new <- add_column(df, b = -1:1, .before = "c")
-  expect_identical(ncol(df_new), ncol(df) + 1L)
-  expect_identical(names(df_new), c("a", "b", "c"))
-  expect_identical(df_new$b, -1:1)
+  expect_identical(df_new, tibble(a = 1:3, b = -1:1, c = 4:6))
 })
 
 test_that("error if both .before and .after are given", {
   df <- tibble(a = 1:3)
-  expect_error(add_column(df, b = 4:6, .after = 2, .before = 3),
-               "Can't specify both [.]before and [.]after")
+  expect_error(
+    add_column(df, b = 4:6, .after = 2, .before = 3),
+    "Can't specify both `.before` and `.after`",
+    fixed = TRUE
+  )
 })
 
 test_that("error if column named by .before or .after not found", {
   df <- tibble(a = 1:3)
-  expect_error(add_column(df, b = 4:6, .after = "x"), "Unknown columns")
-  expect_error(add_column(df, b = 4:6, .before = "x"), "Unknown columns")
+  expect_error(
+    add_column(df, b = 4:6, .after = "x"),
+    "Column `x` not found",
+    fixed = TRUE
+  )
+  expect_error(
+    add_column(df, b = 4:6, .before = "x"),
+    "Column `x` not found",
+    fixed = TRUE
+  )
 })
 
 test_that("missing row names stay missing when adding column", {
diff --git a/tests/testthat/test-data-frame.R b/tests/testthat/test-data-frame.R
index f503e2b..1129d2e 100644
--- a/tests/testthat/test-data-frame.R
+++ b/tests/testthat/test-data-frame.R
@@ -1,22 +1,29 @@
 context("tibble")
 
 test_that("tibble returns correct number of rows with all combinatinos", {
-
   expect_equal(nrow(tibble(value = 1:10)), 10L)
-
   expect_equal(nrow(tibble(value = 1:10, name = "recycle_me")), 10L)
-
   expect_equal(nrow(tibble(name = "recycle_me", value = 1:10)), 10L)
-
   expect_equal(nrow(tibble(name = "recycle_me", value = 1:10, value2 = 11:20)), 10L)
-
   expect_equal(nrow(tibble(value = 1:10, name = "recycle_me", value2 = 11:20)), 10L)
-
 })
 
 test_that("can't make tibble containing data.frame or array", {
-  expect_error(tibble(mtcars), "must be a 1d atomic vector or list")
-  expect_error(tibble(diag(5)), "must be a 1d atomic vector or list")
+  expect_error(
+    tibble(mtcars),
+    "Column `mtcars` must be a 1d atomic vector or a list",
+    fixed = TRUE
+  )
+  expect_error(
+    tibble(diag(5)),
+    "Column `diag(5)` must be a 1d atomic vector or a list",
+    fixed = TRUE
+  )
+  expect_error(
+    tibble(mtcars, diag(5)),
+    "Columns `mtcars`, `diag(5)` must be 1d atomic vectors or lists",
+    fixed = TRUE
+  )
 })
 
 test_that("dim attribute is stripped of 1D array (#84)", {
@@ -24,19 +31,36 @@ test_that("dim attribute is stripped of 1D array (#84)", {
 })
 
 test_that("bogus columns raise an error", {
-  expect_error(as_tibble(list(1)), "named")
-  expect_error(tibble(a = NULL), "1d atomic vector or list")
-  expect_error(tibble(a = ~a), "1d atomic vector or list")
-  expect_error(tibble(a = new.env()), "1d atomic vector or list")
-  expect_error(tibble(a = quote(a)), "1d atomic vector or list")
+  expect_error(
+    as_tibble(list(1)),
+    "Column 1 must be named",
+    fixed = TRUE
+  )
+  expect_error(
+    tibble(a = NULL),
+    "Column `a` must be a 1d atomic vector or a list",
+    fixed = TRUE
+  )
+  expect_error(
+    tibble(a = new.env()),
+    "Column `a` must be a 1d atomic vector or a list",
+    fixed = TRUE
+  )
+  expect_error(
+    tibble(a = quote(a)),
+    "Column `a` must be a 1d atomic vector or a list",
+    fixed = TRUE
+  )
 })
 
 test_that("length 1 vectors are recycled", {
   expect_equal(nrow(tibble(x = 1:10)), 10)
   expect_equal(nrow(tibble(x = 1:10, y = 1)), 10)
   expect_error(
-    nrow(tibble(x = 1:10, y = 1:2)),
-    "Variables must be length 1 or 10"
+    tibble(x = 1:10, y = 1:2),
+    "Column `y` must be length 1 or 10, not 2",
+    fixed = TRUE
+
   )
 })
 
@@ -88,21 +112,55 @@ test_that("tibble aliases", {
 # as_tibble -----------------------------------------------------------
 
 test_that("columns must be same length", {
-  l <- list(x = 1:2, y = 1:3)
-  expect_error(as_tibble(l), "must be length 1 or")
+  expect_error(
+    as_tibble(list(x = 1:2, y = 1:3)),
+    "Column `x` must be length 1 or 3, not 2",
+    fixed = TRUE
+  )
+  expect_error(
+    as_tibble(list(x = 1:2, y = 1:3, z = 1:4)),
+    "Columns `x`, `y` must be length 1 or 4, not 2, 3",
+    fixed = TRUE
+  )
+  expect_error(
+    as_tibble(list(x = 1:4, y = 1:2, z = 1:2)),
+    "Columns `y`, `z` must be length 1 or 4, not 2, 2",
+    fixed = TRUE
+  )
 })
 
 test_that("columns must be named", {
   l1 <- list(1:10)
   l2 <- list(x = 1, 2)
 
-  expect_error(as_tibble(l1), "must be named")
-  expect_error(as_tibble(l2), "must be named")
+  expect_error(
+    as_tibble(l1),
+    "Column 1 must be named",
+    fixed = TRUE
+  )
+  expect_error(
+    as_tibble(l2),
+    "Column 2 must be named",
+    fixed = TRUE
+  )
 })
 
 test_that("can't coerce list data.frame or array", {
-  expect_error(as_tibble(list(x = mtcars)), "must be a 1d atomic vector or list")
-  expect_error(as_tibble(list(x = diag(5))), "must be a 1d atomic vector or list")
+  expect_error(
+    as_tibble(list(x = mtcars)),
+    "Column `x` must be a 1d atomic vector or a list",
+    fixed = TRUE
+  )
+  expect_error(
+    as_tibble(list(x = diag(5))),
+    "Column `x` must be a 1d atomic vector or a list",
+    fixed = TRUE
+  )
+  expect_error(
+    as_tibble(list(x = mtcars, y = diag(5))),
+    "Columns `x`, `y` must be 1d atomic vectors or lists",
+    fixed = TRUE
+  )
 })
 
 test_that("empty list() makes 0 x 0 tbl_df", {
@@ -155,31 +213,65 @@ test_that("Can convert named atomic vectors to data frame", {
 })
 
 
-test_that("as_tibble alias", {
+test_that("as_tibble() can validate (#278)", {
+  df <- tibble(a = 1, b = 2)
+  names(df) <- c("", NA)
+  expect_error(as_tibble(df), NA)
+  expect_error(
+    as_tibble(df, validate = TRUE),
+    "Columns 1, 2 must be named",
+    fixed = TRUE
+  )
+})
+
+
+test_that("as_data_frame is an alias of as_tibble", {
   expect_identical(as_data_frame(NULL), as_tibble(NULL))
 })
 
+test_that("as.tibble is an alias of as_tibble", {
+  expect_identical(as.tibble(NULL), as_tibble(NULL))
+})
+
 
 # Validation --------------------------------------------------------------
 
 test_that("2d object isn't a valid column", {
   expect_error(
     check_tibble(list(x = mtcars)),
-    "Each variable must be a 1d atomic vector"
+    "Column `x` must be a 1d atomic vector or a list",
+    fixed = TRUE
+  )
+  expect_error(
+    check_tibble(list(x = mtcars, y = mtcars)),
+    "Columns `x`, `y` must be 1d atomic vectors or lists",
+    fixed = TRUE
   )
 })
 
 test_that("POSIXlt isn't a valid column", {
   expect_error(
     check_tibble(list(x = as.POSIXlt(Sys.time()))),
-    "Date/times must be stored as POSIXct"
+    "Column `x` is a date/time and must be stored as POSIXct, not POSIXlt",
+    fixed = TRUE
+  )
+  expect_error(
+    check_tibble(list(x = as.POSIXlt(Sys.time()), y = as.POSIXlt(Sys.time()))),
+    "Columns `x`, `y` are dates/times and must be stored as POSIXct, not POSIXlt",
+    fixed = TRUE
   )
 })
 
 test_that("NULL isn't a valid column", {
   expect_error(
     check_tibble(list(a = NULL)),
-    "Each variable must be a 1d atomic vector"
+    "Column `a` must be a 1d atomic vector or a list",
+    fixed = TRUE
+  )
+  expect_error(
+    check_tibble(list(a = NULL, b = NULL)),
+    "Columns `a`, `b` must be 1d atomic vectors or lists",
+    fixed = TRUE
   )
 })
 
@@ -188,23 +280,61 @@ test_that("columns must be named (#1101)", {
 
   expect_error(
     check_tibble(l),
-    "Each variable must be named"
+    "Columns 1, 2 must be named",
+    fixed = TRUE
   )
 
   expect_error(
     check_tibble(setNames(l, c("x", ""))),
-    "Each variable must be named"
+    "Column 2 must be named",
+    fixed = TRUE
   )
 
   expect_error(
     check_tibble(setNames(l, c("x", NA))),
-    "Each variable must be named"
+    "Column 2 must be named",
+    fixed = TRUE
   )
 })
 
 test_that("names must be unique (#820)", {
   expect_error(
-    check_tibble(list(x = 1, x = 2)),
-    "Each variable must have a unique name"
+    check_tibble(list(x = 1, x = 2, y = 3)),
+    "Column `x` must have a unique name",
+    fixed = TRUE
+  )
+  expect_error(
+    check_tibble(list(x = 1, x = 2, y = 3, y = 4)),
+    "Columns `x`, `y` must have unique names",
+    fixed = TRUE
+  )
+})
+
+test_that("mutate() semantics for tibble() (#213)", {
+  expect_equal(
+    tibble(a = 1:2, b = 1, c = b / sum(b)),
+    tibble(a = 1:2, b = c(1, 1), c = c(0.5, 0.5))
+  )
+
+  expect_equal(
+    tibble(b = 1, a = 1:2, c = b / sum(b)),
+    tibble(b = c(1, 1), a = 1:2, c = c(0.5, 0.5))
+  )
+
+  expect_equal(
+    tibble(b = 1, c = b / sum(b), a = 1:2),
+    tibble(b = c(1, 1), c = c(1, 1), a = 1:2)
+  )
+})
+
+test_that("types preserved when recycling in tibble() (#284)", {
+  expect_equal(
+    tibble(a = 1:2, b = as.difftime(1, units = "hours")),
+    tibble(a = 1:2, b = as.difftime(c(1, 1), units = "hours"))
+  )
+
+  expect_equal(
+    tibble(b = as.difftime(1, units = "hours"), a = 1:2),
+    tibble(b = as.difftime(c(1, 1), units = "hours"), a = 1:2)
   )
 })
diff --git a/tests/testthat/test-enframe.R b/tests/testthat/test-enframe.R
index 77f5e78..1e569f1 100644
--- a/tests/testthat/test-enframe.R
+++ b/tests/testthat/test-enframe.R
@@ -1,5 +1,8 @@
 context("enframe")
 
+
+# enframe -----------------------------------------------------------------
+
 test_that("can convert unnamed vector", {
   expect_identical(enframe(3:1),
                    tibble(name = 1:3, value = 3:1))
@@ -20,3 +23,11 @@ test_that("can use custom names", {
                    tibble(index = seq_along(letters),
                           letter = letters))
 })
+
+
+# deframe -----------------------------------------------------------------
+
+test_that("can deframe two-column data frame", {
+  expect_identical(deframe(tibble(name = letters[1:3], value = 3:1)),
+                   c(a = 3L, b = 2L, c = 1L))
+})
diff --git a/tests/testthat/test-equality.R b/tests/testthat/test-equality.R
deleted file mode 100644
index fa1d481..0000000
--- a/tests/testthat/test-equality.R
+++ /dev/null
@@ -1,92 +0,0 @@
-context("Equality")
-
-test_that("data frames equal to themselves", {
-  expect_true(all.equal(as_tibble(mtcars), as_tibble(mtcars)))
-  expect_true(all.equal(as_tibble(iris), as_tibble(iris)))
-  expect_true(all.equal(as_tibble(df_all), as_tibble(df_all)))
-})
-
-test_that("data frames equal to random permutations of themselves", {
-  scramble <- function(x){
-    x[sample(nrow(x)), sample(ncol(x)), drop = FALSE]
-  }
-
-  expect_equal(as_tibble(mtcars), as_tibble(scramble(mtcars)))
-  expect_equal(as_tibble(iris), as_tibble(scramble(iris)))
-  expect_equal(as_tibble(df_all), as_tibble(scramble(df_all)))
-})
-
-test_that("data frames not equal if missing row", {
-  expect_match(all.equal(as_tibble(mtcars), as_tibble(mtcars)[-1, ]), "Different number of rows")
-  expect_match(all.equal(as_tibble(iris), as_tibble(iris)[-1, ]),     "Different number of rows")
-  expect_match(all.equal(as_tibble(df_all), as_tibble(df_all)[-1, ]), "Different number of rows")
-})
-
-test_that("data frames not equal if missing col", {
-  expect_match(all.equal(as_tibble(mtcars), as_tibble(mtcars)[, -1]), "Cols in x but not y: 'mpg'")
-  expect_match(all.equal(as_tibble(mtcars)[, -1], as_tibble(mtcars)), "Cols in y but not x: 'mpg'")
-  expect_match(all.equal(as_tibble(iris), as_tibble(iris)[, -1]),     "Cols in x but not y: 'Sepal.Length'")
-  expect_match(all.equal(as_tibble(df_all), as_tibble(df_all)[, -1]), "Cols in x but not y: 'a'")
-  expect_match(all.equal(as_tibble(mtcars), rev(as_tibble(mtcars)),
-                         ignore_col_order = FALSE),
-               "Column names same but in different order")
-})
-
-test_that("factors equal only if levels equal", {
-  df1 <- data.frame(x = factor(c("a", "b")))
-  df2 <- data.frame(x = factor(c("a", "d")))
-  expect_match(all.equal(as_tibble(df1), as_tibble(df2)), "Factor levels not equal for column x" )
-})
-
-test_that("all.equal.data.frame handles data.frames with NULL names", {
-  x <- data.frame(LETTERS[1:3], rnorm(3))
-  names(x) <- NULL
-  expect_true(all.equal(x,x))
-})
-
-test_that( "data frame equality test with ignore_row_order=TRUE detects difference in number of rows. #1065", {
-  DF1 <- tibble(a = 1:4, b = letters[1:4])
-  DF2 <- tibble(a = c(1:4,4L), b = letters[c(1:4,4L)])
-  expect_false( isTRUE(all.equal(DF1, DF2, ignore_row_order=TRUE)))
-
-  DF1 <- tibble(a = c(1:4,2L), b = letters[c(1:4,2L)])
-  DF2 <- tibble(a = c(1:4,4L), b = letters[c(1:4,4L)])
-  expect_false(isTRUE(all.equal(DF1, DF2, ignore_row_order=TRUE)))
-
-})
-
-test_that("all.equal handles NA_character_ correctly. #1095", {
-  d1 <- tibble(x = c(NA_character_))
-  expect_true(all.equal(d1, d1))
-
-  d2 <- tibble( x = c(NA_character_, "foo", "bar" ) )
-  expect_true(all.equal(d2, d2))
-})
-
-test_that( "handle Date columns of different types, integer and numeric (#1204)", {
-  a <- data.frame(date = as.Date("2015-06-07"))
-  b <- data.frame(date = structure( as.integer(a$date), class = "Date" ) )
-  expect_true( all.equal(a, b) )
-})
-
-test_that("equality test fails when convert is FALSE and types don't match (#1484)", {
-  df1 <- tibble(x = "a")
-  df2 <- tibble(x = factor("a"))
-
-  expect_equal( all.equal(df1, df2, convert = FALSE), "Incompatible type for column x: x character, y factor" )
-  expect_warning( all.equal(df1, df2, convert = TRUE) )
-})
-
-test_that("equality handles data frames with 0 columns (#1506)", {
-  df0 <- tibble(x = numeric(0), y = character(0) )
-  expect_equal(df0, df0)
-})
-
-test_that("equality fails if types different", {
-  expect_equal(all.equal(as_tibble(iris), iris), "Different types: x 'tbl_df', 'tbl', 'data.frame', y 'data.frame'")
-})
-
-test_that("equality works for data frames with columns named like arguments to order() (#107)", {
-  U <- data_frame(method = c("old","new"), time = c(4.5, 2.3))
-  expect_equal(U, U)
-})
diff --git a/tests/testthat/test-glimpse.R b/tests/testthat/test-glimpse.R
index 1641859..2d567ee 100644
--- a/tests/testthat/test-glimpse.R
+++ b/tests/testthat/test-glimpse.R
@@ -16,13 +16,12 @@ test_that("format_v for character", {
 })
 
 test_that("format_v for list", {
-  expect_equal(format_v(list(1:3)), "<1, 2, 3>")
+  expect_equal(format_v(list(1:3)), "[<1, 2, 3>]")
   expect_equal(format_v(as.list(1:3)), "[1, 2, 3]")
   expect_equal(format_v(list(1:3, 4)), "[<1, 2, 3>, 4]")
   expect_equal(format_v(list(1:3, 4:5)), "[<1, 2, 3>, <4, 5>]")
   expect_equal(format_v(list()), "[]")
 
-  skip("format_v corner cases")
   expect_equal(format_v(list(list())), "[[]]")
   expect_equal(format_v(list(character())), "[<>]")
   expect_equal(format_v(list(1:3, list(4))), "[<1, 2, 3>, [4]]")
@@ -43,6 +42,10 @@ test_that("glimpse output matches known output", {
     "glimpse/iris-empty-70.txt")
 
   expect_output_file_rel(
+    glimpse(tibble("mean(x)" = 5, "var(x)" = 3), width = 28),
+    "glimpse/non-syntactic.txt")
+
+  expect_output_file_rel(
     glimpse(as_tibble(df_all), width = 70L),
     "glimpse/all-70.txt")
 
@@ -64,3 +67,11 @@ test_that("glimpse output matches known output", {
     glimpse(5),
     "glimpse/5.txt")
 })
+
+test_that("glimpse(width = Inf) raises legible error", {
+  expect_error(
+    glimpse(mtcars, width = Inf),
+    "`width` must be finite",
+    fixed = TRUE
+  )
+})
diff --git a/tests/testthat/test-lst.R b/tests/testthat/test-lst.R
index 43a4a77..d0a499a 100644
--- a/tests/testthat/test-lst.R
+++ b/tests/testthat/test-lst.R
@@ -11,3 +11,8 @@ test_that("lst handles internal references", {
   expect_identical(lst(a = 1, b = a), list(a = 1, b = 1))
   expect_identical(lst(a = NULL, b = a), list(a = NULL, b = NULL))
 })
+
+test_that("lst supports duplicate names (#291)", {
+  expect_identical(lst(a = 1, a = a + 1, b = a), list(a = 1, a = 2, b = 2))
+  expect_identical(lst(b = 1, a = b, a = b + 1, b = a), list(b = 1, a = 1, a = 2, b = 2))
+})
diff --git a/tests/testthat/test-matrix.R b/tests/testthat/test-matrix.R
index 5b3a1a7..2d8ca81 100644
--- a/tests/testthat/test-matrix.R
+++ b/tests/testthat/test-matrix.R
@@ -68,3 +68,10 @@ test_that("auto-assigning names", {
   expect_identical(as_tibble(diag(3L)),
                    as_tibble(as.data.frame(diag(3L))))
 })
+
+test_that("forwarding to as.data.frame() for ts objects (#184)", {
+  mts <- cbind(
+    A = ts(c(1, 1, 2, 2),     start = 2016, freq = 4),
+    B = ts(c(11, 11, 12, 13), start = 2016, freq = 4))
+  expect_identical(as_tibble(mts), as_tibble(as.data.frame(mts)))
+})
diff --git a/tests/testthat/test-nibble.R b/tests/testthat/test-nibble.R
deleted file mode 100644
index 225720a..0000000
--- a/tests/testthat/test-nibble.R
+++ /dev/null
@@ -1,106 +0,0 @@
-context("tribble()")
-
-test_that("tribble() constructs 'tibble' as expected", {
-
-  result <- tribble(
-    ~colA, ~colB,
-    "a", 1,
-    "b", 2
-  )
-
-  compared <- tibble(colA = c("a", "b"), colB = c(1, 2))
-  expect_equal(result, compared)
-
-  ## wide
-  wide <- tribble(
-    ~colA, ~colB, ~colC, ~colD,
-    1, 2, 3, 4,
-    5, 6, 7, 8
-  )
-
-  wide_expectation <- tibble(
-    colA = c(1, 5),
-    colB = c(2, 6),
-    colC = c(3, 7),
-    colD = c(4, 8)
-  )
-
-  expect_equal(wide, wide_expectation)
-
-  ## long
-  long <- tribble(
-    ~colA, ~colB,
-    1, 6,
-    2, 7,
-    3, 8,
-    4, 9,
-    5, 10
-  )
-
-  long_expectation <- tibble(
-    colA = as.numeric(1:5),
-    colB = as.numeric(6:10)
-  )
-
-  expect_equal(long, long_expectation)
-
-})
-
-test_that("tribble() creates lists for non-atomic inputs (#7)", {
-  expect_identical(
-    tribble(~a, ~b, NA, "A", letters, LETTERS[-1L]),
-    tibble(a = list(NA, letters), b = list("A", LETTERS[-1L]))
-  )
-
-  expect_identical(
-    tribble(~a, ~b, NA, NULL, 1, 2),
-    tibble(a = c(NA, 1), b = list(NULL, 2))
-  )
-})
-
-test_that("tribble() errs appropriately on bad calls", {
-
-  # invalid colname syntax
-  expect_error(tribble(a~b), "single argument")
-
-  # invalid colname syntax
-  expect_error(tribble(~a + b), "symbol or string")
-
-  # tribble() must be passed colnames
-  expect_error(tribble(
-    "a", "b",
-    1, 2
-  ))
-
-  # tribble() must produce rectangular structure (no filling)
-  expect_error(tribble(
-    ~a, ~b, ~c,
-    1, 2,
-    3, 4, 5
-  ))
-
-})
-
-test_that("tribble can have list columns", {
-  df <- tribble(
-    ~x, ~y,
-    1,  list(a = 1),
-    2,  list(b = 2)
-  )
-  expect_equal(df$x, c(1, 2))
-  expect_equal(df$y, list(list(a = 1), list(b = 2)))
-})
-
-test_that("tribble creates n-col empty data frame", {
-  df <- tribble(~x, ~y)
-  expect_equal(names(df), c("x", "y"))
-})
-
-test_that("tribble recognizes quoted non-formula call", {
-  df <- tribble(
-    ~x, ~y,
-    quote(mean(1)), 1
-  )
-  expect_equal(df$x, list(quote(mean(1))))
-  expect_equal(df$y, 1)
-})
diff --git a/tests/testthat/test-obj-sum.R b/tests/testthat/test-obj-sum.R
index 1cfa6fd..058dd5d 100644
--- a/tests/testthat/test-obj-sum.R
+++ b/tests/testthat/test-obj-sum.R
@@ -17,10 +17,8 @@ test_that("NULL handled specially", {
 })
 
 test_that("data frame and tibbles include rows and cols", {
-  skip_on_os("windows")
-
-  expect_equal(obj_sum(mtcars), "data.frame [32 × 11]")
-  expect_equal(obj_sum(as_tibble(mtcars)), "tibble [32 × 11]")
+  expect_equal(obj_sum(mtcars), "data.frame [32 x 11]")
+  expect_equal(obj_sum(as_tibble(mtcars)), "tibble [32 x 11]")
 })
 
 test_that("common data vectors treated as atomic", {
diff --git a/tests/testthat/test-options.R b/tests/testthat/test-options.R
index 4ab4322..e9942c5 100644
--- a/tests/testthat/test-options.R
+++ b/tests/testthat/test-options.R
@@ -20,3 +20,63 @@ test_that("fallback to default option", {
          dplyr.width = NULL),
     expect_equal(tibble_opt("width"), op.tibble[["tibble.width"]]))
 })
+
+test_that("tibble_width returns user-input width,
+          then tibble.width option, then width option", {
+  test_width <- 42
+
+  expect_equal(tibble_width(test_width), test_width)
+  withr::with_options(
+    list(tibble.width = test_width),
+    expect_equal(tibble_width(NULL), test_width))
+  withr::with_options(
+    list(width = test_width),
+    expect_equal(tibble_width(NULL), test_width))
+})
+
+test_that("tibble_width prefers tibble.width / dplyr.width over width", {
+  withr::with_options(
+    list(tibble.width = 10, width = 20),
+    expect_equal(tibble_width(NULL), 10))
+
+  withr::with_options(
+    list(dplyr.width = 10, width = 20),
+    expect_equal(tibble_width(NULL), 10))
+})
+
+test_that("tibble_glimpse_width returns user-input width,
+          then tibble.width option, then width option", {
+  test_width <- 42
+
+  expect_equal(tibble_glimpse_width(test_width), test_width)
+  withr::with_options(
+    list(tibble.width = test_width),
+    expect_equal(tibble_glimpse_width(NULL), test_width))
+  withr::with_options(
+    list(width = test_width),
+    expect_equal(tibble_glimpse_width(NULL), test_width))
+})
+
+test_that("tibble_glimpse_width prefers tibble.width / dplyr.width over width", {
+  withr::with_options(
+    list(tibble.width = 10, width = 20),
+    expect_equal(tibble_glimpse_width(NULL), 10))
+
+  withr::with_options(
+    list(dplyr.width = 10, width = 20),
+    expect_equal(tibble_glimpse_width(NULL), 10))
+})
+
+test_that("tibble_glimpse_width ignores Inf tibble.width", {
+  withr::with_options(
+    list(tibble.width = Inf, width = 20),
+    expect_equal(tibble_glimpse_width(NULL), 20)
+  )
+})
+
+test_that("print.tbl ignores max.print option", {
+  iris2 <- as_tibble(iris)
+  expect_output(
+    withr::with_options(list(max.print = 3), print(iris2)),
+    capture_output(print(iris2)), fixed = TRUE)
+})
diff --git a/tests/testthat/test-repair_names.R b/tests/testthat/test-repair_names.R
index 49efdd2..6fdc889 100644
--- a/tests/testthat/test-repair_names.R
+++ b/tests/testthat/test-repair_names.R
@@ -27,16 +27,3 @@ test_that("blanks skip existing names", {
 test_that("blanks skip names created when de-duping", {
   expect_equal(make_unique(c("", "V", "V")), c("V2", "V", "V1"))
 })
-
-# names2 ------------------------------------------------------------------
-
-test_that("names2 returns character vector even if names NULL", {
-  expect_equal(names2(1:3), rep("", 3))
-})
-
-test_that("names2 replaces missing value with blanks", {
-  x <- 1:3
-  names(x) <- c("a", "b", NA)
-
-  expect_equal(names2(x), c("a", "b", ""))
-})
diff --git a/tests/testthat/test-rownames.R b/tests/testthat/test-rownames.R
index 95c5534..7cc382c 100644
--- a/tests/testthat/test-rownames.R
+++ b/tests/testthat/test-rownames.R
@@ -14,16 +14,47 @@ test_that("setting row names on a tibble raises a warning", {
 })
 
 test_that("rownames_to_column keeps the tbl classes (#882)", {
-  res <- rownames_to_column( mtcars, "Make&Model" )
+  res <- rownames_to_column(mtcars)
   expect_false(has_rownames(res))
-  expect_equal( class(res), class(mtcars) )
-  expect_error(rownames_to_column( mtcars, "wt"),
-               paste("There is a column named wt already!")  )
-  res1 <- rownames_to_column( as_tibble(mtcars), "Make&Model" )
+  expect_equal(class(res), class(mtcars))
+  expect_equal(res$rowname, rownames(mtcars))
+  expect_error(
+    rownames_to_column(mtcars, "wt"),
+    "Column `wt` already exists",
+    fixed = TRUE
+  )
+
+  res1 <- rownames_to_column(as_tibble(mtcars), "Make&Model")
+  expect_false(has_rownames(res1))
+  expect_equal(class(res1), class(as_tibble(mtcars)))
+  expect_equal(res1$`Make&Model`, rownames(mtcars))
+  expect_error(
+    rownames_to_column(as_tibble(mtcars), "wt"),
+    "Column `wt` already exists",
+    fixed = TRUE
+  )
+})
+
+test_that("rowid_to_column keeps the tbl classes", {
+  res <- rowid_to_column(mtcars)
+  expect_false(has_rownames(res))
+  expect_equal(class(res), class(mtcars))
+  expect_equal(res$rowid, seq_len(nrow(mtcars)))
+  expect_error(
+    rowid_to_column(mtcars, "wt"),
+    "Column `wt` already exists",
+    fixed = TRUE
+  )
+
+  res1 <- rowid_to_column(as_tibble(mtcars), "row_id")
   expect_false(has_rownames(res1))
-  expect_equal( class(res1), class(as_tibble(mtcars)) )
-  expect_error(rownames_to_column( mtcars, "wt"),
-               paste("There is a column named wt already!")  )
+  expect_equal(class(res1), class(as_tibble(mtcars)))
+  expect_equal(res1$row_id, seq_len(nrow(mtcars)))
+  expect_error(
+    rowid_to_column(as_tibble(mtcars), "wt"),
+    "Column `wt` already exists",
+    fixed = TRUE
+  )
 })
 
 test_that("column_to_rownames returns tbl", {
@@ -43,9 +74,16 @@ test_that("column_to_rownames returns tbl", {
   expect_warning(res <- column_to_rownames(res0, var = "num"))
   expect_true(has_rownames(res))
   expect_equal(rownames(res), as.character(mtcars1$num))
-  expect_error(column_to_rownames(res), "This data frame already has row names.")
-  expect_error(column_to_rownames(rownames_to_column(mtcars1, var), "num2"),
-               paste("This data frame has no column named num2."))
+  expect_error(
+    column_to_rownames(res),
+    "`df` already has row names",
+    fixed = TRUE
+  )
+  expect_error(
+    column_to_rownames(rownames_to_column(mtcars1, var), "num2"),
+    "Column `num2` not found",
+    fixed = TRUE
+  )
 })
 
 test_that("converting to data frame does not add row names", {
diff --git a/tests/testthat/test-tbl-df.R b/tests/testthat/test-tbl-df.R
index 39c8542..5189f8a 100644
--- a/tests/testthat/test-tbl-df.R
+++ b/tests/testthat/test-tbl-df.R
@@ -36,30 +36,88 @@ test_that("[ with 0 cols creates correct row names (#656)", {
   expect_identical(zero_row, as_tibble(iris)[0])
 })
 
+test_that("[ with 0 cols returns correct number of rows", {
+  iris_tbl <- as_tibble(iris)
+  nrow_iris <- nrow(iris_tbl)
+
+  expect_equal(nrow(iris_tbl[0]), nrow_iris)
+  expect_equal(nrow(iris_tbl[, 0]), nrow_iris)
+
+  expect_equal(nrow(iris_tbl[, 0][1:10, ]), 10)
+  expect_equal(nrow(iris_tbl[0][1:10, ]), 10)
+  expect_equal(nrow(iris_tbl[1:10, ][, 0]), 10)
+  expect_equal(nrow(iris_tbl[1:10, ][0]), 10)
+  expect_equal(nrow(iris_tbl[1:10, 0]), 10)
+
+  expect_equal(nrow(iris_tbl[, 0][-(1:10), ]), nrow_iris - 10)
+  expect_equal(nrow(iris_tbl[0][-(1:10), ]), nrow_iris - 10)
+  expect_equal(nrow(iris_tbl[-(1:10), ][, 0]), nrow_iris - 10)
+  expect_equal(nrow(iris_tbl[-(1:10), ][0]), nrow_iris - 10)
+  expect_equal(nrow(iris_tbl[-(1:10), 0]), nrow_iris - 10)
+})
+
 test_that("[.tbl_df is careful about names (#1245)",{
+  z_msg <- "Column `z` not found"
+
   foo <- tibble(x = 1:10, y = 1:10)
-  expect_error(foo["z"], "Unknown columns 'z'", fixed = TRUE)
-  expect_error(foo[ c("x", "y", "z") ], "Unknown columns 'z'", fixed = TRUE)
+  expect_error(foo["z"], z_msg, fixed = TRUE)
+  expect_error(foo[ c("x", "y", "z") ], z_msg, fixed = TRUE)
 
-  expect_error(foo[, "z"], "Unknown columns 'z'", fixed = TRUE)
-  expect_error(foo[, c("x", "y", "z") ], "Unknown columns 'z'", fixed = TRUE)
+  expect_error(foo[, "z"], z_msg, fixed = TRUE)
+  expect_error(foo[, c("x", "y", "z") ], z_msg, fixed = TRUE)
 
-  expect_error(foo[as.matrix("x")], "matrix")
-  expect_error(foo[array("x", dim = c(1, 1, 1))], "array")
+  expect_error(
+    foo[as.matrix("x")],
+    "Can't use matrix or array for column indexing",
+    fixed = TRUE
+  )
+  expect_error(
+    foo[array("x", dim = c(1, 1, 1))],
+    "Can't use matrix or array for column indexing",
+    fixed = TRUE
+  )
 })
 
 test_that("[.tbl_df is careful about column indexes (#83)",{
   foo <- tibble(x = 1:10, y = 1:10, z = 1:10)
   expect_identical(foo[1:3], foo)
-  expect_error(foo[0.5], "Invalid non-integer column indexes: 0.5", fixed = TRUE)
-  expect_error(foo[1:5], "Invalid column indexes: 4, 5", fixed = TRUE)
-  expect_error(foo[-1:1], "mixed with negative")
-  expect_error(foo[c(-1, 1)], "mixed with negative")
-  expect_error(foo[-4], "Invalid negative column indexes: -4", fixed = TRUE)
-  expect_error(foo[c(1:3, NA)], "NA column indexes not supported", fixed = TRUE)
 
-  expect_error(foo[as.matrix(1)], "matrix")
-  expect_error(foo[array(1, dim = c(1, 1, 1))], "array")
+  expect_error(
+    foo[0.5],
+    "Column index must be integer, not 0.5",
+    fixed = TRUE
+  )
+  expect_error(
+    foo[1:5],
+    "Column indexes must be at most 3 if positive, not 4, 5",
+    fixed = TRUE
+  )
+
+  # Message from base R
+  expect_error(foo[-1:1])
+  expect_error(foo[c(-1, 1)])
+
+  expect_error(
+    foo[-4],
+    "Column index must be at least -3 if negative, not -4",
+    fixed = TRUE
+  )
+  expect_error(
+    foo[c(1:3, NA)],
+    "NA column indexes not supported",
+    fixed = TRUE
+  )
+
+  expect_error(
+    foo[as.matrix(1)],
+    "Can't use matrix or array for column indexing",
+    fixed = TRUE
+  )
+  expect_error(
+    foo[array(1, dim = c(1, 1, 1))],
+    "Can't use matrix or array for column indexing",
+    fixed = TRUE
+  )
 })
 
 test_that("[.tbl_df is careful about column flags (#83)",{
@@ -69,21 +127,61 @@ test_that("[.tbl_df is careful about column flags (#83)",{
   expect_identical(foo[FALSE], foo[integer()])
   expect_identical(foo[c(FALSE, TRUE, FALSE)], foo[2])
 
-  expect_error(foo[c(TRUE, TRUE)], "Length of logical index vector must be 1 or 3, got: 2", fixed = TRUE)
-  expect_error(foo[c(TRUE, TRUE, FALSE, FALSE)], "Length of logical index vector must be 1 or 3, got: 4", fixed = TRUE)
-  expect_error(foo[c(TRUE, TRUE, NA)], "NA column indexes not supported", fixed = TRUE)
+  expect_error(
+    foo[c(TRUE, TRUE)],
+    "Length of logical index vector must be 1 or 3 (the number of rows), not 2",
+    fixed = TRUE
+  )
+  expect_error(
+    foo[c(TRUE, TRUE, FALSE, FALSE)],
+    "Length of logical index vector must be 1 or 3 (the number of rows), not 4",
+    fixed = TRUE
+  )
+  expect_error(
+    foo[c(TRUE, TRUE, NA)],
+    "NA column indexes not supported",
+    fixed = TRUE
+  )
 
-  expect_error(foo[as.matrix(TRUE)], "matrix")
-  expect_error(foo[array(TRUE, dim = c(1, 1, 1))], "array")
+  expect_error(
+    foo[as.matrix(TRUE)],
+    "Can't use matrix or array for column indexing",
+    fixed = TRUE
+  )
+  expect_error(
+    foo[array(TRUE, dim = c(1, 1, 1))],
+    "Can't use matrix or array for column indexing",
+    fixed = TRUE
+  )
 })
 
 test_that("[.tbl_df rejects unknown column indexes (#83)",{
   foo <- tibble(x = 1:10, y = 1:10, z = 1:10)
-  expect_error(foo[list(1:3)], "Unsupported index type: list", fixed = TRUE)
-  expect_error(foo[as.list(1:3)], "Unsupported index type: list", fixed = TRUE)
-  expect_error(foo[factor(1:3)], "Unsupported index type: factor", fixed = TRUE)
-  expect_error(foo[Sys.Date()], "Unsupported index type: Date", fixed = TRUE)
-  expect_error(foo[Sys.time()], "Unsupported index type: POSIXct", fixed = TRUE)
+  expect_error(
+    foo[list(1:3)],
+    "Unsupported index type: list",
+    fixed = TRUE
+  )
+  expect_error(
+    foo[as.list(1:3)],
+    "Unsupported index type: list",
+    fixed = TRUE
+  )
+  expect_error(
+    foo[factor(1:3)],
+    "Unsupported index type: factor",
+    fixed = TRUE
+  )
+  expect_error(
+    foo[Sys.Date()],
+    "Unsupported index type: Date",
+    fixed = TRUE
+  )
+  expect_error(
+    foo[Sys.time()],
+    "Unsupported index type: POSIXct",
+    fixed = TRUE
+  )
 })
 
 test_that("[.tbl_df is no-op if args missing",{
@@ -127,15 +225,20 @@ test_that("can use two-dimensional indexing with [[", {
 test_that("$ throws warning if name doesn't exist", {
   df <- tibble(x = 1)
   expect_warning(expect_null(df$y),
-                 "Unknown column 'y'")
+                 "Unknown or uninitialised column: 'y'")
+})
+
+test_that("$ throws different warning if attempting a partial initialization (#199)", {
+  df <- tibble(x = 1)
+  expect_warning(df$y[1] <- 2, "Unknown or uninitialised column: 'y'")
 })
 
 test_that("$ doesn't do partial matching", {
   df <- tibble(partial = 1)
   expect_warning(expect_null(df$p),
-                 "Unknown column 'p'")
+                 "Unknown or uninitialised column: 'p'")
   expect_warning(expect_null(df$part),
-                 "Unknown column 'part'")
+                 "Unknown or uninitialised column: 'part'")
   expect_error(df$partial, NA)
 })
 
diff --git a/tests/testthat/test-tidy_names.R b/tests/testthat/test-tidy_names.R
new file mode 100644
index 0000000..360b130
--- /dev/null
+++ b/tests/testthat/test-tidy_names.R
@@ -0,0 +1,86 @@
+context("tidy_names")
+
+test_that("zero-length inputs given character names", {
+  out <- set_tidy_names(character())
+  expect_equal(names(out), character())
+})
+
+test_that("unnamed input gives uniquely named output", {
+  out <- set_tidy_names(1:3)
+  expect_equal(names(out), c("..1", "..2", "..3"))
+})
+
+test_that("messages by default", {
+  expect_message(
+    set_tidy_names(set_names(1, "")),
+    "New names:\n -> ..1\n",
+    fixed = TRUE
+  )
+})
+
+test_that("quiet = TRUE", {
+  expect_message(set_tidy_names(set_names(1, ""), quiet = TRUE), NA)
+})
+
+test_that("syntactic = TRUE", {
+  out <- set_tidy_names(set_names(1, "a b"))
+  expect_equal(names(out), tidy_names("a b"))
+})
+
+# tidy_names ---------------------------------------------------------------
+
+test_that("zero-length input", {
+  expect_equal(tidy_names(character()), character())
+})
+
+test_that("proper names", {
+  expect_equal(tidy_names(letters), letters)
+})
+
+test_that("dupes", {
+  expect_equal(
+    tidy_names(c("a", "b", "a", "c", "b")),
+    c("a..1", "b..2", "a..3", "c", "b..5")
+  )
+})
+
+test_that("empty", {
+  expect_equal(tidy_names(""), "..1")
+})
+
+test_that("NA", {
+  expect_equal(tidy_names(NA_character_), "..1")
+})
+
+test_that("corner case", {
+  expect_equal(tidy_names(c("a..2", "a")), c("a..2", "a"))
+  expect_equal(tidy_names(c("a..3", "a", "a")), c("a..1", "a..2", "a..3"))
+  expect_equal(tidy_names(c("a..2", "a", "a")), c("a..1", "a..2", "a..3"))
+  expect_equal(tidy_names(c("a..2", "a..2", "a..2")), c("a..1", "a..2", "a..3"))
+})
+
+test_that("syntactic", {
+  expect_equal(tidy_names("a b", syntactic = TRUE), make.names("a b"))
+})
+
+test_that("some syntactic + message (#260)", {
+  expect_equal(
+    tidy_names(c("a b", "c"), syntactic = TRUE),
+    c(make.names("a b"), "c")
+  )
+})
+
+test_that("message", {
+  expect_message(
+    tidy_names(""),
+    "New names:\n -> ..1\n",
+    fixed = TRUE
+  )
+})
+
+test_that("quiet", {
+  expect_message(
+    tidy_names("", quiet = TRUE),
+    NA
+  )
+})
diff --git a/tests/testthat/test-tribble.R b/tests/testthat/test-tribble.R
new file mode 100644
index 0000000..7f471d2
--- /dev/null
+++ b/tests/testthat/test-tribble.R
@@ -0,0 +1,176 @@
+context("tribble()")
+
+test_that("tribble() constructs 'tibble' as expected", {
+
+  result <- tribble(
+    ~colA, ~colB,
+    "a", 1,
+    "b", 2
+  )
+
+  compared <- tibble(colA = c("a", "b"), colB = c(1, 2))
+  expect_equal(result, compared)
+
+  ## wide
+  wide <- tribble(
+    ~colA, ~colB, ~colC, ~colD,
+    1, 2, 3, 4,
+    5, 6, 7, 8
+  )
+
+  wide_expectation <- tibble(
+    colA = c(1, 5),
+    colB = c(2, 6),
+    colC = c(3, 7),
+    colD = c(4, 8)
+  )
+
+  expect_equal(wide, wide_expectation)
+
+  ## long
+  long <- tribble(
+    ~colA, ~colB,
+    1, 6,
+    2, 7,
+    3, 8,
+    4, 9,
+    5, 10
+  )
+
+  long_expectation <- tibble(
+    colA = as.numeric(1:5),
+    colB = as.numeric(6:10)
+  )
+
+  expect_equal(long, long_expectation)
+
+})
+
+test_that("tribble() handles columns with a class (#161)", {
+  sys_date <- Sys.Date()
+  sys_time <- Sys.time()
+  date_time_col <- tribble(
+    ~dt, ~dttm,
+    sys_date, sys_time,
+    as.Date("2003-01-02"), as.POSIXct("2004-04-05 13:45:17", tz = "UTC")
+  )
+
+  date_time_col_expectation <- tibble(
+    dt = c(sys_date, as.Date("2003-01-02")),
+    dttm = c(sys_time, as.POSIXct("2004-04-05 13:45:17", tz = "UTC"))
+  )
+
+  expect_equal(date_time_col, date_time_col_expectation)
+})
+
+test_that("tribble() creates lists for non-atomic inputs (#7)", {
+  expect_identical(
+    tribble(~a, ~b, NA, "A", letters, LETTERS[-1L]),
+    tibble(a = list(NA, letters), b = list("A", LETTERS[-1L]))
+  )
+
+  expect_identical(
+    tribble(~a, ~b, NA, NULL, 1, 2),
+    tibble(a = c(NA, 1), b = list(NULL, 2))
+  )
+})
+
+test_that("tribble() errs appropriately on bad calls", {
+
+  # invalid colname syntax
+  expect_error(
+    tribble(a~b),
+    "Expected a column name with a single argument; e.g. `~name`",
+    fixed = TRUE
+  )
+
+  # invalid colname syntax
+  expect_error(
+    tribble(~a + b),
+    "Expected a symbol or string denoting a column name, not a call",
+    fixed = TRUE
+  )
+
+  # tribble() must be passed colnames
+  expect_error(
+    tribble(
+      "a", "b",
+      1, 2
+    ),
+    fixed = TRUE
+  )
+
+  # tribble() must produce rectangular structure (no filling)
+  expect_error(
+    tribble(
+      ~a, ~b, ~c,
+      1, 2,
+      3, 4, 5
+    ),
+    fixed = TRUE
+  )
+
+})
+
+test_that("tribble can have list columns", {
+  df <- tribble(
+    ~x, ~y,
+    1,  list(a = 1),
+    2,  list(b = 2)
+  )
+  expect_equal(df$x, c(1, 2))
+  expect_equal(df$y, list(list(a = 1), list(b = 2)))
+})
+
+test_that("tribble creates n-col empty data frame", {
+  df <- tribble(~x, ~y)
+  expect_equal(df, tibble(x = logical(), y = logical()))
+})
+
+test_that("tribble recognizes quoted non-formula call", {
+  df <- tribble(
+    ~x, ~y,
+    quote(mean(1)), 1
+  )
+  expect_equal(df$x, list(quote(mean(1))))
+  expect_equal(df$y, 1)
+})
+
+test_that("tribble returns 0x0 tibble when there's no argument", {
+  df <- tribble()
+  expect_equal(df, tibble())
+})
+
+# ---- frame_matrix() ----
+
+test_that("frame_matrix constructs a matrix as expected", {
+  result <- frame_matrix(
+    ~col1, ~col2,
+    10,     3,
+    5,     2
+  )
+  expected <- matrix(c(10, 5, 3, 2), ncol = 2)
+  colnames(expected) <- c("col1", "col2")
+  expect_equal(result, expected)
+})
+
+test_that("frame_matrix constructs empty matrix as expected", {
+  result <- frame_matrix(
+    ~col1, ~col2
+  )
+  expected <- matrix(logical(), ncol = 2)
+  colnames(expected) <- c("col1", "col2")
+  expect_equal(result, expected)
+})
+
+test_that("frame_matrix cannot have list columns", {
+  expect_error(
+    frame_matrix(
+      ~x,  ~y,
+      "a", 1:3,
+      "b", 4:6
+    ),
+    "Can't use list columns in `frame_matrix()`",
+    fixed = TRUE
+  )
+})
diff --git a/tests/testthat/test-trunc-mat.R b/tests/testthat/test-trunc-mat.R
index 5e972a3..9973643 100644
--- a/tests/testthat/test-trunc-mat.R
+++ b/tests/testthat/test-trunc-mat.R
@@ -1,7 +1,7 @@
 context("Truncated matrix")
 
 test_that("interface of print() identical to trunc_mat()", {
-  print_arg_names <- names(formals(print.tbl_df))
+  print_arg_names <- names(formals(print.tbl))
   print_arg_names_without_ellipsis <- setdiff(print_arg_names, "...")
 
   trunc_mat_arg_names <- names(formals(trunc_mat))
@@ -79,6 +79,14 @@ test_that("trunc_mat output matches known output", {
     print(trunc_mat(df_all, n = 1L, n_extra = 0L, width = 30L)),
     "trunc_mat/all-1-30-0.txt")
 
+  expect_output_file_rel(
+    print(trunc_mat(tibble(`\n` = c("\n", '"'), `\r` = factor(`\n`)))),
+    "trunc_mat/newline.txt")
+
+  expect_output_file_rel(
+    print(trunc_mat(tibble("mean(x)" = 5, "var(x)" = 3), width = 28)),
+    "trunc_mat/non-syntactic.txt")
+
   expect_output_knit(
     knitr::knit_print(trunc_mat(df_all, width = 60L)),
     "trunc_mat/all-knit-60.txt")
@@ -104,9 +112,9 @@ test_that("trunc_mat for POSIXlt columns (#86)", {
 })
 
 test_that("trunc_mat for wide-character columns (#100)", {
-  skip_on_os("windows")
+  skip_on_os("windows") # capture_output_lines() forces native encoding
 
-  x <- c("成交日期", "合同录入日期")
+  x <- c("\u6210\u4ea4\u65e5\u671f", "\u5408\u540c\u5f55\u5165\u65e5\u671f")
   df <- setNames(tibble(1:3, 4:6), x)
 
   expect_output_file_rel(
@@ -114,6 +122,19 @@ test_that("trunc_mat for wide-character columns (#100)", {
     "trunc_mat/wide-8-60.txt")
 })
 
+test_that("trunc_mat for wide-character columns in non-UTF-8 locale", {
+  skip_on_os("windows") # capture_output_lines() forces native encoding
+
+  with_non_utf8_locale({
+    x <- c("\u6210\u4ea4\u65e5\u671f", "\u5408\u540c\u5f55\u5165\u65e5\u671f")
+    df <- setNames(tibble(1:3, 4:6), x)
+
+    expect_output_file_rel(
+      print(df, n = 8L, width = 60L),
+      "trunc_mat/wide-8-60.txt")
+  })
+})
+
 test_that("trunc_mat backticks non-syntactic names", {
   tb <- tibble(
     `:)` = "smile",
@@ -123,3 +144,16 @@ test_that("trunc_mat backticks non-syntactic names", {
   expect_equal(names(narrow$table), "`:)`")
   expect_equal(names(narrow$extra), "` `")
 })
+
+test_that("trunc_mat backticks NA names", {
+  tb <- tibble(
+    x = 1,
+    y = 2,
+    z = 3
+  )
+  ## there must be at least 2 NA names
+  colnames(tb)[1:2] <- NA
+  narrow <- trunc_mat(tb, width = 5)
+  expect_equal(names(narrow$table), "`NA`")
+  expect_equal(names(narrow$extra), c("`NA`", "z"))
+})
diff --git a/tests/testthat/test-type_sum.R b/tests/testthat/test-type_sum.R
new file mode 100644
index 0000000..0f68f96
--- /dev/null
+++ b/tests/testthat/test-type_sum.R
@@ -0,0 +1,7 @@
+context("type_sum")
+
+test_that("works with glimpse", {
+  foo <- as_override_type_sum(2011:2013)
+  expect_equal(type_sum(foo), "SC")
+  expect_output(glimpse(tibble(foo)), "foo <SC>")
+})
diff --git a/tests/testthat/test-utils-format.R b/tests/testthat/test-utils-format.R
new file mode 100644
index 0000000..df18fa8
--- /dev/null
+++ b/tests/testthat/test-utils-format.R
@@ -0,0 +1,20 @@
+context("utils-format")
+
+test_that("pluralise works correctly", {
+  expect_identical(pluralise("[an] index(es)", c("x")), "an index")
+  expect_identical(pluralise("[an] index(es)", c("x", "y")), "indexes")
+})
+
+test_that("pluralise leaves alone parentheses / square brackets that have spaces inside", {
+  expect_identical(pluralise("[an] invalid index(es) (be careful) [for real]", c("x")),
+                   "an invalid index (be careful) [for real]")
+  expect_identical(pluralise("[an] invalid index(es) (be careful) [for real]", c("x", "y")),
+                   "invalid indexes (be careful) [for real]")
+})
+
+test_that("pluralise_msg works correctly", {
+  expect_identical(pluralise_msg("[an] index(es) ", c("x")), "an index `x`")
+  expect_identical(pluralise_msg("[an] index(es) ", c("x", "y")), "indexes `x`, `y`")
+  expect_identical(pluralise_msg("[an] index(es) ", c(-4, -5)), "indexes -4, -5")
+})
+
diff --git a/vignettes/formatting.Rmd b/vignettes/extending.Rmd
similarity index 91%
rename from vignettes/formatting.Rmd
rename to vignettes/extending.Rmd
index d74bc9c..ff89d7c 100644
--- a/vignettes/formatting.Rmd
+++ b/vignettes/extending.Rmd
@@ -1,10 +1,8 @@
 ---
-title: "Formatting of column data"
-author: "Kirill Müller, Hadley Wickham"
-date: "`r Sys.Date()`"
+title: "Extending tibble"
 output: rmarkdown::html_vignette
 vignette: >
-  %\VignetteIndexEntry{Formatting of column data}
+  %\VignetteIndexEntry{Extending tibble}
   %\VignetteEngine{knitr::rmarkdown}
   %\VignetteEncoding{UTF-8}
 ---
@@ -15,7 +13,7 @@ options(tibble.print_min = 4L, tibble.print_max = 4L)
 library(tibble)
 ```
 
-The presentation of a column in a tibble is powered by two S3 generics:
+To extend the tibble package for new types of columnar data, you need to understand how printing works. The presentation of a column in a tibble is powered by two S3 generics:
 
 * `type_sum()` determines what goes into the column header.
 * `obj_sum()` is used when rendering list columns.
diff --git a/vignettes/tibble.Rmd b/vignettes/tibble.Rmd
index cfadfc5..14f890d 100644
--- a/vignettes/tibble.Rmd
+++ b/vignettes/tibble.Rmd
@@ -1,6 +1,5 @@
 ---
 title: "Tibbles"
-date: "`r Sys.Date()`"
 output: rmarkdown::html_vignette
 vignette: >
   %\VignetteIndexEntry{Tibbles}
@@ -12,6 +11,7 @@ vignette: >
 knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
 options(tibble.print_min = 4L, tibble.print_max = 4L)
 library(tibble)
+set.seed(1014)
 ```
 
 Tibbles are a modern take on data frames. They keep the features that have stood the test of time, and drop the features that used to be convenient but are now frustrating (i.e. converting character vectors to factors).
@@ -62,20 +62,22 @@ To complement `tibble()`, tibble provides `as_tibble()` to coerce objects into t
 `as_tibble()` has been written with an eye for performance:
 
 ```{r}
-l <- replicate(26, sample(100), simplify = FALSE)
-names(l) <- letters
-
-microbenchmark::microbenchmark(
-  as_tibble(l),
-  as.data.frame(l)
-)
+if (requireNamespace("microbenchmark", quiet = TRUE)) {
+  l <- replicate(26, sample(100), simplify = FALSE)
+  names(l) <- letters
+
+  microbenchmark::microbenchmark(
+    as_tibble(l),
+    as.data.frame(l)
+  )
+}
 ```
 
 The speed of `as.data.frame()` is not usually a bottleneck when used interactively, but can be a problem when combining thousands of messy inputs into one tidy data frame.
 
 ## Tibbles vs data frames
 
-There are two key differences between tibbles and data frames: printing and subsetting.
+There are three key differences between tibbles and data frames: printing, subsetting, and recycling rules.
 
 ### Printing
 
@@ -125,4 +127,21 @@ df2 <- tibble(abc = 1)
 df2$a
 ```
 
-Also, tibbles ignore the `drop` argument.
+tibbles also ignore the `drop` argument:
+
+```{r}
+tibble(a = 1:3)[, "a", drop = TRUE]
+```
+
+
+### Recycling
+
+When constructing a tibble, only values of length 1 are recycled.  The first column with length different to one determines the number of rows in the tibble, conflicts lead to an error.  This also extends to tibbles with *zero* rows, which is sometimes important for programming:
+
+```{r, error = TRUE}
+tibble(a = 1, b = 1:3)
+tibble(a = 1:3, b = 1)
+tibble(a = 1:3, c = 1:2)
+tibble(a = 1, b = integer())
+tibble(a = integer(), b = 1)
+```

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/r-cran-tibble.git



More information about the debian-med-commit mailing list