From dbd226d05d5d5800dd4462cae434de6ae3994fd9 Mon Sep 17 00:00:00 2001
From: Lovro Lugovic <lovro.lugovic@memgraph.io>
Date: Fri, 24 May 2019 14:16:42 +0200
Subject: [PATCH] LCP: Minor improvements

Summary:
- Add a unified `generate_lcp` target
- Simplify SLK-ERROR and CLONE-ERROR
- Rework WITH-VARS
- Expect CPP-CLASS, not CPP-TYPE
- Fix CPP-TYPE-REFERENCE-P
- Add CPP-GENSYM
- Add util.lisp to LCP's source files in CMake
- Make the CMake variable `lcp_src_files` a cache (persistent) variable
- Add `lcp_src_files` as a dependency to `test_lcp`
- Rename `lcp_compile` to `compile-lcp`
- Fetch docstrings for CPP-NAME-* functions at run-time
- Remove existing C++ entities on redefinition

Reviewers: mtomic, teon.banek

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2094
---
 src/CMakeLists.txt                    |   3 +
 src/lisp/CMakeLists.txt               |  12 ++-
 src/lisp/clone.lisp                   |  30 ++++----
 src/lisp/code-gen.lisp                |  73 +++++++++++++++---
 src/lisp/{lcp-compile => compile-lcp} |   1 +
 src/lisp/names.lisp                   |  27 +++----
 src/lisp/slk.lisp                     |  14 ++--
 src/lisp/test.lisp                    | 104 +++++++++++++-------------
 src/lisp/types.lisp                   |  27 ++++++-
 tests/unit/CMakeLists.txt             |   2 +-
 10 files changed, 185 insertions(+), 108 deletions(-)
 rename src/lisp/{lcp-compile => compile-lcp} (87%)

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index e2421b000..ec8d748a4 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -321,6 +321,9 @@ target_compile_definitions(mg-single-node-ha PUBLIC MG_SINGLE_NODE_HA)
 # END Memgraph Single Node High Availability
 # ----------------------------------------------------------------------------
 
+add_custom_target(generate_lcp)
+add_dependencies(generate_lcp generate_lcp_single_node generate_lcp_single_node_ha generate_lcp_distributed)
+
 string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
 
 # STATIC library used to store key-value pairs
diff --git a/src/lisp/CMakeLists.txt b/src/lisp/CMakeLists.txt
index 91521081e..3a3be3125 100644
--- a/src/lisp/CMakeLists.txt
+++ b/src/lisp/CMakeLists.txt
@@ -3,7 +3,7 @@
 # Don't forget to repeat this list below in `define_add_lcp`.
 set(lcp_src_files
     ${CMAKE_SOURCE_DIR}/src/lisp/lcp.asd
-    ${CMAKE_SOURCE_DIR}/src/lisp/lcp-compile
+    ${CMAKE_SOURCE_DIR}/src/lisp/compile-lcp
     ${CMAKE_SOURCE_DIR}/src/lisp/package.lisp
     ${CMAKE_SOURCE_DIR}/src/lisp/names.lisp
     ${CMAKE_SOURCE_DIR}/src/lisp/types.lisp
@@ -13,12 +13,17 @@ set(lcp_src_files
     ${CMAKE_SOURCE_DIR}/src/lisp/lcp.lisp
     ${CMAKE_SOURCE_DIR}/src/lisp/debug.lisp
     ${CMAKE_SOURCE_DIR}/src/lisp/test.lisp
+    ${CMAKE_SOURCE_DIR}/src/lisp/util.lisp
     ${CMAKE_SOURCE_DIR}/tools/lcp)
 
+# Make `lcp_src_files` a persistent (cache) variable so that
+# tests/unit/CMakeLists.txt can see it.
+set(lcp_src_files "${lcp_src_files}" CACHE INTERNAL "")
+
 add_custom_target(lcp
   DEPENDS ${lcp_src_files}
   WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
-  COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/lcp-compile)
+  COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/compile-lcp)
 
 # Define `add_lcp` function named `name` for registering a lcp file for generation.
 #
@@ -53,7 +58,7 @@ macro(define_add_lcp name main_src_files generated_lcp_files)
     # not visible when invoked in another file.
     set(lcp_src_files
         ${CMAKE_SOURCE_DIR}/src/lisp/lcp.asd
-        ${CMAKE_SOURCE_DIR}/src/lisp/lcp-compile
+        ${CMAKE_SOURCE_DIR}/src/lisp/compile-lcp
         ${CMAKE_SOURCE_DIR}/src/lisp/package.lisp
         ${CMAKE_SOURCE_DIR}/src/lisp/names.lisp
         ${CMAKE_SOURCE_DIR}/src/lisp/types.lisp
@@ -63,6 +68,7 @@ macro(define_add_lcp name main_src_files generated_lcp_files)
         ${CMAKE_SOURCE_DIR}/src/lisp/lcp.lisp
         ${CMAKE_SOURCE_DIR}/src/lisp/debug.lisp
         ${CMAKE_SOURCE_DIR}/src/lisp/test.lisp
+				${CMAKE_SOURCE_DIR}/src/lisp/util.lisp
         ${CMAKE_SOURCE_DIR}/tools/lcp)
     add_custom_command(OUTPUT ${h_file} ${cpp_file}
       COMMAND ${CMAKE_SOURCE_DIR}/tools/lcp ${lcp_file} ${slk_serialize}
diff --git a/src/lisp/clone.lisp b/src/lisp/clone.lisp
index 0f9e1c1c4..eac856c41 100644
--- a/src/lisp/clone.lisp
+++ b/src/lisp/clone.lisp
@@ -1,18 +1,14 @@
 (in-package #:lcp.clone)
 
-(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)))))
+(define-condition clone-error (simple-error)
+  ())
 
-(defun clone-error (message &rest format-args)
-  (error 'clone-error :message message :format-args format-args))
+(defun clone-error (format-control &rest format-arguments)
+  (error 'clone-error :format-control format-control
+                      :format-arguments format-arguments))
 
 (defun cloning-parent (cpp-class)
-  (check-type cpp-class lcp::cpp-type)
+  (check-type cpp-class lcp::cpp-class)
   (let ((supers (lcp::cpp-class-super-classes cpp-class))
         (opts (lcp::cpp-class-clone-opts cpp-class)))
     (unless opts
@@ -29,14 +25,14 @@
        (car supers)))))
 
 (defun cloning-root (cpp-class)
-  (check-type cpp-class lcp::cpp-type)
+  (check-type cpp-class lcp::cpp-class)
   (let ((parent-class (cloning-parent cpp-class)))
     (if parent-class
         (cloning-root parent-class)
         cpp-class)))
 
 (defun members-for-cloning (cpp-class)
-  (check-type cpp-class lcp::cpp-type)
+  (check-type cpp-class lcp::cpp-class)
   (alexandria:flatten
    (reverse
     (loop :for current := cpp-class :then (cloning-parent current)
@@ -117,7 +113,7 @@
                       (lcp::cpp-type-decl object-type))))))
 
 (defun clone-vector (elem-type source-name dest-name &key args)
-  (lcp::with-vars ((loop-counter "i"))
+  (lcp::with-cpp-gensyms ((loop-counter "i"))
     (format nil
             "~A.resize(~A.size());
              for (auto ~A = 0; ~A < ~A.size(); ++~A) { ~A }"
@@ -129,7 +125,8 @@
                           :args args))))
 
 (defun clone-map (key-type value-type source-name dest-name &key args)
-  (lcp::with-vars ((loop-var "kv") (entry-var "entry"))
+  (lcp::with-cpp-gensyms ((loop-var "kv")
+                          (entry-var "entry"))
     (let ((entry-type (lcp::make-cpp-type
                        "pair"
                        :namespace '("std")
@@ -146,7 +143,7 @@
               dest-name entry-var))))
 
 (defun clone-optional (value-type source-name dest-name &key args)
-  (lcp::with-vars ((value-var "value"))
+  (lcp::with-cpp-gensyms ((value-var "value"))
     (format nil
             "if (~A) {
                ~A ~A;
@@ -165,7 +162,8 @@
           dest-name)))
 
 (defun clone-pair (first-type second-type source-name dest-name &key args)
-  (lcp::with-vars ((first-var "first") (second-var "second"))
+  (lcp::with-cpp-gensyms ((first-var "first")
+                          (second-var "second"))
     (with-output-to-string (cpp-out)
       (lcp::with-cpp-block-output (cpp-out)
         (format cpp-out
diff --git a/src/lisp/code-gen.lisp b/src/lisp/code-gen.lisp
index 2df2a153f..92df6f63f 100644
--- a/src/lisp/code-gen.lisp
+++ b/src/lisp/code-gen.lisp
@@ -65,23 +65,72 @@ context which binds OPEN-NAMESPACE-FUN function for opening namespaces."
           (cl-ppcre:regex-replace-all
            (string #\Newline) documentation (format nil "~%/// "))))
 
-(defvar *variable-idx* 0 "Used to generate unique variable names")
+(defvar *cpp-gensym-counter* 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.
+(defun cpp-gensym (&optional (prefix "var"))
+  "Generate a unique C++ name.
 
-Usage example:
-  (with-vars ((loop-counter \"i\"))
+The name is constructed by concatenating the string PREFIX with the current
+value of *CPP-GENSYM-COUNTER*. Afterwards, the value of *CPP-GENSYM-COUNTER* is
+incremented by 1.
+
+Despite the suggestive name \"gensym\", this function cannot guarantee that the
+name is globally unique (because C++ has no concept equivalent to uninterned
+symbols). The name is only unique across all of the names generated by the
+function."
+  (prog1 (format nil "~A~A" prefix *cpp-gensym-counter*)
+    (incf *cpp-gensym-counter*)))
+
+(defmacro with-cpp-gensyms (vars &body body)
+  "Evaluate and return the result of the implicit progn BODY with the variables
+specified within VARS bound to strings representing unique C++ names.
+
+Each element of VARS is either a symbol SYMBOL or a pair (SYMBOL NAME). Bare
+symbols are equivalent to the pair (SYMBOL SYMBOL-NAME) where SYMBOL-NAME is the
+result of (cpp-name-for-variable SYMBOL).
+
+Each pair (SYMBOL NAME) specifies a single unique C++ name. SYMBOL should be a
+symbol naming the variable to which the generated C++ name will bound. NAME
+should be a prefix that will be used to construct the name using CPP-GENSYM.
+
+Example:
+
+  (with-cpp-gensyms ((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*))))
+            loop-counter loop-counter loop-counter))
+
+  ;;; >>
+  ;;; for (auto i0 = 0; i0 < v.size(); ++i0) {
+  ;;;     // do something
+  ;;; }
+
+Example:
+
+  Assume *CPP-GENSYM-COUNTER* is 0.
+
+  (defun gen1 ()
+    (with-cpp-gensyms ((hello \"hello\"))
+      (format t \"int ~a;~%\" hello)))
+
+  (defun gen2 ()
+    (with-cpp-gensyms ((hello \"hello\"))
+      (gen1)
+      (format t \"int ~a;~%\" hello)))
+
+  (gen2)
+
+  ;;; >>
+  ;;; int hello1;
+  ;;; int hello0;"
+  `(let* (,@(mapcar
+              (lambda (var)
+                (destructuring-bind (sym &optional name)
+                    (alexandria:ensure-list var)
+                  (let ((name (or name (cpp-name-for-variable sym))))
+                    `(,sym (cpp-gensym ,name)))))
+              vars))
      ,@body))
 
 (defun cpp-member-reader-name (cpp-member)
diff --git a/src/lisp/lcp-compile b/src/lisp/compile-lcp
similarity index 87%
rename from src/lisp/lcp-compile
rename to src/lisp/compile-lcp
index 2d224ebcf..28e6d24be 100755
--- a/src/lisp/lcp-compile
+++ b/src/lisp/compile-lcp
@@ -9,4 +9,5 @@ echo \
 "
 (load \"${quicklisp_install_dir}/setup.lisp\")
 (ql:quickload :lcp :silent t)
+(ql:quickload :lcp/test :silent t)
 " | sbcl --script
diff --git a/src/lisp/names.lisp b/src/lisp/names.lisp
index 27381f3ad..1bacc042d 100644
--- a/src/lisp/names.lisp
+++ b/src/lisp/names.lisp
@@ -154,19 +154,20 @@ The name function's name is of the form CPP-<CPP-OBJECT>-NAME.
 
 The namestring function's name is of the form
 ENSURE-NAMESTRING-FOR-<CPP-OBJECT>."
-  `(progn
-     (defun ,(alexandria:symbolicate 'cpp-name-for- cpp-object)
-         (thing &key from-style)
-       ,(documentation name-op 'function)
-       (check-type thing (or symbol string))
-       (,name-op thing :from-style from-style))
-     (defun ,(alexandria:symbolicate 'ensure-namestring-for- cpp-object) (thing)
-       ,(format nil +cpp-namestring-docstring+
-                (string-downcase cpp-object)
-                (string-downcase cpp-object)
-                name-op)
-       (check-type thing (or symbol string))
-       (ensure-namestring-for thing #',name-op))))
+  (let ((cpp-name-for (alexandria:symbolicate 'cpp-name-for- cpp-object)))
+    `(progn
+       (defun ,cpp-name-for (thing &key from-style)
+         (check-type thing (or symbol string))
+         (,name-op thing :from-style from-style))
+       (setf (documentation ',cpp-name-for 'function)
+             (documentation ',name-op 'function))
+       (defun ,(alexandria:symbolicate 'ensure-namestring-for- cpp-object) (thing)
+         ,(format nil +cpp-namestring-docstring+
+                  (string-downcase cpp-object)
+                  (string-downcase cpp-object)
+                  name-op)
+         (check-type thing (or symbol string))
+         (ensure-namestring-for thing #',name-op)))))
 
 (define-cpp-name namespace  lower-snake-case-name)
 (define-cpp-name class      pascal-case-name)
diff --git a/src/lisp/slk.lisp b/src/lisp/slk.lisp
index a1f19cee5..216980c31 100644
--- a/src/lisp/slk.lisp
+++ b/src/lisp/slk.lisp
@@ -4,16 +4,12 @@
 
 (in-package #:lcp.slk)
 
-(define-condition slk-error (error)
-  ((message :type string :initarg :message :reader slk-error-message)
-   (format-args :type list :initform nil :initarg :format-args :reader slk-error-format-args))
-  (:report (lambda (condition stream)
-             (apply #'format stream
-                    (slk-error-message condition)
-                    (slk-error-format-args condition)))))
+(define-condition slk-error (simple-error)
+  ())
 
-(defun slk-error (message &rest format-args)
-  (error 'slk-error :message message :format-args format-args))
+(defun slk-error (format-control &rest format-arguments)
+  (error 'slk-error :format-control format-control
+                    :format-arguments format-arguments))
 
 ;;; CPP-CLASS serialization generation
 
diff --git a/src/lisp/test.lisp b/src/lisp/test.lisp
index cbeb626cf..8c8ca7f60 100644
--- a/src/lisp/test.lisp
+++ b/src/lisp/test.lisp
@@ -182,8 +182,10 @@ CPP-TYPE-DECL."
      '(#\Newline)
      (uiop:run-program "clang-format -style=file" :input s :output '(:string :stripped t)))))
 
-(defun is-generated (got expected)
-  (is (clang-format got) (clang-format expected) :test #'string=))
+(defmacro is-generated (got expected)
+  `(is (let ((lcp::*cpp-gensym-counter* 0))
+         (clang-format ,got))
+       (clang-format ,expected) :test #'string=))
 
 (defun undefine-cpp-types ()
   (setf lcp::*cpp-classes* nil)
@@ -784,9 +786,9 @@ CPP-TYPE-DECL."
                       "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;
+                       for (auto i0 = 0; i0 < expressions_.size(); ++i0) {
+                         object.expressions_[i0] =
+                             expressions_[i0] ? expressions_[i0]->Clone(exp_storage) : nullptr;
                        }
                        return object;
                      }")))
@@ -841,15 +843,15 @@ CPP-TYPE-DECL."
                             "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();
+                             for (auto i0 = 0; i0 < member_.size(); ++i0) {
+                               object.member_[i0] = member_[i0].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();
+                             for (auto i0 = 0; i0 < member_.size(); ++i0) {
+                               object.member_[i0].resize(member_[i0].size());
+                               for (auto i1 = 0; i1 < member_[i0].size(); ++i1) {
+                                 object.member_[i0][i1] = member_[i0][i1].Clone();
                                }
                              }"))
       (subtest "optional"
@@ -857,9 +859,9 @@ CPP-TYPE-DECL."
                             "object.member_ = member_;")
         (single-member-test (member "std::optional<Klondike>")
                             "if (member_) {
-                               Klondike value1;
-                               value1 = (*member_).Clone();
-                               object.member_.emplace(std::move(value1));
+                               Klondike value0;
+                               value0 = (*member_).Clone();
+                               object.member_.emplace(std::move(value0));
                              } else {
                                object.member_ = std::nullopt;
                              }"))
@@ -869,52 +871,52 @@ CPP-TYPE-DECL."
         (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_) {
+                            "for (const auto &kv0 : 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));
+                                 first2 = kv0.first;
+                                 Klondike second3;
+                                 second3 = kv0.second.Clone();
+                                 entry1 = std::make_pair(std::move(first2), std::move(second3));
                                }
 
                                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_) {
+                            "for (const auto &kv0 : 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;
+                                 first2 = kv0.first;
+                                 std::unordered_map<int32_t, Klondike> second3;
+                                 for (const auto &kv4 : kv0.second) {
+                                   std::pair<int32_t, Klondike> entry5;
                                    {
-                                     int32_t first4;
-                                     first4 = kv3.first;
-                                     Klondike second4;
-                                     second4 = kv3.second.Clone();
-                                     entry3 = std::make_pair(std::move(first4), std::move(second4));
+                                     int32_t first6;
+                                     first6 = kv4.first;
+                                     Klondike second7;
+                                     second7 = kv4.second.Clone();
+                                     entry5 = std::make_pair(std::move(first6), std::move(second7));
                                    }
 
-                                   second2.emplace(std::move(entry3));
+                                   second3.emplace(std::move(entry5));
                                  }
-                                 entry1 = std::make_pair(std::move(first2), std::move(second2));
+                                 entry1 = std::make_pair(std::move(first2), std::move(second3));
 
                                }
 
                                object.member_.emplace(std::move(entry1));
                              }")
         (single-member-test (member "std::unordered_map<Klondike, Klondike>")
-                            "for (const auto &kv1 : member_) {
+                            "for (const auto &kv0 : 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));
+                                 first2 = kv0.first.Clone();
+                                 Klondike second3;
+                                 second3 = kv0.second.Clone();
+                                 entry1 = std::make_pair(std::move(first2), std::move(second3));
                                }
 
                                object.member_.emplace(std::move(entry1));
@@ -924,42 +926,42 @@ CPP-TYPE-DECL."
                             "object.member_ = member_;")
         (single-member-test (member "std::pair<int32_t, Klondike>")
                             "{
-                               int32_t first1;
-                               first1 = member_.first;
+                               int32_t first0;
+                               first0 = member_.first;
                                Klondike second1;
                                second1 = member_.second.Clone();
-                               object.member_ = std::make_pair(std::move(first1), std::move(second1));
+                               object.member_ = std::make_pair(std::move(first0), std::move(second1));
                              }")
         (single-member-test (member "std::pair<Klondike, int32_t>")
                             "{
-                               Klondike first1;
-                               first1 = member_.first.Clone();
+                               Klondike first0;
+                               first0 = member_.first.Clone();
                                int32_t second1;
                                second1 = member_.second;
-                               object.member_ = std::make_pair(std::move(first1), std::move(second1));
+                               object.member_ = std::make_pair(std::move(first0), std::move(second1));
                              }")
         (single-member-test (member "std::pair<Klondike, Klondike>")
                             "{
-                               Klondike first1;
-                               first1 = member_.first.Clone();
+                               Klondike first0;
+                               first0 = member_.first.Clone();
                                Klondike second1;
                                second1 = member_.second.Clone();
-                               object.member_ = std::make_pair(std::move(first1), std::move(second1));
+                               object.member_ = std::make_pair(std::move(first0), std::move(second1));
                              }")
         (single-member-test (member "std::pair<std::string, std::pair<int32_t, Klondike>>")
                             "{
-                               std::string first1;
-                               first1 = member_.first;
+                               std::string first0;
+                               first0 = 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));
+                                 Klondike second3;
+                                 second3 = member_.second.second.Clone();
+                                 second1 = std::make_pair(std::move(first2), std::move(second3));
                                }
 
-                               object.member_ = std::make_pair(std::move(first1), std::move(second1));
+                               object.member_ = std::make_pair(std::move(first0), std::move(second1));
                              }"))
       (subtest "pointers"
         (single-member-test (member "Klondike *")
diff --git a/src/lisp/types.lisp b/src/lisp/types.lisp
index 8df9fd236..c13b44da8 100644
--- a/src/lisp/types.lisp
+++ b/src/lisp/types.lisp
@@ -825,7 +825,7 @@ not an instance of UNSUPPORTED-CPP-TYPE)."
 (defun cpp-type-reference-p (cpp-type)
   "Test whether CPP-TYPE represents a reference type."
   (check-type cpp-type cpp-type)
-  (string= (cpp-type-name cpp-type) "*"))
+  (string= (cpp-type-name cpp-type) "&"))
 
 (defun cpp-type-smart-pointer-p (cpp-type)
   "Test whether CPP-TYPE represents a smart pointer type."
@@ -1038,10 +1038,26 @@ defined.")
   "A list of strings naming the enclosing classes of the current class being
 defined. The names are ordered from outermost to innermost enclosing class.")
 
+(defun cons-or-replace (element list &key (test #'eql) (key #'identity))
+  "Cons the ELEMENT to the LIST and remove existing elements.
+
+All elements that compare equal (under TEST) with ELEMENT are removed. KEY will
+be applied to each element of LIST before calling TEST."
+  (cons element (remove-if (lambda (other) (funcall test element other))
+                           list :key key)))
+
 (defun register-enum (cpp-enum)
+  "Register the given CPP-ENUM instance with the enum registry."
   (check-type cpp-enum cpp-enum)
   (prog1 cpp-enum
-    (push cpp-enum *cpp-enums*)
+    ;; Add or redefine the enum.
+    (setf *cpp-enums*
+          (cons cpp-enum
+                (delete-if
+                 (lambda (other)
+                   (string= (cpp-type-decl cpp-enum) (cpp-type-decl other)))
+                 *cpp-enums*)))
+    ;; Add to the parent's inner types.
     (unless (eq *cpp-inner-types* :toplevel)
       (push cpp-enum *cpp-inner-types*))))
 
@@ -1050,7 +1066,12 @@ defined. The names are ordered from outermost to innermost enclosing class.")
   (check-type cpp-class cpp-class)
   (prog1 cpp-class
     ;; Add or redefine the class.
-    (push cpp-class *cpp-classes*)
+    (setf *cpp-classes*
+          (cons cpp-class
+                (delete-if
+                 (lambda (other)
+                   (string= (cpp-type-decl cpp-class) (cpp-type-decl other)))
+                 *cpp-classes*)))
     ;; Add to the parent's inner types.
     (unless (eq *cpp-inner-types* :toplevel)
       (push cpp-class *cpp-inner-types*))))
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
index 5f8fa7fa6..deac0accf 100644
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -378,7 +378,7 @@ target_link_libraries(${test_prefix}auth mg-auth kvstore_lib)
 
 add_custom_command(
   OUTPUT test_lcp
-  DEPENDS lcp test_lcp.lisp
+  DEPENDS ${lcp_src_files} lcp test_lcp.lisp
   COMMAND sbcl --script ${CMAKE_CURRENT_SOURCE_DIR}/test_lcp.lisp)
 add_custom_target(test_lcp ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/test_lcp)
 add_test(test_lcp ${CMAKE_CURRENT_BINARY_DIR}/test_lcp)