From f8e76efa3e8e79bd4d1476539d1582f894c584f7 Mon Sep 17 00:00:00 2001
From: Marin Tomic <marin.tomic@memgraph.io>
Date: Thu, 17 Jan 2019 16:44:55 +0100
Subject: [PATCH] Implement cloning in LCP

Summary:
Add automatic generating of cloning (deep-copy) functions for LCP
defined classes. This enables us to remove a bunch of manually written `Clone`
functions from AST and also to implement logical plan cloning properly (before
it was using serialization).

Reviewers: teon.banek, llugovic

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1808
---
 docs/dev/lcp.md         | 235 +++++++++++++++++++++++++
 src/lisp/CMakeLists.txt |   2 +
 src/lisp/clone.lisp     | 255 +++++++++++++++++++++++++++
 src/lisp/lcp-test.lisp  | 376 ++++++++++++++++++++++++++++++++++++++++
 src/lisp/lcp.asd        |   1 +
 src/lisp/lcp.lisp       |  11 +-
 src/lisp/package.lisp   |   5 +
 src/lisp/slk.lisp       |  16 +-
 src/lisp/types.lisp     |  27 ++-
 9 files changed, 909 insertions(+), 19 deletions(-)
 create mode 100644 src/lisp/clone.lisp

diff --git a/docs/dev/lcp.md b/docs/dev/lcp.md
index 6c3021782..83180b428 100644
--- a/docs/dev/lcp.md
+++ b/docs/dev/lcp.md
@@ -23,6 +23,7 @@ Contents
     - [Defining an RPC](#defining-an-rpc)
     - [Cap'n Proto Serialization](#capnp-serial)
     - [SaveLoadKit Serialization](#slk-serial)
+    - [Object Cloning](#object-cloning)
 
 ## Running LCP
 
@@ -1014,3 +1015,237 @@ example:
   (:serialize (:slk)))
 ```
 
+### Object Cloning
+
+LCP supports automatic generation of cloning (deep copy) code for user-defined
+classes.
+
+A textbook example of an object that would require a deep copy functionality is
+a tree structure. The following class represents a node in the binary tree,
+carrying an integer value and having pointers to its two children:
+
+```lisp
+(lcp:define-class node ()
+  ((value :int32_t)
+   (left "std::unique_ptr<Node>")
+   (right "std::unique_ptr<Node>"))
+  (:clone :return-type (lambda (typename)
+                         #>cpp
+                         std::unique_ptr<${typename}>
+                         cpp<#)
+          :init-object (lambda (var typename)
+                         #>cpp
+                         auto ${var} = std::make_unique<${typename}>();
+                         cpp<#)))
+```
+
+The above will generate the following C++ class with a `Clone` function that
+can be used for making a deep copy of the binary tree structure:
+
+```cpp
+class Node {
+ public:
+  std::unique_ptr<Node> Clone() const {
+    auto object = std::make_unique<Node>();
+    object->value_ = value_;
+    object->left_ = left_ ? left_->Clone() : nullptr;
+    object->right_ = right_ ? right_->Clone() : nullptr;
+    return object;
+  }
+
+ private:
+  int32_t value_;
+  std::unique_ptr<Node> left_;
+  std::unique_ptr<Node> right_;
+};
+```
+
+To specify that a class is deep copyable, `:clone` class option must be passed.
+We have already seen two options that `:clone` accepts: `:return-type` and
+`:init-object`.
+
+`:return-type` expects a function that takes a single argument which is the C++
+type name of the class and produces C++ code, which is a valid C++ type
+delcaration. Here we used it to specify that `Clone` function should return a
+`std::unique_ptr` to the newly created `Node` to override the default behavior.
+When `:return-type` option is not provided and class `T` is a member of an
+inheritance hierarchy, `Clone` will return `std::unique_ptr<Base>`, where
+`Base` is the root of that hierarchy. If `T` is not a member of inheritance
+hierarchy, `Clone` will return `T` by default.
+
+`:init-object` expects a function that takes two arguments, first is a variable
+name, and the second one is the C++ type name of the class. It must produce C++
+code that initializes an object with the given name of the same type that
+`Clone` function returns.  Here we had to use it since we are overriding the
+default return value of `Clone`. Unless `:init-object` argument is provided, an
+object of type `T` will be instantiated with `auto object =
+std::make_unique<T>();` if `T` is a member of inheritance hierarchy, and `T
+object;` if it is not. As you can see, deep copyable objects must be default
+constructible.
+
+
+#### Single Inheritance
+
+LCP supports deep copying of classes with single inheritance. The root class
+will have a virtual `Clone` function that child classes will override. For
+example:
+
+```lisp
+(lcp:define-class base ()
+  ((member :int32_t))
+  (:clone))
+
+(lcp:define-class derived (base)
+  ((another-member :int32_t))
+  (:clone))
+```
+
+We get the following code:
+
+```cpp
+class Base {
+ public:
+  virtual std::unique_ptr<Base> Clone() const {
+    auto object = std::make_unique<Base>();
+    object->member_ = member_;
+    return object;
+  }
+
+ private:
+  int32_t member_;
+};
+
+class Derived : public Base {
+ public:
+  std::unique_ptr<Base> Clone() const override {
+    auto object = std::make_unique<Derived>();
+    object->member_ = member_;
+    object->another_member_ = another_member_;
+    return object;
+  }
+
+ private:
+  int32_t another_member_;
+};
+```
+
+Notice that the `Clone` function of derived class also returns
+`std::unique_ptr<Base>`, because C++ doesn't support return type covariance
+with smart pointers.
+
+#### Multiple Inheritance
+
+Deep copying of classes with multiple inheritance is *not* supported!
+
+Usually, multiple inheritance is used to satisfy some interface which doesn't
+carry data. In such cases, you can ignore the multiple inheritance by
+specifying `:ignore-other-base-classes` option. For example:
+
+```lisp
+(lcp:define-class derived (primary-base some-interface ...)
+  ...
+  (:clone :ignore-other-base-classes t))
+```
+
+The above will produce deep copying code as if `derived` is inheriting *only*
+from `primary-base`.
+
+#### Templated Types
+
+Deep copying of templated types is *not* supported!
+
+#### Custom Clone Hooks
+
+In cases when default deep copying code is not adequate, you may wish to
+provide your own. LCP provides `:clone` option that can be specified for each
+member.
+
+These hooks for custom copying expect a function with two arguments, `source`
+and `dest`, representing the member location in the cloned struct and member
+location in the new struct. This allows to have a more generic function which
+works with any member of some type. The return value of the function needs to
+be C++ code.
+
+It is also possible to specify that a member is cloned by copying by passing
+`:copy` instead of a function as an argument to `:clone`.
+
+```lisp
+(lcp:define-class my-class ()
+  ((callback "std::function<void(int, int)>"
+             :clone :copy)
+   (widget "Widget"
+           :clone (lambda (source dest)
+                   #>cpp
+                   ${dest} = WidgetFactory::Create(${source}.type());
+                   cpp<#)))
+  (:clone))
+```
+
+#### Additional Arguments to Generated Clone Function
+
+By default, `Clone` function takes no argument. In some cases we would like to
+accept additional arguments necessary to create a deep copy. Let's see how this
+is done in LCP using the `:args` option.
+
+`:args` expects a list of pairs. Each pair designates one argument. The first
+element of pair is the argument name and the second is the C++ type of that
+argument.
+
+One case where we want to pass additional arguments to `Clone` is when there is
+another object that owns all objects being cloned. For example, `AstStorage`
+owns all Memgraph AST nodes. For that reason, `Clone` function of all AST node
+types takes an `AstStorage \*` argument. Here's a snippet from the actual AST
+code:
+
+```lisp
+(lcp:define-class tree ()
+  ((uid :int32_t))
+  (:abstractp t)
+  ...
+  (:clone :return-type (lambda (typename)
+                         (format nil "~A*" typename))
+          :args '((storage "AstStorage *"))
+          :init-object (lambda (var typename)
+                         (format nil "~A* ~A = storage->Create<~A>();"
+                                 typename var typename))))
+
+(lcp:define-class expression (tree)
+  ()
+  (:abstractp t)
+  ...
+  (:clone))
+
+(lcp:define-class where (tree)
+  ((expression "Expression *" :initval "nullptr" :scope :public))
+  (:clone))
+```
+
+`:args` option is only passed to the root class in inheritance hierarchy. By
+default, the same extra arguments will be passed to all class members that are
+cloned using `Clone` metehod. The generated code is:
+
+```cpp
+class Tree {
+ public:
+  virtual Tree *Clone(AstStorage *storage) const = 0;
+ private:
+  int32_t uid_;
+};
+
+class Expression : public Tree {
+ public:
+  Expression *Clone(AstStorage *storage) const override = 0;
+};
+
+class Where : public Tree {
+ public:
+  Expression *expression_{nullptr};
+
+  Where *Clone(AstStorage *storage) const override {
+    Where *object = storage->Create<Where>();
+    object->uid_ = uid_;
+    object->expression_ = expression_ ? expression_->Clone(storage) : nullptr;
+    return object;
+  }
+};
+```
diff --git a/src/lisp/CMakeLists.txt b/src/lisp/CMakeLists.txt
index 5cd68ec53..4eb37a953 100644
--- a/src/lisp/CMakeLists.txt
+++ b/src/lisp/CMakeLists.txt
@@ -6,6 +6,7 @@ set(lcp_src_files
     ${CMAKE_SOURCE_DIR}/src/lisp/lcp-compile
     ${CMAKE_SOURCE_DIR}/src/lisp/package.lisp
     ${CMAKE_SOURCE_DIR}/src/lisp/types.lisp
+    ${CMAKE_SOURCE_DIR}/src/lisp/clone.lisp
     ${CMAKE_SOURCE_DIR}/src/lisp/code-gen.lisp
     ${CMAKE_SOURCE_DIR}/src/lisp/slk.lisp
     ${CMAKE_SOURCE_DIR}/src/lisp/lcp.lisp
@@ -64,6 +65,7 @@ macro(define_add_lcp name main_src_files generated_lcp_files)
         ${CMAKE_SOURCE_DIR}/src/lisp/lcp-compile
         ${CMAKE_SOURCE_DIR}/src/lisp/package.lisp
         ${CMAKE_SOURCE_DIR}/src/lisp/types.lisp
+        ${CMAKE_SOURCE_DIR}/src/lisp/clone.lisp
         ${CMAKE_SOURCE_DIR}/src/lisp/code-gen.lisp
         ${CMAKE_SOURCE_DIR}/src/lisp/slk.lisp
         ${CMAKE_SOURCE_DIR}/src/lisp/lcp.lisp
diff --git a/src/lisp/clone.lisp b/src/lisp/clone.lisp
new file mode 100644
index 000000000..78e7597c6
--- /dev/null
+++ b/src/lisp/clone.lisp
@@ -0,0 +1,255 @@
+(in-package #:lcp.clone)
+
+(defvar *variable-idx* 0 "Used to generate unique variable names")
+
+(defmacro with-vars (vars &body body)
+  "Generates unique variable names for use in generated code by
+appending an index to desired variable names. Useful when generating
+loops which might reuse counter names.
+
+Usage example:
+  (with-vars ((loop-counter \"i\"))
+    (format nil \"for (auto ~A = 0; ~A < v.size(); ++~A) {
+                    // do something
+                  }\"
+            loop-counter loop-counter loop-counter))"
+  `(let* ((*variable-idx* (1+ *variable-idx*))
+          ,@(loop for var in vars collecting
+                 `(,(first var)
+                    (format nil "~A~A" ,(second var) *variable-idx*))))
+     ,@body))
+
+(define-condition clone-error (error)
+  ((message :type string :initarg :message :reader clone-error-message)
+   (format-args :type list :initform nil :initarg :format-args :reader clone-error-format-args))
+  (:report (lambda (condition stream)
+             (apply #'format stream
+                    (clone-error-message condition)
+                    (clone-error-format-args condition)))))
+
+(defun clone-error (message &rest format-args)
+  (error 'clone-error :message message :format-args format-args))
+
+(defun cloning-parent (cpp-class)
+  (let ((supers (lcp::cpp-class-super-classes cpp-class))
+        (opts (lcp::cpp-class-clone-opts cpp-class)))
+    (unless opts
+      (clone-error "Class ~A isn't cloneable" (lcp::cpp-type-base-name cpp-class)))
+    (cond
+      ((lcp::clone-opts-base opts) nil)
+      ((lcp::clone-opts-ignore-other-base-classes opts) (car supers))
+      (t
+       (when (> (length supers) 1)
+         (clone-error "Cloning doesn't support multiple inheritance (class '~A', parents: '~A')"
+                      (lcp::cpp-type-base-name cpp-class) supers))
+       (car supers)))))
+
+(defun cloning-root (cpp-class)
+  (let ((parent-class (cloning-parent cpp-class)))
+    (if parent-class
+        (cloning-root (lcp::find-cpp-class parent-class))
+        cpp-class)))
+
+(defun members-for-cloning (cpp-class)
+  (do ((current-class cpp-class) members)
+      ((not current-class) members)
+    (setf members (append (remove-if-not #'lcp::cpp-member-clone
+                                         (lcp::cpp-class-members current-class))
+                          members))
+    (setf current-class (lcp::find-cpp-class (cloning-parent current-class)))))
+
+(defun copy-object (source-name dest-name)
+  (format nil "~A = ~A;" dest-name source-name))
+
+(defun clone-by-copy-p (object-type)
+  (cond
+    ((string= "vector" (lcp::cpp-type-name object-type))
+     (clone-by-copy-p (car (lcp::cpp-type-type-args object-type))))
+    ((string= "optional" (lcp::cpp-type-name object-type))
+     (clone-by-copy-p (car (lcp::cpp-type-type-args object-type))))
+    ((string= "unordered_map" (lcp::cpp-type-name object-type))
+     (and (clone-by-copy-p (first (lcp::cpp-type-type-args object-type)))
+          (clone-by-copy-p (second (lcp::cpp-type-type-args object-type)))))
+    ((string= "pair" (lcp::cpp-type-name object-type))
+     (and (clone-by-copy-p (first (lcp::cpp-type-type-args object-type)))
+          (clone-by-copy-p (second (lcp::cpp-type-type-args object-type)))))
+    ((lcp::cpp-type-type-args object-type) nil)
+    (t (or
+        (lcp::find-cpp-enum (lcp::cpp-type-name object-type))
+        (typep object-type 'lcp::cpp-primitive-type)
+        (string= "string" (lcp::cpp-type-name object-type))
+        (not (lcp::find-cpp-class (lcp::cpp-type-name object-type)))
+        (not (lcp::cpp-class-clone-opts
+              (lcp::find-cpp-class (lcp::cpp-type-name object-type))))))))
+
+(defun clone-object (object-type source-name dest-name &key args)
+  (let ((object-type
+         (ctypecase object-type
+           (lcp::cpp-type object-type)
+           (string (lcp::parse-cpp-type-declaration object-type))
+           (symbol (lcp::cpp-type object-type))))
+        (arg-list (format nil "~{~A~^, ~}"
+                          (mapcar (lambda (name-and-type)
+                                    (lcp::cpp-variable-name (first name-and-type)))
+                                  args))))
+    (cond
+      ((clone-by-copy-p object-type)
+       (copy-object source-name dest-name))
+      ((lcp::cpp-pointer-type-p object-type)
+       (format nil "~A = ~A ? ~A->Clone(~A) : nullptr;"
+               dest-name source-name source-name arg-list))
+      ((string= "optional" (lcp::cpp-type-name object-type))
+       (let ((value-type (car (lcp::cpp-type-type-args object-type))))
+         (clone-optional value-type source-name dest-name :args args)))
+      ((string= "vector" (lcp::cpp-type-name object-type))
+       (let ((elem-type (car (lcp::cpp-type-type-args object-type))))
+         (clone-vector elem-type source-name dest-name :args args)))
+      ((string= "unordered_map" (lcp::cpp-type-name object-type))
+       (let ((key-type (first (lcp::cpp-type-type-args object-type)))
+             (value-type (second (lcp::cpp-type-type-args object-type))))
+             (clone-map key-type value-type source-name dest-name :args args)))
+      ((string= "pair" (lcp::cpp-type-name object-type))
+       (let ((first-type (first (lcp::cpp-type-type-args object-type)))
+             (second-type (second (lcp::cpp-type-type-args object-type))))
+             (clone-pair first-type second-type source-name dest-name :args args)))
+      ((and (lcp::find-cpp-class (lcp::cpp-type-name object-type))
+            (lcp::cpp-class-clone-opts (lcp::find-cpp-class (lcp::cpp-type-name object-type))))
+       (format nil "~A = ~A.Clone(~A);" dest-name source-name arg-list))
+      (t
+       (format nil "static_assert(false, \"Don't know how to clone object of type ~A\");"
+               (lcp::cpp-type-decl object-type))))))
+
+(defun clone-vector (elem-type source-name dest-name &key args)
+  (with-vars ((loop-counter "i"))
+    (format nil
+            "~A.resize(~A.size());
+             for (auto ~A = 0; ~A < ~A.size(); ++~A) { ~A }"
+            dest-name source-name
+            loop-counter loop-counter source-name loop-counter
+            (clone-object elem-type
+                          (format nil "~A[~A]" source-name loop-counter)
+                          (format nil "~A[~A]" dest-name loop-counter)
+                          :args args))))
+
+(defun clone-map (key-type value-type source-name dest-name &key args)
+  (with-vars ((loop-var "kv") (entry-var "entry"))
+    (let ((entry-type (lcp::make-cpp-type "pair"
+                                          :namespace '("std")
+                                          :type-args (list key-type value-type))))
+      (format nil
+              "for (const auto &~A : ~A) {
+                 ~A ~A;
+                 ~A
+                 ~A.emplace(std::move(~A));
+               }"
+              loop-var source-name
+              (lcp::cpp-type-decl entry-type) entry-var
+              (clone-object entry-type loop-var entry-var :args args)
+              dest-name entry-var))))
+
+(defun clone-optional (value-type source-name dest-name &key args)
+  (with-vars ((value-var "value"))
+    (format nil
+            "if (~A) {
+               ~A ~A;
+               ~A
+               ~A.emplace(std::move(~A));
+             } else {
+               ~A = std::experimental::nullopt;
+             }"
+          source-name
+          (lcp::cpp-type-decl value-type) value-var
+          (clone-object value-type
+                        (format nil "(*~A)" source-name)
+                        value-var
+                        :args args)
+          dest-name value-var
+          dest-name)))
+
+(defun clone-pair (first-type second-type source-name dest-name &key args)
+  (with-vars ((first-var "first") (second-var "second"))
+    (with-output-to-string (cpp-out)
+      (lcp::with-cpp-block-output (cpp-out)
+        (format cpp-out
+                "~A ~A;
+                 ~A
+                 ~A ~A;
+                 ~A
+                 ~A = std::make_pair(std::move(~A), std::move(~A));"
+                (lcp::cpp-type-decl first-type) first-var
+                (clone-object first-type
+                              (format nil "~A.first" source-name)
+                              first-var
+                              :args args)
+                (lcp::cpp-type-decl second-type) second-var
+                (clone-object second-type
+                              (format nil "~A.second" source-name)
+                              second-var
+                              :args args)
+                dest-name first-var second-var))
+      cpp-out)))
+
+(defun clone-function-definition-for-class (cpp-class)
+  (check-type cpp-class lcp::cpp-class)
+  (when (lcp::cpp-type-type-params cpp-class)
+    (clone-error "Don't know how to clone templated class '~A'"
+                 (lcp::cpp-type-base-name cpp-class)))
+  (let* ((cloning-root (cloning-root cpp-class))
+         (root-opts (lcp::cpp-class-clone-opts cloning-root))
+         (inheritancep (or (lcp::direct-subclasses-of cpp-class)
+                           (cloning-parent cpp-class)))
+         (return-type (cond
+                        ((lcp::clone-opts-return-type root-opts)
+                         (lcp::cpp-code
+                          (funcall (lcp::clone-opts-return-type root-opts)
+                                   (lcp::cpp-type-name cpp-class))))
+                        (inheritancep (format nil "std::unique_ptr<~A>"
+                                              (lcp::cpp-type-name (cloning-root cpp-class))))
+                        (t (lcp::cpp-type-name cpp-class))))
+         (declaration
+          (lcp::cpp-method-declaration cpp-class "Clone"
+                                       :args (lcp::clone-opts-args root-opts)
+                                       :returns return-type
+                                       :virtual (and inheritancep
+                                                     (eq cpp-class cloning-root))
+                                       :inline t
+                                       :const t
+                                       :override (and inheritancep
+                                                      (not (eq cpp-class cloning-root)))
+                                       :delete (lcp::cpp-class-abstractp cpp-class))))
+    (if (lcp::cpp-class-abstractp cpp-class)
+        (return-from clone-function-definition-for-class (format nil "~A;" declaration)))
+    (with-output-to-string (cpp-out)
+      (lcp::with-cpp-block-output (cpp-out :name declaration :semicolonp nil)
+        (let (object-access)
+          (cond
+            ((lcp::clone-opts-init-object root-opts)
+             (setf object-access "object->")
+             (write-line
+              (lcp::cpp-code
+               (funcall (lcp::clone-opts-init-object root-opts)
+                        "object" (lcp::cpp-type-name cpp-class)))
+               cpp-out))
+            (inheritancep
+             (setf object-access "object->")
+             (format cpp-out "~&auto object = std::make_unique<~A>();"
+                     (lcp::cpp-type-name cpp-class)))
+            (t
+             (setf object-access "object.")
+             (format cpp-out "~&~A object;"
+                     (lcp::cpp-type-name cpp-class))))
+          (dolist (member (members-for-cloning cpp-class))
+            (let* ((source (lcp::cpp-member-name member :struct (lcp::cpp-class-structp cpp-class)))
+                   (dest (format nil "~A~A" object-access source)))
+              (cond
+                ((eq (lcp::cpp-member-clone member) :copy)
+                 (format cpp-out "~&~A" (copy-object source dest)))
+                ((functionp (lcp::cpp-member-clone member))
+                 (format cpp-out "~&~A"
+                         (lcp::cpp-code (funcall (lcp::cpp-member-clone member) source dest))))
+                 (t
+                  (format cpp-out "~&~A"
+                          (clone-object (lcp::cpp-member-type member)
+                                        source dest
+                                        :args (lcp::clone-opts-args root-opts)))))))
+              (format cpp-out "~&return object;"))))))
diff --git a/src/lisp/lcp-test.lisp b/src/lisp/lcp-test.lisp
index cc22f92a2..f731500f7 100644
--- a/src/lisp/lcp-test.lisp
+++ b/src/lisp/lcp-test.lisp
@@ -564,3 +564,379 @@
                  ((protected-member :int64_t :scope :protected)
                   (public-member :char))))
               'lcp.slk:slk-error)))
+
+(deftest "clone"
+  (subtest "no inheritance"
+    (undefine-cpp-types)
+    (let ((tree-class (lcp:define-class tree ()
+                        ((value :int32_t)
+                         (left "std::unique_ptr<Tree>")
+                         (right "std::unique_ptr<Tree>"))
+                        (:clone :return-type (lambda (typename)
+                                               (format nil "std::unique_ptr<~A>" typename))
+                                :init-object (lambda (var typename)
+                                               (format nil "auto ~A = std::make_unique<~A>();"
+                                                       var typename)))))
+          (forest-class (lcp:define-class forest ()
+                          ((name "std::string")
+                           (small-tree "std::unique_ptr<Tree>")
+                           (big-tree "std::unique_ptr<Tree>"))
+                          (:clone))))
+      (is-generated (lcp.clone:clone-function-definition-for-class tree-class)
+                    "std::unique_ptr<Tree> Clone() const {
+                       auto object = std::make_unique<Tree>();
+                       object->value_ = value_;
+                       object->left_ = left_ ? left_->Clone() : nullptr;
+                       object->right_ = right_ ? right_->Clone() : nullptr;
+                       return object;
+                     }")
+      (is-generated (lcp.clone:clone-function-definition-for-class forest-class)
+                    "Forest Clone() const {
+                       Forest object;
+                       object.name_ = name_;
+                       object.small_tree_ = small_tree_ ? small_tree_->Clone() : nullptr;
+                       object.big_tree_ = big_tree_ ? big_tree_->Clone() : nullptr;
+                       return object;
+                     }")))
+  (subtest "single inheritance"
+    (undefine-cpp-types)
+    ;; Simple case
+    (let ((base-class (lcp:define-class base ()
+                        ((int-member :int32_t)
+                         (string-member "std::string"))
+                        (:clone)))
+          (child-class (lcp:define-class child (base)
+                         ((another-int-member :int64_t))
+                         (:clone))))
+      (is-generated (lcp.clone:clone-function-definition-for-class base-class)
+                    "virtual std::unique_ptr<Base> Clone() const {
+                       auto object = std::make_unique<Base>();
+                       object->int_member_ = int_member_;
+                       object->string_member_ = string_member_;
+                       return object;
+                     }")
+      (is-generated (lcp.clone:clone-function-definition-for-class child-class)
+                    "std::unique_ptr<Base> Clone() const override {
+                       auto object = std::make_unique<Child>();
+                       object->int_member_ = int_member_;
+                       object->string_member_ = string_member_;
+                       object->another_int_member_ = another_int_member_;
+                       return object;
+                     }"))
+    (undefine-cpp-types)
+    ;; Abstract base class
+    (let ((base-class (lcp:define-class base ()
+                        ((int-member :int32_t)
+                         (string-member "std::string"))
+                        (:abstractp t)
+                        (:clone)))
+          (child-class (lcp:define-class child (base)
+                         ((another-int-member :int64_t))
+                         (:clone))))
+      (is-generated (lcp.clone:clone-function-definition-for-class base-class)
+                    "virtual std::unique_ptr<Base> Clone() const = 0;")
+      (is-generated (lcp.clone:clone-function-definition-for-class child-class)
+                    "std::unique_ptr<Base> Clone() const override {
+                       auto object = std::make_unique<Child>();
+                       object->int_member_ = int_member_;
+                       object->string_member_ = string_member_;
+                       object->another_int_member_ = another_int_member_;
+                       return object;
+                     }"))
+    (undefine-cpp-types)
+    ;; :return-type and :init-object propagation
+    (let ((base-class (lcp:define-class base ()
+                        ((int-member :int32_t)
+                         (string-member "std::string"))
+                        (:abstractp t)
+                        (:clone :return-type (lambda (typename)
+                                               (format nil "~A*" typename))
+                                :init-object (lambda (var typename)
+                                               (format nil "~A* ~A = GlobalFactory::Create();"
+                                                       typename var)))))
+          (child-class (lcp:define-class child (base)
+                         ((another-int-member :int64_t))
+                         (:clone))))
+      (is-generated (lcp.clone:clone-function-definition-for-class base-class)
+                    "virtual Base *Clone() const = 0;")
+      (is-generated (lcp.clone:clone-function-definition-for-class child-class)
+                    "Child *Clone() const override {
+                       Child *object = GlobalFactory::Create();
+                       object->int_member_ = int_member_;
+                       object->string_member_ = string_member_;
+                       object->another_int_member_ = another_int_member_;
+                       return object;
+                     }"))
+    (undefine-cpp-types)
+    ;; inheritance with :ignore-other-base-classes and :base
+    (let ((base-class (lcp:define-class base ("utils::TotalOrdering")
+                        ((int-member :int32_t)
+                         (string-member "std::string"))
+                        (:abstractp t)
+                        (:clone :base t
+                                :return-type (lambda (typename)
+                                               (format nil "~A*" typename))
+                                :init-object (lambda (var typename)
+                                               (format nil "~A* ~A = GlobalFactory::Create();"
+                                                       typename var)))))
+          (child-class (lcp:define-class child (base "utils::TotalOrdering" "utils::TotalOrdering")
+                         ((another-int-member :int64_t))
+                         (:clone :ignore-other-base-classes t))))
+      (is-generated (lcp.clone:clone-function-definition-for-class base-class)
+                    "virtual Base *Clone() const = 0;")
+      (is-generated (lcp.clone:clone-function-definition-for-class child-class)
+                    "Child *Clone() const override {
+                       Child *object = GlobalFactory::Create();
+                       object->int_member_ = int_member_;
+                       object->string_member_ = string_member_;
+                       object->another_int_member_ = another_int_member_;
+                       return object;
+                     }")))
+  (subtest "extra args"
+    (undefine-cpp-types)
+    ;; extra arguments are always passed when calling `Clone` function
+    (let ((expression-class (lcp:define-class expression ()
+                              ((lhs "Expression *")
+                               (rhs "Expression *"))
+                              (:abstractp t)
+                              (:clone :return-type (lambda (typename)
+                                                     (format nil "~A*" typename))
+                                      :init-object (lambda (var typename)
+                                                     (format nil "~A* ~A = storage->Create<~A>();"
+                                                             typename var typename))
+                                      :args '((storage "ExpressionStorage *")))))
+          (and-class (lcp:define-class and (expression)
+                       ()
+                       (:clone)))
+          (or-class (lcp:define-class or (expression)
+                      ()
+                      (:clone)))
+          (filter-class (lcp:define-class filter ()
+                          ((expressions "std::vector<Expression *>"))
+                          (:clone :args '((exp-storage "ExpressionStorage *"))))))
+      (is-generated (lcp.clone:clone-function-definition-for-class expression-class)
+                    "virtual Expression *Clone(ExpressionStorage *storage) const = 0;")
+      (is-generated (lcp.clone:clone-function-definition-for-class and-class)
+                    "And *Clone(ExpressionStorage *storage) const override {
+                       And *object = storage->Create<And>();
+                       object->lhs_ = lhs_ ? lhs_->Clone(storage) : nullptr;
+                       object->rhs_ = rhs_ ? rhs_->Clone(storage) : nullptr;
+                       return object;
+                     }")
+      (is-generated (lcp.clone:clone-function-definition-for-class or-class)
+                    "Or *Clone(ExpressionStorage *storage) const override {
+                       Or *object = storage->Create<Or>();
+                       object->lhs_ = lhs_ ? lhs_->Clone(storage) : nullptr;
+                       object->rhs_ = rhs_ ? rhs_->Clone(storage) : nullptr;
+                       return object;
+                     }")
+      (is-generated (lcp.clone:clone-function-definition-for-class filter-class)
+                    "Filter Clone(ExpressionStorage *exp_storage) const {
+                       Filter object;
+                       object.expressions_.resize(expressions_.size());
+                       for (auto i1 = 0; i1 < expressions_.size(); ++i1) {
+                         object.expressions_[i1] =
+                             expressions_[i1] ? expressions_[i1]->Clone(exp_storage) : nullptr;
+                       }
+                       return object;
+                     }")))
+  (subtest "unsupported"
+    ;; multiple inheritance
+    (undefine-cpp-types)
+    (lcp:define-class first-base ()
+      ((int-member :int32_t))
+      (:clone))
+    (lcp:define-class second-base ()
+      ((private-member :int32_t :scope :private))
+      (:clone))
+    (let ((child-class (lcp:define-class child (first-base second-base)
+                        ((name "std::string"))
+                        (:clone))))
+      (is-error (lcp.clone:clone-function-definition-for-class child-class)
+                'lcp.clone:clone-error))
+    ;; template classes
+    (undefine-cpp-types)
+    (let ((container-class (lcp:define-class (my-container t-element) ()
+                             ((data "TElement *")
+                              (size "size_t")))))
+      (is-error (lcp.clone:clone-function-definition-for-class container-class)
+                'lcp.clone:clone-error)))
+  (subtest "custom clone"
+    (undefine-cpp-types)
+    (let ((my-class (lcp:define-class my-class ()
+                      ((callback "std::function<void(int, int)>" :clone :copy)
+                       (click-counter :int32_t :clone nil)
+                       (widget "Widget"
+                               :clone (lambda (source dest)
+                                        #>cpp
+                                        ${dest} = WidgetFactory::Create(${source}.type());
+                                        cpp<#)))
+                      (:clone))))
+      (is-generated (lcp.clone:clone-function-definition-for-class my-class)
+                    "MyClass Clone() const {
+                       MyClass object;
+                       object.callback_ = callback_;
+                       object.widget_ = WidgetFactory::Create(widget_.type());
+                       return object;
+                     }")))
+  (subtest "types"
+    (undefine-cpp-types)
+    (macrolet ((single-member-test (member expected)
+                 (let ((class-sym (gensym)))
+                   `(let ((,class-sym (lcp:define-class my-class ()
+                                        (,member)
+                                        (:clone))))
+                      (is-generated (lcp.clone:clone-function-definition-for-class ,class-sym)
+                                    (format nil "MyClass Clone() const {
+                                                   MyClass object;
+                                                   ~A
+                                                   return object;
+                                                 }"
+                                            ,expected))))))
+      (lcp:define-class klondike ()
+        ()
+        (:clone))
+      (subtest "vector"
+        (single-member-test (member "std::vector<int32_t>")
+                            "object.member_ = member_;")
+        (single-member-test (member "std::vector<std::vector<int32_t>>")
+                            "object.member_ = member_;")
+        (single-member-test (member "std::vector<Klondike>")
+                            "object.member_.resize(member_.size());
+                             for (auto i1 = 0; i1 < member_.size(); ++i1) {
+                               object.member_[i1] = member_[i1].Clone();
+                             }")
+        (single-member-test (member "std::vector<std::vector<Klondike>>")
+                            "object.member_.resize(member_.size());
+                             for (auto i1 = 0; i1 < member_.size(); ++i1) {
+                               object.member_[i1].resize(member_[i1].size());
+                               for (auto i2 = 0; i2 < member_[i1].size(); ++i2) {
+                                 object.member_[i1][i2] = member_[i1][i2].Clone();
+                               }
+                             }"))
+      (subtest "optional"
+        (single-member-test (member "std::experimental::optional<int32_t>")
+                            "object.member_ = member_;")
+        (single-member-test (member "std::experimental::optional<Klondike>")
+                            "if (member_) {
+                               Klondike value1;
+                               value1 = (*member_).Clone();
+                               object.member_.emplace(std::move(value1));
+                             } else {
+                               object.member_ = std::experimental::nullopt;
+                             }"))
+      (subtest "unordered_map"
+        (single-member-test (member "std::unordered_map<int32_t, std::string>")
+                            "object.member_ = member_;")
+        (single-member-test (member "std::unordered_map<int32_t, std::unordered_map<int32_t, std::string>>")
+                            "object.member_ = member_;")
+        (single-member-test (member "std::unordered_map<int32_t, Klondike>")
+                            "for (const auto &kv1 : member_) {
+                               std::pair<int32_t, Klondike> entry1;
+                               {
+                                 int32_t first2;
+                                 first2 = kv1.first;
+                                 Klondike second2;
+                                 second2 = kv1.second.Clone();
+                                 entry1 = std::make_pair(std::move(first2), std::move(second2));
+                               }
+
+                               object.member_.emplace(std::move(entry1));
+                             }")
+        (single-member-test (member "std::unordered_map<int32_t, std::unordered_map<int32_t, Klondike>>")
+                            "for (const auto &kv1 : member_) {
+                               std::pair<int32_t, std::unordered_map<int32_t, Klondike>> entry1;
+                               {
+                                 int32_t first2;
+                                 first2 = kv1.first;
+                                 std::unordered_map<int32_t, Klondike> second2;
+                                 for (const auto &kv3 : kv1.second) {
+                                   std::pair<int32_t, Klondike> entry3;
+                                   {
+                                     int32_t first4;
+                                     first4 = kv3.first;
+                                     Klondike second4;
+                                     second4 = kv3.second.Clone();
+                                     entry3 = std::make_pair(std::move(first4), std::move(second4));
+                                   }
+
+                                   second2.emplace(std::move(entry3));
+                                 }
+                                 entry1 = std::make_pair(std::move(first2), std::move(second2));
+
+                               }
+
+                               object.member_.emplace(std::move(entry1));
+                             }")
+        (single-member-test (member "std::unordered_map<Klondike, Klondike>")
+                            "for (const auto &kv1 : member_) {
+                               std::pair<Klondike, Klondike> entry1;
+                               {
+                                 Klondike first2;
+                                 first2 = kv1.first.Clone();
+                                 Klondike second2;
+                                 second2 = kv1.second.Clone();
+                                 entry1 = std::make_pair(std::move(first2), std::move(second2));
+                               }
+
+                               object.member_.emplace(std::move(entry1));
+                             }"))
+      (subtest "pair"
+        (single-member-test (member "std::pair<int32_t, int32_t>")
+                            "object.member_ = member_;")
+        (single-member-test (member "std::pair<int32_t, Klondike>")
+                            "{
+                               int32_t first1;
+                               first1 = member_.first;
+                               Klondike second1;
+                               second1 = member_.second.Clone();
+                               object.member_ = std::make_pair(std::move(first1), std::move(second1));
+                             }")
+        (single-member-test (member "std::pair<Klondike, int32_t>")
+                            "{
+                               Klondike first1;
+                               first1 = member_.first.Clone();
+                               int32_t second1;
+                               second1 = member_.second;
+                               object.member_ = std::make_pair(std::move(first1), std::move(second1));
+                             }")
+        (single-member-test (member "std::pair<Klondike, Klondike>")
+                            "{
+                               Klondike first1;
+                               first1 = member_.first.Clone();
+                               Klondike second1;
+                               second1 = member_.second.Clone();
+                               object.member_ = std::make_pair(std::move(first1), std::move(second1));
+                             }")
+        (single-member-test (member "std::pair<std::string, std::pair<int32_t, Klondike>>")
+                            "{
+                               std::string first1;
+                               first1 = member_.first;
+                               std::pair<int32_t, Klondike> second1;
+                               {
+                                 int32_t first2;
+                                 first2 = member_.second.first;
+                                 Klondike second2;
+                                 second2 = member_.second.second.Clone();
+                                 second1 = std::make_pair(std::move(first2), std::move(second2));
+                               }
+
+                               object.member_ = std::make_pair(std::move(first1), std::move(second1));
+                             }")
+        )
+      (subtest "pointers"
+        (single-member-test (member "Klondike *")
+                            "object.member_ = member_ ? member_->Clone() : nullptr;")
+        (single-member-test (member "std::unique_ptr<Klondike>")
+                            "object.member_ = member_ ? member_->Clone() : nullptr;")
+        (single-member-test (member "std::shared_ptr<Klondike>")
+                            "object.member_ = member_ ? member_->Clone() : nullptr;"))
+      (subtest "enum"
+        (lcp:define-enum enum '(val1 val2 val3))
+        (single-member-test (member "Enum")
+                            "object.member_ = member_;"))
+      (subtest "builtin c++ types"
+        (single-member-test (member :int32_t)
+                            "object.member_ = member_;")
+        (single-member-test (member :char)
+                            "object.member_ = member_;")))))
diff --git a/src/lisp/lcp.asd b/src/lisp/lcp.asd
index b99098507..16e951f82 100644
--- a/src/lisp/lcp.asd
+++ b/src/lisp/lcp.asd
@@ -8,6 +8,7 @@
                (:file "types")
                (:file "code-gen")
                (:file "slk")
+               (:file "clone")
                (:file "lcp"))
   :in-order-to ((test-op (test-op "lcp/test"))))
 
diff --git a/src/lisp/lcp.lisp b/src/lisp/lcp.lisp
index 61141f3d4..d08286ac8 100644
--- a/src/lisp/lcp.lisp
+++ b/src/lisp/lcp.lisp
@@ -96,7 +96,9 @@ NIL, returns a string."
             (format s "~%~{~A~%~}" (mapcar #'cpp-code (cpp-class-public cpp-class)))
             (format s "~{~%~A~}~%" (mapcar #'cpp-member-reader-definition reader-members))
             (format s "~{  ~%~A~}~%"
-                    (mapcar #'member-declaration (cpp-class-members-scoped :public)))))
+                    (mapcar #'member-declaration (cpp-class-members-scoped :public)))
+            (when (cpp-class-clone-opts cpp-class)
+              (format s "~%~A" (lcp.clone:clone-function-definition-for-class cpp-class)))))
         (when (or (cpp-class-protected cpp-class) (cpp-class-members-scoped :protected))
           (write-line " protected:" s)
           (format s "~{~A~%~}" (mapcar #'cpp-code (cpp-class-protected cpp-class)))
@@ -144,7 +146,7 @@ the function.  TYPE-PARAMS is a list of names for template argments"
 
 (defun cpp-method-declaration (class method-name
                                &key args (returns "void") (inline t) static
-                                 virtual const override)
+                                 virtual const override delete)
   "Generate a C++ method declaration as a string for the given METHOD-NAME on
 CLASS.  ARGS is a list of (variable type) arguments to method.  RETURNS is the
 return type of the function.  When INLINE is set to NIL, generates a
@@ -165,11 +167,12 @@ which generate the corresponding C++ keywords."
                                        (cpp-variable-name (first name-and-type))))
                                args)))
          (const (if const "const" ""))
-         (override (if (and override inline) "override" "")))
+         (override (if (and override inline) "override" ""))
+         (delete (if delete "= 0" "")))
     (raw-cpp-string
      #>cpp
      ${template} ${static/virtual}
-     ${returns} ${namespace}${method-name}(${args}) ${const} ${override}
+     ${returns} ${namespace}${method-name}(${args}) ${const} ${override} ${delete}
      cpp<#)))
 
 (defstruct cpp-list
diff --git a/src/lisp/package.lisp b/src/lisp/package.lisp
index 63e7e5c75..a270610dc 100644
--- a/src/lisp/package.lisp
+++ b/src/lisp/package.lisp
@@ -32,3 +32,8 @@
            #:save-function-definition-for-enum
            #:load-function-declaration-for-enum
            #:load-function-definition-for-enum))
+
+(defpackage #:lcp.clone
+  (:use #:cl)
+  (:export #:clone-error
+           #:clone-function-definition-for-class))
diff --git a/src/lisp/slk.lisp b/src/lisp/slk.lisp
index 2e6afb8e3..5798569b1 100644
--- a/src/lisp/slk.lisp
+++ b/src/lisp/slk.lisp
@@ -105,18 +105,6 @@ generation expects the declarations and definitions to be in `slk` namespace."
      "Load" :args (cons self-arg (cons reader-arg (load-extra-args cpp-class)))
      :type-params (lcp::cpp-type-type-params cpp-class))))
 
-(defun cpp-type-pointer-p (cpp-type)
-  (check-type cpp-type (or lcp::cpp-type string lcp::cpp-primitive-type-keywords))
-  (typecase cpp-type
-    (string (cpp-type-pointer-p (lcp::parse-cpp-type-declaration cpp-type)))
-    (lcp::cpp-type
-     (or
-      (string= "*" (lcp::cpp-type-name cpp-type))
-      (string= "shared_ptr" (lcp::cpp-type-name cpp-type))
-      ;; Note, we could forward to default slk::Load for unique_ptr and hope
-      ;; everything is alright w.r.t to inheritance.
-      (string= "unique_ptr" (lcp::cpp-type-name cpp-type))))))
-
 (defun save-members (cpp-class)
   "Generate code for saving members of CPP-CLASS.  Raise `SLK-ERROR' if the
 serializable member has no public access."
@@ -134,7 +122,7 @@ serializable member has no public access."
                                                  member-name))
                          s)))
           ;; TODO: Maybe support saving (but not loading) unique_ptr.
-          ((cpp-type-pointer-p (lcp::cpp-member-type member))
+          ((lcp::cpp-pointer-type-p (lcp::cpp-member-type member))
            (slk-error "Don't know how to save pointer '~A' in '~A'"
                       (lcp::cpp-member-type member)
                       (lcp::cpp-type-base-name cpp-class)))
@@ -164,7 +152,7 @@ serializable member has no public access."
              (write-line (lcp::cpp-code (funcall (lcp::cpp-member-slk-load member)
                                                  member-name))
                          s)))
-          ((cpp-type-pointer-p (lcp::cpp-member-type member))
+          ((lcp::cpp-pointer-type-p (lcp::cpp-member-type member))
            (slk-error "Don't know how to load pointer '~A' in '~A'"
                       (lcp::cpp-member-type member)
                       (lcp::cpp-type-base-name cpp-class)))
diff --git a/src/lisp/types.lisp b/src/lisp/types.lisp
index 6d5121243..5066415ac 100644
--- a/src/lisp/types.lisp
+++ b/src/lisp/types.lisp
@@ -139,7 +139,8 @@
   ;; May be a function which takes 1 argument, member-name.  It needs to
   ;; return C++ code.
   (slk-save nil :type (or null function) :read-only t)
-  (slk-load nil :type (or null function) :read-only t))
+  (slk-load nil :type (or null function) :read-only t)
+  (clone t :type (or boolean (eql :copy) function) :read-only t))
 
 (defstruct capnp-opts
   "Cap'n Proto serialization options for C++ class."
@@ -171,6 +172,15 @@
   ;; In case of multiple inheritance, pretend we only inherit the 1st base class.
   (ignore-other-base-classes nil :type boolean :read-only t))
 
+(defstruct clone-opts
+  "Cloning options for C++ class."
+  ;; Extra arguments to the generated clone function. List of (name cpp-type).
+  (args nil :read-only t)
+  (return-type nil :type (or null function) :read-only t)
+  (base nil :read-only t)
+  (ignore-other-base-classes nil :read-only t)
+  (init-object nil :read-only t))
+
 (defclass cpp-class (cpp-type)
   ((structp :type boolean :initarg :structp :initform nil
             :reader cpp-class-structp)
@@ -186,6 +196,8 @@
                :reader cpp-class-capnp-opts)
    (slk-opts :type (or null slk-opts) :initarg :slk-opts :initform nil
              :reader cpp-class-slk-opts)
+   (clone-opts :type (or null clone-opts) :initarg :clone-opts :initform nil
+               :reader cpp-class-clone-opts)
    (inner-types :initarg :inner-types :initform nil :reader cpp-class-inner-types)
    (abstractp :initarg :abstractp :initform nil :reader cpp-class-abstractp))
   (:documentation "Meta information on a C++ class (or struct)."))
@@ -270,6 +282,17 @@ documentation on `CPP-TYPE' members for function arguments."
   "Whether the C++ type designated by TYPE-DECL is a primitive type."
   (typep (cpp-type type-decl) 'cpp-primitive-type))
 
+(defun cpp-pointer-type-p (type-decl)
+  "Whether the C++ type designated by TYPE-DECL is a smart or raw pointer type."
+  (check-type type-decl (or lcp::cpp-type string lcp::cpp-primitive-type-keywords))
+  (typecase type-decl
+    (string (cpp-pointer-type-p (lcp::parse-cpp-type-declaration type-decl)))
+    (lcp::cpp-type
+     (or
+      (string= "*" (lcp::cpp-type-name type-decl))
+      (string= "shared_ptr" (lcp::cpp-type-name type-decl))
+      (string= "unique_ptr" (lcp::cpp-type-name type-decl))))))
+
 (defun parse-cpp-type-declaration (type-decl)
   "Parse C++ type from TYPE-DECL string and return CPP-TYPE.
 
@@ -579,6 +602,8 @@ Generates C++:
                                                 `(make-capnp-opts ,@(cdr (assoc :capnp serialize))))
                                  :slk-opts ,(when (assoc :slk serialize)
                                               `(make-slk-opts ,@(cdr (assoc :slk serialize))))
+                                 :clone-opts ,(when (assoc :clone options)
+                                                `(make-clone-opts ,@(cdr (assoc :clone options))))
                                  :abstractp ,abstractp
                                  :namespace (reverse *cpp-namespaces*)
                                  ;; Set inner types at the end. This works