diff --git a/docs/dev/lcp.md b/docs/dev/lcp.md
index 727ac5814..35829dfc2 100644
--- a/docs/dev/lcp.md
+++ b/docs/dev/lcp.md
@@ -481,8 +481,24 @@ Proto, we simply enumerate all derived types inside the union of a base type.
 Multiple inheritance is a different beast and as such is not directly
 supported.
 
-Most form of inheritance should actually be a simple composition, and we can
-treat parent classes as being composed inside our derived type.
+One way to use multiple inheritance is only to implement the interface of pure
+virtual classes without any members (i.e. interface classes). In such a case,
+you do not want to serialize any other base class except the primary one. To
+let LCP know that is the case, use `:ignore-other-base-classes t`. LCP will
+only try to serialize the base class that is the first (leftmost) in the list
+of super classes.
+
+```lisp
+(lcp:define-class derived (primary-base some-interface other-interface)
+  ...
+  (:serialize :capnp :ignore-other-base-classes t))
+```
+
+Another form of multiple inheritance is reusing some common code. In
+actuality, this is a very bad code practice and should be replaced with
+composition. If it would take too long to fix such code to use composition
+proper, we can tell LCP to treat such inheritance as if they are indeed
+composed. This is done via `:inherit-compose` option.
 
 For example:
 
diff --git a/src/lisp/lcp.lisp b/src/lisp/lcp.lisp
index d32498450..4d143fa67 100644
--- a/src/lisp/lcp.lisp
+++ b/src/lisp/lcp.lisp
@@ -339,7 +339,9 @@ CPP-TYPE has no namespace, return an empty string."
   (type-args nil :read-only t)
   ;; In case of multiple inheritance, list of classes which should be handled
   ;; as a composition.
-  (inherit-compose nil :read-only t))
+  (inherit-compose nil :read-only t)
+  ;; In case of multiple inheritance, pretend we only inherit the 1st base class.
+  (ignore-other-base-classes nil :type boolean :read-only t))
 
 (defclass cpp-class (cpp-type)
   ((structp :type boolean :initarg :structp :initform nil
@@ -658,8 +660,16 @@ Cap'n Proto schema."
   (declare (type (or symbol cpp-class) cpp-class))
   (let ((class-name (if (symbolp cpp-class) cpp-class (cpp-type-base-name cpp-class))))
     (remove-if (lambda (subclass)
-                 (member class-name
-                         (capnp-opts-inherit-compose (cpp-class-capnp-opts subclass))))
+                 (let ((capnp-opts (cpp-class-capnp-opts subclass)))
+                   (or
+                    ;; Remove if we are a parent that should be ignored (not
+                    ;; the 1st in the list).
+                    (and (capnp-opts-ignore-other-base-classes capnp-opts)
+                         (not (eq class-name (car (cpp-class-super-classes subclass)))))
+                    ;; Remove if we are a parent that should be treated as
+                    ;; composition.
+                    (member class-name
+                            (capnp-opts-inherit-compose (cpp-class-capnp-opts subclass))))))
                (direct-subclasses-of cpp-class))))
 
 (defun capnp-union-and-compose-parents (cpp-class)
@@ -668,17 +678,23 @@ secondary value contains parents which are modeled as being composed inside
 CPP-CLASS."
   (declare (type (or symbol cpp-class) cpp-class))
   (let* ((class (if (symbolp cpp-class) (find-cpp-class cpp-class) cpp-class))
-         (capnp-opts (cpp-class-capnp-opts class))
-         union compose)
+         (capnp-opts (cpp-class-capnp-opts class)))
     (when (not capnp-opts)
       (error "Class ~A should be marked for capnp serialization,
 or its derived classes set as :CAPNP :BASE T" (cpp-type-base-name class)))
     (when (not (capnp-opts-base capnp-opts))
-      (dolist (parent (cpp-class-super-classes class))
-        (if (member parent (capnp-opts-inherit-compose capnp-opts))
-            (push parent compose)
-            (push parent union))))
-    (values union compose)))
+      (if (capnp-opts-ignore-other-base-classes capnp-opts)
+          ;; Since we are ignoring multiple inheritance, return the 1st class
+          ;; (as union parent).
+          (list (car (cpp-class-super-classes class)))
+          ;; We aren't ignoring multiple inheritance, collect union and
+          ;; compose parents.
+          (let (union compose)
+            (dolist (parent (cpp-class-super-classes class))
+              (if (member parent (capnp-opts-inherit-compose capnp-opts))
+                  (push parent compose)
+                  (push parent union)))
+            (values union compose))))))
 
 (defun capnp-union-parents-rec (cpp-class)
   "Return a list of all parent clases recursively for CPP-CLASS that should be
diff --git a/src/query/frontend/ast/ast.lcp b/src/query/frontend/ast/ast.lcp
index 8995406ad..e5d8e2a6b 100644
--- a/src/query/frontend/ast/ast.lcp
+++ b/src/query/frontend/ast/ast.lcp
@@ -150,8 +150,7 @@ class AstStorage {
 };
 cpp<#
 
-(lcp:define-class tree ("::utils::Visitable<HierarchicalTreeVisitor>"
-                        "::utils::Visitable<TreeVisitor<TypedValue>>")
+(lcp:define-class tree ("::utils::Visitable<HierarchicalTreeVisitor>")
   ((uid :int32_t :scope :public))
   (:abstractp t)
   (:public
@@ -159,7 +158,6 @@ cpp<#
     Tree() = default;
 
     using ::utils::Visitable<HierarchicalTreeVisitor>::Accept;
-    using ::utils::Visitable<TreeVisitor<TypedValue>>::Accept;
 
     virtual Tree *Clone(AstStorage &storage) const = 0;
     cpp<#)
@@ -187,11 +185,18 @@ cpp<#
                            saved_uids->push_back(self.uid_);
                            cpp<#)))
 
-(lcp:define-class expression (tree)
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Expressions
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(lcp:define-class expression (tree "::utils::Visitable<ExpressionVisitor<TypedValue>>")
   ()
   (:abstractp t)
   (:public
     #>cpp
+    using ::utils::Visitable<ExpressionVisitor<TypedValue>>::Accept;
+    using Tree::Accept;
+
     Expression() = default;
 
     Expression *Clone(AstStorage &storage) const override = 0;
@@ -204,7 +209,7 @@ cpp<#
     #>cpp
     friend class AstStorage;
     cpp<#)
-  (:serialize :capnp))
+  (:serialize :capnp :ignore-other-base-classes t))
 
 (lcp:define-class where (tree)
   ((expression "Expression *" :initval "nullptr" :scope :public
@@ -215,7 +220,6 @@ cpp<#
     #>cpp
     Where() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         expression_->Accept(visitor);
@@ -304,7 +308,7 @@ cpp<#
                           (:public
                             (let ((cpp-name (lcp::cpp-type-name ',op)))
                               #>cpp
-                              DEFVISITABLE(TreeVisitor<TypedValue>);
+                              DEFVISITABLE(ExpressionVisitor<TypedValue>);
                               bool Accept(HierarchicalTreeVisitor &visitor) override {
                                 if (visitor.PreVisit(*this)) {
                                   expression1_->Accept(visitor) && expression2_->Accept(visitor);
@@ -335,7 +339,7 @@ cpp<#
                           (:public
                             (let ((cpp-name (lcp::cpp-type-name ',op)))
                               #>cpp
-                              DEFVISITABLE(TreeVisitor<TypedValue>);
+                              DEFVISITABLE(ExpressionVisitor<TypedValue>);
                               bool Accept(HierarchicalTreeVisitor &visitor) override {
                                 if (visitor.PreVisit(*this)) {
                                   expression_->Accept(visitor);
@@ -377,7 +381,7 @@ cpp<#
       return op_strings[static_cast<int>(op)];
     }
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         if (expression1_) expression1_->Accept(visitor);
@@ -432,7 +436,7 @@ cpp<#
     #>cpp
     ListSlicingOperator() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         bool cont = list_->Accept(visitor);
@@ -486,7 +490,7 @@ cpp<#
     #>cpp
     IfOperator() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         condition_->Accept(visitor) && then_expression_->Accept(visitor) &&
@@ -551,7 +555,7 @@ cpp<#
     #>cpp
     PrimitiveLiteral() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     DEFVISITABLE(HierarchicalTreeVisitor);
 
     PrimitiveLiteral *Clone(AstStorage &storage) const override {
@@ -583,7 +587,7 @@ cpp<#
     #>cpp
     ListLiteral() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         for (auto expr_ptr : elements_)
@@ -622,7 +626,7 @@ cpp<#
     #>cpp
     MapLiteral() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         for (auto pair : elements_)
@@ -659,7 +663,7 @@ cpp<#
     #>cpp
     Identifier() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     DEFVISITABLE(HierarchicalTreeVisitor);
 
     Identifier *Clone(AstStorage &storage) const override {
@@ -689,7 +693,7 @@ cpp<#
     #>cpp
     PropertyLookup() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         expression_->Accept(visitor);
@@ -739,7 +743,7 @@ cpp<#
     #>cpp
     LabelsTest() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         expression_->Accept(visitor);
@@ -781,7 +785,7 @@ cpp<#
     #>cpp
     Function() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         for (auto *argument : arguments_) {
@@ -848,7 +852,7 @@ cpp<#
     #>cpp
     Reduce() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         accumulator_->Accept(visitor) && initializer_->Accept(visitor) &&
@@ -893,7 +897,7 @@ cpp<#
    #>cpp
    Coalesce() = default;
 
-   DEFVISITABLE(TreeVisitor<TypedValue>);
+   DEFVISITABLE(ExpressionVisitor<TypedValue>);
    bool Accept(HierarchicalTreeVisitor &visitor) override {
      if (visitor.PreVisit(*this)) {
        for (auto *expr : expressions_) {
@@ -942,7 +946,7 @@ cpp<#
     #>cpp
     Extract() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         identifier_->Accept(visitor) && list_->Accept(visitor) &&
@@ -989,7 +993,7 @@ cpp<#
     #>cpp
     All() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         identifier_->Accept(visitor) && list_expression_->Accept(visitor) &&
@@ -1019,9 +1023,9 @@ cpp<#
     cpp<#)
   (:serialize :capnp))
 
-;;;; TODO: This is pretty much copy pasted from All. Consider merging Reduce,
-;;;; All, Any and Single into something like a higher-order function call which
-;;;; takes a list argument and a function which is applied on list elements.
+;; TODO: This is pretty much copy pasted from All. Consider merging Reduce,
+;; All, Any and Single into something like a higher-order function call which
+;; takes a list argument and a function which is applied on list elements.
 (lcp:define-class single (expression)
   ((identifier "Identifier *" :initval "nullptr" :scope :public
                :capnp-type "Tree" :capnp-init nil
@@ -1039,7 +1043,7 @@ cpp<#
     #>cpp
     Single() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         identifier_->Accept(visitor) && list_expression_->Accept(visitor) &&
@@ -1076,7 +1080,7 @@ cpp<#
     #>cpp
     ParameterLookup() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     DEFVISITABLE(HierarchicalTreeVisitor);
 
     ParameterLookup *Clone(AstStorage &storage) const override {
@@ -1095,7 +1099,7 @@ cpp<#
     cpp<#)
   (:serialize :capnp))
 
-(lcp:define-class named-expression (tree)
+(lcp:define-class named-expression (tree "::utils::Visitable<ExpressionVisitor<TypedValue>>")
   ((name "std::string" :scope :public)
    (expression "Expression *" :initval "nullptr" :scope :public
                :capnp-type "Tree" :capnp-init nil
@@ -1105,9 +1109,11 @@ cpp<#
                    :documentation "This field contains token position of first token in named expression used to create name_. If NamedExpression object is not created from query or it is aliased leave this value at -1."))
   (:public
     #>cpp
+    using ::utils::Visitable<ExpressionVisitor<TypedValue>>::Accept;
+
     NamedExpression() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
+    DEFVISITABLE(ExpressionVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         expression_->Accept(visitor);
@@ -1137,7 +1143,11 @@ cpp<#
     #>cpp
     friend class AstStorage;
     cpp<#)
-  (:serialize :capnp))
+  (:serialize :capnp :ignore-other-base-classes t))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; END Expressions
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (lcp:define-class pattern-atom (tree)
   ((identifier "Identifier *" :initval "nullptr" :scope :public
@@ -1178,7 +1188,6 @@ cpp<#
                :scope :public))
   (:public
     #>cpp
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         bool cont = identifier_->Accept(visitor);
@@ -1273,7 +1282,6 @@ cpp<#
                   :load-args '((storage "AstStorage *")
                                (loaded-uids "std::vector<int32_t> *"))))
     #>cpp
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         bool cont = identifier_->Accept(visitor);
@@ -1366,7 +1374,6 @@ cpp<#
     #>cpp
     Pattern() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         bool cont = identifier_->Accept(visitor);
@@ -1427,7 +1434,6 @@ cpp<#
     #>cpp
     SingleQuery() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         for (auto &clause : clauses_) {
@@ -1467,7 +1473,6 @@ cpp<#
     #>cpp
     CypherUnion() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         single_query_->Accept(visitor);
@@ -1534,7 +1539,6 @@ cpp<#
     #>cpp
     CypherQuery() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         bool should_continue = single_query_->Accept(visitor);
@@ -1576,7 +1580,6 @@ cpp<#
     #>cpp
     ExplainQuery() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         cypher_query_->Accept(visitor);
@@ -1614,7 +1617,6 @@ cpp<#
     #>cpp
     IndexQuery() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     DEFVISITABLE(HierarchicalTreeVisitor);
 
     IndexQuery *Clone(AstStorage &storage) const override {
@@ -1644,7 +1646,6 @@ cpp<#
     #>cpp
     Create() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         for (auto &pattern : patterns_) {
@@ -1689,7 +1690,6 @@ cpp<#
     #>cpp
     Match() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         bool cont = true;
@@ -1799,7 +1799,6 @@ cpp<#
     #>cpp
     Return() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         bool cont = true;
@@ -1850,7 +1849,6 @@ cpp<#
     #>cpp
     With() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         bool cont = true;
@@ -1905,7 +1903,6 @@ cpp<#
     #>cpp
     Delete() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         for (auto &expr : expressions_) {
@@ -1949,7 +1946,6 @@ cpp<#
     #>cpp
     SetProperty() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         property_lookup_->Accept(visitor) && expression_->Accept(visitor);
@@ -1990,7 +1986,6 @@ cpp<#
     #>cpp
     SetProperties() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         identifier_->Accept(visitor) && expression_->Accept(visitor);
@@ -2035,7 +2030,6 @@ cpp<#
     #>cpp
     SetLabels() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         identifier_->Accept(visitor);
@@ -2069,7 +2063,6 @@ cpp<#
     #>cpp
     RemoveProperty() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         property_lookup_->Accept(visitor);
@@ -2109,7 +2102,6 @@ cpp<#
     #>cpp
     RemoveLabels() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         identifier_->Accept(visitor);
@@ -2153,7 +2145,6 @@ cpp<#
     #>cpp
     Merge() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         bool cont = pattern_->Accept(visitor);
@@ -2214,7 +2205,6 @@ cpp<#
     #>cpp
     Unwind() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         named_expression_->Accept(visitor);
@@ -2266,7 +2256,6 @@ cpp<#
     #>cpp
     AuthQuery() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         if (password_) password_->Accept(visitor);
@@ -2346,7 +2335,6 @@ cpp<#
     #>cpp
     StreamQuery() = default;
 
-    DEFVISITABLE(TreeVisitor<TypedValue>);
     bool Accept(HierarchicalTreeVisitor &visitor) override {
       if (visitor.PreVisit(*this)) {
         bool cont = true;
diff --git a/src/query/frontend/ast/ast_visitor.hpp b/src/query/frontend/ast/ast_visitor.hpp
index 860b37944..d8bc80781 100644
--- a/src/query/frontend/ast/ast_visitor.hpp
+++ b/src/query/frontend/ast/ast_visitor.hpp
@@ -92,19 +92,18 @@ class HierarchicalTreeVisitor : public TreeCompositeVisitor,
   using typename TreeLeafVisitor::ReturnType;
 };
 
-template <typename TResult>
-using TreeVisitor = ::utils::Visitor<
-    TResult, CypherQuery, ExplainQuery, SingleQuery, CypherUnion,
-    NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator,
-    AdditionOperator, SubtractionOperator, MultiplicationOperator,
-    DivisionOperator, ModOperator, NotEqualOperator, EqualOperator,
-    LessOperator, GreaterOperator, LessEqualOperator, GreaterEqualOperator,
-    InListOperator, SubscriptOperator, ListSlicingOperator, IfOperator,
-    UnaryPlusOperator, UnaryMinusOperator, IsNullOperator, ListLiteral,
-    MapLiteral, PropertyLookup, LabelsTest, Aggregation, Function, Reduce,
-    Coalesce, Extract, All, Single, ParameterLookup, Create, Match, Return,
-    With, Pattern, NodeAtom, EdgeAtom, Delete, Where, SetProperty,
-    SetProperties, SetLabels, RemoveProperty, RemoveLabels, Merge, Unwind,
-    Identifier, PrimitiveLiteral, IndexQuery, AuthQuery, StreamQuery>;
+template <class TResult>
+class ExpressionVisitor
+    : public ::utils::Visitor<
+          TResult, NamedExpression, OrOperator, XorOperator, AndOperator,
+          NotOperator, AdditionOperator, SubtractionOperator,
+          MultiplicationOperator, DivisionOperator, ModOperator,
+          NotEqualOperator, EqualOperator, LessOperator, GreaterOperator,
+          LessEqualOperator, GreaterEqualOperator, InListOperator,
+          SubscriptOperator, ListSlicingOperator, IfOperator, UnaryPlusOperator,
+          UnaryMinusOperator, IsNullOperator, ListLiteral, MapLiteral,
+          PropertyLookup, LabelsTest, Aggregation, Function, Reduce, Coalesce,
+          Extract, All, Single, ParameterLookup, Identifier, PrimitiveLiteral> {
+};
 
 }  // namespace query
diff --git a/src/query/interpret/eval.hpp b/src/query/interpret/eval.hpp
index bb1091db9..295b2a784 100644
--- a/src/query/interpret/eval.hpp
+++ b/src/query/interpret/eval.hpp
@@ -18,7 +18,7 @@
 
 namespace query {
 
-class ExpressionEvaluator : public TreeVisitor<TypedValue> {
+class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
  public:
   ExpressionEvaluator(Frame *frame, const SymbolTable &symbol_table,
                       const EvaluationContext &ctx,
@@ -29,39 +29,7 @@ class ExpressionEvaluator : public TreeVisitor<TypedValue> {
         dba_(dba),
         graph_view_(graph_view) {}
 
-  using TreeVisitor<TypedValue>::Visit;
-
-#define BLOCK_VISIT(TREE_TYPE)                                        \
-  TypedValue Visit(TREE_TYPE &) override {                            \
-    LOG(FATAL) << "ExpressionEvaluator should not visit " #TREE_TYPE; \
-  }
-
-  BLOCK_VISIT(CypherQuery);
-  BLOCK_VISIT(ExplainQuery);
-  BLOCK_VISIT(CypherUnion);
-  BLOCK_VISIT(SingleQuery);
-  BLOCK_VISIT(Create);
-  BLOCK_VISIT(Match);
-  BLOCK_VISIT(Return);
-  BLOCK_VISIT(With);
-  BLOCK_VISIT(Pattern);
-  BLOCK_VISIT(NodeAtom);
-  BLOCK_VISIT(EdgeAtom);
-  BLOCK_VISIT(Delete);
-  BLOCK_VISIT(Where);
-  BLOCK_VISIT(SetProperty);
-  BLOCK_VISIT(SetProperties);
-  BLOCK_VISIT(SetLabels);
-  BLOCK_VISIT(RemoveProperty);
-  BLOCK_VISIT(RemoveLabels);
-  BLOCK_VISIT(Merge);
-  BLOCK_VISIT(Unwind);
-  BLOCK_VISIT(IndexQuery);
-  BLOCK_VISIT(AuthQuery);
-  BLOCK_VISIT(StreamQuery);
-
-
-#undef BLOCK_VISIT
+  using ExpressionVisitor<TypedValue>::Visit;
 
   TypedValue Visit(NamedExpression &named_expression) override {
     const auto &symbol = symbol_table_->at(named_expression);