Index: src/mongo/db/pipeline/abt/transformer_visitor.cpp
===================================================================
diff --git a/src/mongo/db/pipeline/abt/transformer_visitor.cpp b/src/mongo/db/pipeline/abt/transformer_visitor.cpp
deleted file mode 100644
--- a/src/mongo/db/pipeline/abt/transformer_visitor.cpp	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
+++ /dev/null	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
@@ -1,230 +0,0 @@
-/**
- *    Copyright (C) 2022-present MongoDB, Inc.
- *
- *    This program is free software: you can redistribute it and/or modify
- *    it under the terms of the Server Side Public License, version 1,
- *    as published by MongoDB, Inc.
- *
- *    This program is distributed in the hope that it will be useful,
- *    but WITHOUT ANY WARRANTY; without even the implied warranty of
- *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *    Server Side Public License for more details.
- *
- *    You should have received a copy of the Server Side Public License
- *    along with this program. If not, see
- *    <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- *    As a special exception, the copyright holders give permission to link the
- *    code of portions of this program with the OpenSSL library under certain
- *    conditions as described in each individual source file and distribute
- *    linked combinations including the program with the OpenSSL library. You
- *    must comply with the Server Side Public License in all respects for
- *    all of the code used other than as permitted herein. If you modify file(s)
- *    with this exception, you may extend this exception to your version of the
- *    file(s), but you are not obligated to do so. If you do not wish to do so,
- *    delete this exception statement from your version. If you delete this
- *    exception statement from all source files in the program, then also delete
- *    it in the license file.
- */
-
-#include "mongo/db/pipeline/abt/transformer_visitor.h"
-
-#include "mongo/db/pipeline/abt/agg_expression_visitor.h"
-#include "mongo/db/pipeline/abt/utils.h"
-#include "mongo/db/pipeline/document_source_group.h"
-#include "mongo/db/pipeline/document_source_replace_root.h"
-#include "mongo/db/pipeline/visitors/transformer_interface_walker.h"
-
-namespace mongo::optimizer {
-
-void ABTTransformerVisitor::visit(
-    const projection_executor::AddFieldsProjectionExecutor* transformer) {
-    visitInclusionNode(transformer->getRoot(), true /*isAddingFields*/);
-}
-
-void ABTTransformerVisitor::visit(
-    const projection_executor::ExclusionProjectionExecutor* transformer) {
-    visitExclusionNode(*transformer->getRoot());
-}
-
-void ABTTransformerVisitor::visit(
-    const projection_executor::InclusionProjectionExecutor* transformer) {
-    visitInclusionNode(*transformer->getRoot(), false /*isAddingFields*/);
-}
-
-void ABTTransformerVisitor::visit(const GroupFromFirstDocumentTransformation* transformer) {
-    unsupportedTransformer(transformer);
-}
-
-void ABTTransformerVisitor::visit(const ReplaceRootTransformation* transformer) {
-    auto entry = _ctx.getNode();
-    const std::string& projName = _ctx.getNextId("newRoot");
-    ABT expr =
-        generateAggExpression(transformer->getExpression().get(), entry._rootProjection, projName);
-
-    _ctx.setNode<EvaluationNode>(projName, projName, std::move(expr), std::move(entry._node));
-}
-
-void ABTTransformerVisitor::generateCombinedProjection() const {
-    auto result = _builder.generateABT();
-    if (!result) {
-        return;
-    }
-
-    auto entry = _ctx.getNode();
-    const ProjectionName projName = _ctx.getNextId("combinedProjection");
-    _ctx.setNode<EvaluationNode>(projName, projName, std::move(*result), std::move(entry._node));
-}
-
-void ABTTransformerVisitor::unsupportedTransformer(const TransformerInterface* transformer) const {
-    uasserted(ErrorCodes::InternalErrorNotSupported,
-              str::stream() << "Transformer is not supported (code: "
-                            << static_cast<int>(transformer->getType()) << ")");
-}
-
-void ABTTransformerVisitor::assertSupportedPath(const std::string& path) {
-    uassert(ErrorCodes::InternalErrorNotSupported,
-            "Projection contains unsupported numeric path component",
-            !FieldRef(path).hasNumericPathComponents());
-}
-
-/**
- * Handles simple inclusion projections.
- */
-void ABTTransformerVisitor::processProjectedPaths(const projection_executor::InclusionNode& node) {
-    // For each preserved path, mark that each path element along the field path should be
-    // included.
-    OrderedPathSet preservedPaths;
-    node.reportProjectedPaths(&preservedPaths);
-
-    for (const std::string& preservedPathStr : preservedPaths) {
-        assertSupportedPath(preservedPathStr);
-
-        _builder.integrateFieldPath(FieldPath(preservedPathStr),
-                                    [](const bool isLastElement, FieldMapEntry& entry) {
-                                        entry._hasLeadingObj = true;
-                                        entry._hasKeep = true;
-                                    });
-    }
-}
-
-/**
- * Handles renamed fields and computed projections.
- */
-void ABTTransformerVisitor::processComputedPaths(const projection_executor::InclusionNode& node,
-                                                 const std::string& rootProjection,
-                                                 const bool isAddingFields) {
-    OrderedPathSet computedPaths;
-    StringMap<std::string> renamedPaths;
-    node.reportComputedPaths(&computedPaths, &renamedPaths);
-
-    // Handle path renames: essentially single element FieldPath expression.
-    for (const auto& renamedPathEntry : renamedPaths) {
-        ABT path = translateFieldPath(
-            FieldPath(renamedPathEntry.second),
-            make<PathIdentity>(),
-            [](const std::string& fieldName, const bool isLastElement, ABT input) {
-                return make<PathGet>(
-                    fieldName,
-                    isLastElement ? std::move(input)
-                                  : make<PathTraverse>(std::move(input), PathTraverse::kUnlimited));
-            });
-
-        auto entry = _ctx.getNode();
-        const std::string& renamedProjName = _ctx.getNextId("projRenamedPath");
-        _ctx.setNode<EvaluationNode>(
-            entry._rootProjection,
-            renamedProjName,
-            make<EvalPath>(std::move(path), make<Variable>(entry._rootProjection)),
-            std::move(entry._node));
-
-        _builder.integrateFieldPath(
-            FieldPath(renamedPathEntry.first),
-            [&renamedProjName, &isAddingFields](const bool isLastElement, FieldMapEntry& entry) {
-                if (!isAddingFields) {
-                    entry._hasKeep = true;
-                }
-                if (isLastElement) {
-                    entry._constVarName = renamedProjName;
-                    entry._hasTrailingDefault = true;
-                }
-            });
-    }
-
-    // Handle general expression projection.
-    for (const std::string& computedPathStr : computedPaths) {
-        assertSupportedPath(computedPathStr);
-
-        const FieldPath computedPath(computedPathStr);
-
-        auto entry = _ctx.getNode();
-        const std::string& getProjName = _ctx.getNextId("projGetPath");
-        ABT getExpr = generateAggExpression(
-            node.getExpressionForPath(computedPath).get(), rootProjection, getProjName);
-
-        _ctx.setNode<EvaluationNode>(std::move(entry._rootProjection),
-                                     getProjName,
-                                     std::move(getExpr),
-                                     std::move(entry._node));
-
-        _builder.integrateFieldPath(
-            computedPath,
-            [&getProjName, &isAddingFields](const bool isLastElement, FieldMapEntry& entry) {
-                if (!isAddingFields) {
-                    entry._hasKeep = true;
-                }
-                if (isLastElement) {
-                    entry._constVarName = getProjName;
-                    entry._hasTrailingDefault = true;
-                }
-            });
-    }
-}
-
-void ABTTransformerVisitor::visitInclusionNode(const projection_executor::InclusionNode& node,
-                                               const bool isAddingFields) {
-    auto entry = _ctx.getNode();
-    const std::string rootProjection = entry._rootProjection;
-
-    processProjectedPaths(node);
-    processComputedPaths(node, rootProjection, isAddingFields);
-}
-
-void ABTTransformerVisitor::visitExclusionNode(const projection_executor::ExclusionNode& node) {
-    // Handle simple exclusion projections: for each excluded path, mark that the last field
-    // path element should be dropped.
-    OrderedPathSet excludedPaths;
-    node.reportProjectedPaths(&excludedPaths);
-    for (const std::string& excludedPathStr : excludedPaths) {
-        assertSupportedPath(excludedPathStr);
-        _builder.integrateFieldPath(FieldPath(excludedPathStr),
-                                    [](const bool isLastElement, FieldMapEntry& entry) {
-                                        if (isLastElement) {
-                                            entry._hasDrop = true;
-                                        }
-                                    });
-    }
-}
-
-/**
- * TODO SERVER-68690: Refactor this function to be shared with the DocumentSource implementation.
- */
-void translateProjection(AlgebrizerContext& ctx,
-                         ProjectionName scanProjName,
-                         boost::intrusive_ptr<ExpressionContext> expCtx,
-                         const projection_ast::Projection* proj) {
-
-    const auto projExecutor = projection_executor::buildProjectionExecutor(
-        expCtx,
-        proj,
-        ProjectionPolicies::findProjectionPolicies(),
-        projection_executor::BuilderParamsBitSet{projection_executor::kDefaultBuilderParams});
-
-    FieldMapBuilder builder(scanProjName, true);
-    ABTTransformerVisitor visitor(ctx, builder);
-    TransformerInterfaceWalker walker(&visitor);
-    walker.walk(projExecutor.get());
-    visitor.generateCombinedProjection();
-}
-
-}  // namespace mongo::optimizer
Index: src/mongo/db/pipeline/abt/transformer_visitor.h
===================================================================
diff --git a/src/mongo/db/pipeline/abt/transformer_visitor.h b/src/mongo/db/pipeline/abt/transformer_visitor.h
deleted file mode 100644
--- a/src/mongo/db/pipeline/abt/transformer_visitor.h	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
+++ /dev/null	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
@@ -1,95 +0,0 @@
-/**
- *    Copyright (C) 2022-present MongoDB, Inc.
- *
- *    This program is free software: you can redistribute it and/or modify
- *    it under the terms of the Server Side Public License, version 1,
- *    as published by MongoDB, Inc.
- *
- *    This program is distributed in the hope that it will be useful,
- *    but WITHOUT ANY WARRANTY; without even the implied warranty of
- *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *    Server Side Public License for more details.
- *
- *    You should have received a copy of the Server Side Public License
- *    along with this program. If not, see
- *    <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- *    As a special exception, the copyright holders give permission to link the
- *    code of portions of this program with the OpenSSL library under certain
- *    conditions as described in each individual source file and distribute
- *    linked combinations including the program with the OpenSSL library. You
- *    must comply with the Server Side Public License in all respects for
- *    all of the code used other than as permitted herein. If you modify file(s)
- *    with this exception, you may extend this exception to your version of the
- *    file(s), but you are not obligated to do so. If you do not wish to do so,
- *    delete this exception statement from your version. If you delete this
- *    exception statement from all source files in the program, then also delete
- *    it in the license file.
- */
-
-#pragma once
-
-#include "mongo/db/exec/add_fields_projection_executor.h"
-#include "mongo/db/exec/exclusion_projection_executor.h"
-#include "mongo/db/exec/inclusion_projection_executor.h"
-#include "mongo/db/exec/projection_executor_builder.h"
-#include "mongo/db/pipeline/abt/algebrizer_context.h"
-#include "mongo/db/pipeline/abt/field_map_builder.h"
-#include "mongo/db/pipeline/visitors/transformer_interface_visitor.h"
-
-namespace mongo::optimizer {
-
-// TODO: SERVER-68516 remove this file and implement computed projection translation in
-// projection_ast_visitor.h.
-class ABTTransformerVisitor : public TransformerInterfaceConstVisitor {
-public:
-    ABTTransformerVisitor(AlgebrizerContext& ctx, FieldMapBuilder& builder)
-        : _ctx(ctx), _builder(builder) {}
-
-    void visit(const projection_executor::AddFieldsProjectionExecutor* transformer) override;
-
-    void visit(const projection_executor::ExclusionProjectionExecutor* transformer) override;
-
-    void visit(const projection_executor::InclusionProjectionExecutor* transformer) override;
-
-    void visit(const GroupFromFirstDocumentTransformation* transformer) override;
-
-    void visit(const ReplaceRootTransformation* transformer) override;
-
-    /**
-     * Creates a single EvaluationNode representing simple projections (e.g. inclusion projections)
-     * and computed projections, if present, and updates the context with the new node.
-     */
-    void generateCombinedProjection() const;
-
-private:
-    void unsupportedTransformer(const TransformerInterface* transformer) const;
-
-    void assertSupportedPath(const std::string& path);
-
-    /**
-     * Handles simple inclusion projections.
-     */
-    void processProjectedPaths(const projection_executor::InclusionNode& node);
-
-    /**
-     * Handles renamed fields and computed projections.
-     */
-    void processComputedPaths(const projection_executor::InclusionNode& node,
-                              const std::string& rootProjection,
-                              bool isAddingFields);
-
-    void visitInclusionNode(const projection_executor::InclusionNode& node, bool isAddingFields);
-
-    void visitExclusionNode(const projection_executor::ExclusionNode& node);
-
-    AlgebrizerContext& _ctx;
-    FieldMapBuilder& _builder;
-};
-
-void translateProjection(AlgebrizerContext& ctx,
-                         ProjectionName scanProjName,
-                         boost::intrusive_ptr<ExpressionContext> expCtx,
-                         const projection_ast::Projection* proj);
-
-}  // namespace mongo::optimizer
Index: src/mongo/db/pipeline/abt/document_source_visitor.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/mongo/db/pipeline/abt/document_source_visitor.cpp b/src/mongo/db/pipeline/abt/document_source_visitor.cpp
--- a/src/mongo/db/pipeline/abt/document_source_visitor.cpp	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
+++ b/src/mongo/db/pipeline/abt/document_source_visitor.cpp	(date 1666037665931)
@@ -34,7 +34,6 @@
 #include "mongo/db/pipeline/abt/collation_translation.h"
 #include "mongo/db/pipeline/abt/match_expression_visitor.h"
 #include "mongo/db/pipeline/abt/projection_ast_visitor.h"
-#include "mongo/db/pipeline/abt/transformer_visitor.h"
 #include "mongo/db/pipeline/abt/utils.h"
 #include "mongo/db/pipeline/document_source_bucket_auto.h"
 #include "mongo/db/pipeline/document_source_coll_stats.h"
@@ -471,36 +470,53 @@
 
     void visit(const DocumentSourceSingleDocumentTransformation* source) override {
         switch (source->getType()) {
-            case TransformerInterface::TransformerType::kComputedProjection:
+            case TransformerInterface::TransformerType::kComputedProjection: {
+                auto entry = _ctx.getNode();
+                const std::string& projName = _ctx.getNextId("combinedProjection");
+                tassert(6851600,
+                        "Parsing kComputedProjection but there's no Expression in "
+                        "DocumentSource",
+                        source->hasAddFieldsMap());
+
+                FieldMapBuilder builder(entry._rootProjection,
+                                        entry._rootProjection == _ctx.getScanProjName());
+
+                processAddFields(
+                    entry._rootProjection, true, source->getAddFieldsMap(), _ctx, builder);
+
+                auto result = builder.generateABT();
+                _ctx.setNode<EvaluationNode>(
+                    projName, projName, std::move(*result), std::move(_ctx.getNode()._node));
+
+                return;
+            }
             case TransformerInterface::TransformerType::kGroupFromFirstDocument:
-            case TransformerInterface::TransformerType::kReplaceRoot:
-                break;
+                unsupportedStage(source);
+                return;
+            case TransformerInterface::TransformerType::kReplaceRoot: {
+                auto entry = _ctx.getNode();
+                const std::string& projName = _ctx.getNextId("newRoot");
+                tassert(6851601,
+                        "Parsing ReplaceRoot but there's no Expression in "
+                        "DocumentSource",
+                        source->hasExpression());
+                ABT expr =
+                    generateAggExpression(source->getExpression(), entry._rootProjection, projName);
+
+                _ctx.setNode<EvaluationNode>(
+                    projName, projName, std::move(expr), std::move(entry._node));
+                return;
+            }
             case TransformerInterface::TransformerType::kExclusionProjection:
             case TransformerInterface::TransformerType::kInclusionProjection: {
                 tassert(6684600,
                         "Translating Inclusion or Exclusion projection but there's no AST in "
                         "DocumentSource",
                         source->hasProjectionAST());
-                try {
-                    translateProjection(_ctx, source->getProjectionAST());
-                    return;
-                } catch (const DBException& ex) {
-                    if (ex.code() != ErrorCodes::InternalErrorNotSupported) {
-                        throw;
-                    }
-                }
+
+                translateProjection(_ctx, source->getProjectionAST());
             }
         }
-
-        // TODO: SERVER-68516: this is a workaround to support computed projection
-        // in the short term. Remove this workaround and complete projection translation via
-        // projection_ast_visitor instead.
-        const ProjectionName& rootProjName = _ctx.getNode()._rootProjection;
-        FieldMapBuilder builder(rootProjName, rootProjName == _ctx.getScanProjName());
-        ABTTransformerVisitor visitor(_ctx, builder);
-        TransformerInterfaceWalker walker(&visitor);
-        walker.walk(&source->getTransformer());
-        visitor.generateCombinedProjection();
     }
 
     void visit(const DocumentSourceSkip* source) override {
Index: src/mongo/db/pipeline/abt/projection_ast_visitor.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/mongo/db/pipeline/abt/projection_ast_visitor.cpp b/src/mongo/db/pipeline/abt/projection_ast_visitor.cpp
--- a/src/mongo/db/pipeline/abt/projection_ast_visitor.cpp	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
+++ b/src/mongo/db/pipeline/abt/projection_ast_visitor.cpp	(date 1666037665987)
@@ -33,15 +33,18 @@
 #include "mongo/db/pipeline/abt/utils.h"
 #include "mongo/db/query/projection_ast_path_tracking_visitor.h"
 
+
 namespace mongo::optimizer {
 
 class ProjectionPreVisitor final : public projection_ast::ProjectionASTConstVisitor {
 public:
     ProjectionPreVisitor(projection_ast::PathTrackingVisitorContext<>* context,
+                         AlgebrizerContext& ctx,
                          bool isInclusion,
                          const ProjectionName& rootProjName,
                          const ProjectionName& scanProjName)
         : _context{context},
+          _ctx{ctx},
           _builder(rootProjName, rootProjName == scanProjName),
           _isInclusion(isInclusion) {
         invariant(_context);
@@ -60,7 +63,8 @@
     }
 
     void visit(const projection_ast::ExpressionASTNode* node) final {
-        unsupportedProjectionType("ExpressionASTNode");
+        const auto& path = _context->fullPath();
+        processExpression(_ctx, _builder, false, node->expression(), path.fullPath());
     }
 
     void visit(const projection_ast::BooleanConstantASTNode* node) final {
@@ -81,6 +85,7 @@
     }
 
     void visit(const projection_ast::ProjectionPathASTNode* node) final {}
+
     void visit(const projection_ast::MatchExpressionASTNode* node) final {
         unsupportedProjectionType("MatchExpressionASTNode");
     }
@@ -91,15 +96,10 @@
 
 private:
     projection_ast::PathTrackingVisitorContext<>* _context;
+    AlgebrizerContext& _ctx;
     FieldMapBuilder _builder;
     bool _isInclusion;
 
-    void assertSupportedPath(const std::string& path) {
-        uassert(ErrorCodes::InternalErrorNotSupported,
-                "Projection contains unsupported numeric path component",
-                !FieldRef(path).hasNumericPathComponents());
-    }
-
     void builderIntegrateInclusion(const std::string& fullPath) {
         assertSupportedPath(fullPath);
         _builder.integrateFieldPath(FieldPath(fullPath),
@@ -131,7 +131,8 @@
     const bool isInclusion = proj->type() == projection_ast::ProjectType::kInclusion;
     const ProjectionName& rootProjName = ctx.getNode()._rootProjection;
 
-    ProjectionPreVisitor astVisitor{&context, isInclusion, rootProjName, ctx.getScanProjName()};
+    ProjectionPreVisitor astVisitor{
+        &context, ctx, isInclusion, rootProjName, ctx.getScanProjName()};
     projection_ast::PathTrackingWalker walker{&context, {&astVisitor}, {}};
     tree_walker::walk<true, projection_ast::ASTNode>(proj->root(), &walker);
 
@@ -139,6 +140,7 @@
     if (!result) {
         return;
     }
+
     auto entry = ctx.getNode();
     const ProjectionName projName = ctx.getNextId("combinedProjection");
     ctx.setNode<EvaluationNode>(projName, projName, std::move(*result), std::move(entry._node));
Index: src/mongo/db/pipeline/abt/utils.h
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/mongo/db/pipeline/abt/utils.h b/src/mongo/db/pipeline/abt/utils.h
--- a/src/mongo/db/pipeline/abt/utils.h	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
+++ b/src/mongo/db/pipeline/abt/utils.h	(date 1666037666043)
@@ -31,6 +31,10 @@
 
 #include "mongo/db/exec/document_value/value.h"
 #include "mongo/db/exec/sbe/values/bson.h"
+#include "mongo/db/pipeline/abt/agg_expression_visitor.h"
+#include "mongo/db/pipeline/abt/algebrizer_context.h"
+#include "mongo/db/pipeline/abt/field_map_builder.h"
+#include "mongo/db/pipeline/expression.h"
 #include "mongo/db/pipeline/field_path.h"
 #include "mongo/db/query/optimizer/node.h"
 
@@ -58,4 +62,28 @@
  */
 boost::optional<IntervalReqExpr::Node> defaultConvertPathToInterval(const ABT& node);
 
+/**
+ * Assert that the given path does not contain numeric components.
+ **/
+void assertSupportedPath(const std::string& path);
+
+/**
+ * For each field added, constructs the ABT based on whether it is a renamed field or an expression
+ * to be computed.
+ **/
+void processAddFields(const std::string& rootProjection,
+                      bool isAddingFields,
+                      StringMap<boost::intrusive_ptr<Expression>> addFieldsMap,
+                      AlgebrizerContext& ctx,
+                      FieldMapBuilder& builder);
+
+/**
+ * Constructs the ABT based on whether the given expression is a renamed field or one to be
+ * computed.
+ **/
+void processExpression(AlgebrizerContext& ctx,
+                       FieldMapBuilder& builder,
+                       bool isAddingFields,
+                       boost::intrusive_ptr<Expression> expression,
+                       const std::string& path);
 }  // namespace mongo::optimizer
Index: src/mongo/db/pipeline/document_source_single_document_transformation.h
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/mongo/db/pipeline/document_source_single_document_transformation.h b/src/mongo/db/pipeline/document_source_single_document_transformation.h
--- a/src/mongo/db/pipeline/document_source_single_document_transformation.h	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
+++ b/src/mongo/db/pipeline/document_source_single_document_transformation.h	(date 1666037666111)
@@ -65,6 +65,20 @@
         bool independentOfAnyCollection,
         std::unique_ptr<projection_ast::Projection> projectionAST);
 
+    DocumentSourceSingleDocumentTransformation(
+        const boost::intrusive_ptr<ExpressionContext>& pExpCtx,
+        std::unique_ptr<TransformerInterface> parsedTransform,
+        StringData name,
+        bool independentOfAnyCollection,
+        boost::intrusive_ptr<Expression> expression);
+
+    DocumentSourceSingleDocumentTransformation(
+        const boost::intrusive_ptr<ExpressionContext>& pExpCtx,
+        std::unique_ptr<TransformerInterface> parsedTransform,
+        StringData name,
+        bool independentOfAnyCollection,
+        StringMap<boost::intrusive_ptr<Expression>> expressionMap);
+
     // virtuals from DocumentSource
     const char* getSourceName() const final;
 
@@ -113,6 +127,22 @@
         return _projectionAST != nullptr;
     }
 
+    const Expression* getExpression() const {
+        return _expression.get();
+    }
+
+    bool hasExpression() const {
+        return _expression != nullptr;
+    }
+
+    StringMap<boost::intrusive_ptr<Expression>> getAddFieldsMap() const {
+        return _addFieldsMap;
+    }
+
+    bool hasAddFieldsMap() const {
+        return !_addFieldsMap.empty();
+    }
+
     /**
      * Extract computed projection(s) depending on the 'oldName' argument if the transformation is
      * of type inclusion projection or computed projection. Extraction is not allowed if the name of
@@ -170,6 +200,12 @@
 
     // Stores a projectionAST if the DocumentSource is a projection/addFields, otherwise is nullptr
     std::unique_ptr<projection_ast::Projection> _projectionAST = nullptr;
+
+    // Stores an expression if the DocumentSource is a replaceRoot, otherwise is nullptr.
+    boost::intrusive_ptr<Expression> _expression = nullptr;
+
+    // Stores a map from string to expression for every new field from the $addFields stage.
+    StringMap<boost::intrusive_ptr<Expression>> _addFieldsMap;
 };
 
 }  // namespace mongo
Index: src/mongo/db/pipeline/abt/utils.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/mongo/db/pipeline/abt/utils.cpp b/src/mongo/db/pipeline/abt/utils.cpp
--- a/src/mongo/db/pipeline/abt/utils.cpp	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
+++ b/src/mongo/db/pipeline/abt/utils.cpp	(date 1666037666023)
@@ -249,4 +249,171 @@
     return PathToIntervalTransport{}.convert(node);
 }
 
+void assertSupportedPath(const std::string& path) {
+    uassert(ErrorCodes::InternalErrorNotSupported,
+            "Projection contains unsupported numeric path component",
+            !FieldRef(path).hasNumericPathComponents());
+}
+
+void addPathToRenamedPaths(std::string path,
+                           boost::intrusive_ptr<Expression> expr,
+                           StringMap<std::string>* renamedPaths) {
+    auto exprPath = path;
+    auto exprComputedPaths = expr->getComputedPaths(exprPath);
+
+    for (auto&& rename : exprComputedPaths.renames) {
+        (*renamedPaths)[rename.first] = rename.second;
+    }
+}
+
+void addPathToComputedPaths(std::string exprPath,
+                            boost::intrusive_ptr<Expression> expr,
+                            OrderedPathSet* computedPaths) {
+    auto exprComputedPaths = expr->getComputedPaths(exprPath);
+    computedPaths->insert(exprComputedPaths.paths.begin(), exprComputedPaths.paths.end());
+}
+
+/**
+ * Returns a map where the keys are strings representing the old name and the values are strings
+ * representing the new name of the path for added fields that are renames.
+ **/
+StringMap<std::string> extractRenamedPaths(
+    StringMap<boost::intrusive_ptr<Expression>> addFieldsMap) {
+    StringMap<std::string> renamedPaths;
+    for (const auto& elem : addFieldsMap) {
+        addPathToRenamedPaths(elem.first, elem.second, &renamedPaths);
+    }
+
+    return renamedPaths;
+}
+
+/**
+ * Returns an OrderedPathSet of all the computed paths for each added field.
+ **/
+OrderedPathSet extractComputedPaths(StringMap<boost::intrusive_ptr<Expression>> addFieldsMap) {
+    OrderedPathSet computedPaths;
+    for (const auto& elem : addFieldsMap) {
+        addPathToComputedPaths(elem.first, elem.second, &computedPaths);
+    }
+
+    return computedPaths;
+}
+
+/**
+ * Returns true if the given path is contained within the key set of the renamed paths map, false
+ * otherwise.
+ **/
+bool isRenamedPath(std::string path, StringMap<std::string> renamedPaths) {
+    return renamedPaths.find(path) != renamedPaths.end();
+}
+
+/**
+ * Handles the case where the projection is a renamed path.
+ **/
+std::string processRenamedPathProjection(AlgebrizerContext& ctx,
+                                         std::string renamedPath,
+                                         std::string path) {
+    std::string renamedProjName = ctx.getNextId("projRenamedPath");
+
+    ABT fieldPath = translateFieldPath(
+        FieldPath(renamedPath),
+        make<PathIdentity>(),
+        [](const std::string& fieldName, const bool isLastElement, ABT input) {
+            return make<PathGet>(
+                fieldName,
+                isLastElement ? std::move(input)
+                              : make<PathTraverse>(std::move(input), PathTraverse::kUnlimited));
+        });
+
+    auto entry = ctx.getNode();
+    ctx.setNode<EvaluationNode>(
+        entry._rootProjection,
+        renamedProjName,
+        make<EvalPath>(std::move(fieldPath), make<Variable>(entry._rootProjection)),
+        std::move(entry._node));
+
+    return renamedProjName;
+}
+
+/**
+ * Handles the case where the projection is a computed expression.
+ **/
+std::string processGeneralExprProjection(AlgebrizerContext& ctx,
+                                         const std::string& rootProjection,
+                                         std::string path,
+                                         boost::intrusive_ptr<Expression> expr) {
+    const FieldPath computedPath(path);
+
+    auto entry = ctx.getNode();
+    std::string getProjName = ctx.getNextId("projGetPath");
+
+    ABT getExpr = generateAggExpression(expr.get(), rootProjection, getProjName);
+
+    ctx.setNode<EvaluationNode>(
+        std::move(entry._rootProjection), getProjName, std::move(getExpr), std::move(entry._node));
+
+    return getProjName;
+}
+
+void builderIntegratePath(FieldMapBuilder& builder,
+                          std::string fieldPath,
+                          std::string varName,
+                          bool isAddingFields) {
+    assertSupportedPath(fieldPath);
+    builder.integrateFieldPath(
+        FieldPath(fieldPath),
+        [&varName, &isAddingFields](const bool isLastElement, FieldMapEntry& entry) {
+            if (!isAddingFields) {
+                entry._hasKeep = true;
+            }
+            if (isLastElement) {
+                entry._constVarName = varName;
+                entry._hasTrailingDefault = true;
+            }
+        });
+}
+
+void processAddFields(const std::string& rootProjection,
+                      const bool isAddingFields,
+                      StringMap<boost::intrusive_ptr<Expression>> addFieldsMap,
+                      AlgebrizerContext& ctx,
+                      FieldMapBuilder& builder) {
+    StringMap<std::string> renamedPaths = extractRenamedPaths(addFieldsMap);
+
+    for (auto&& pair : addFieldsMap) {
+        std::string varName;
+        if (isRenamedPath(pair.first, renamedPaths)) {
+            varName = processRenamedPathProjection(ctx, renamedPaths[pair.first], pair.first);
+        } else {
+            varName = processGeneralExprProjection(ctx, rootProjection, pair.first, pair.second);
+        }
+
+        builderIntegratePath(builder, pair.first, varName, isAddingFields);
+    }
+}
+
+void processExpression(AlgebrizerContext& ctx,
+                       FieldMapBuilder& builder,
+                       const bool isAddingFields,
+                       boost::intrusive_ptr<Expression> expression,
+                       const std::string& path) {
+    OrderedPathSet computedPaths;
+    StringMap<std::string> renamedPaths;
+    addPathToComputedPaths(path, expression, &computedPaths);
+    addPathToRenamedPaths(path, expression, &renamedPaths);
+
+    for (const auto& renamedPathEntry : renamedPaths) {
+        std::string renamedProjName =
+            processRenamedPathProjection(ctx, renamedPathEntry.second, path);
+        builderIntegratePath(builder, renamedPathEntry.first, renamedProjName, isAddingFields);
+    }
+
+    for (const std::string& computedPathStr : computedPaths) {
+        std::string& rootProjection = ctx.getNode()._rootProjection;
+        std::string getProjName =
+            processGeneralExprProjection(ctx, rootProjection, computedPathStr, expression);
+        builderIntegratePath(builder, computedPathStr, getProjName, isAddingFields);
+    }
+}
+
 }  // namespace mongo::optimizer
Index: jstests/cqf/computed_projection.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/jstests/cqf/computed_projection.js b/jstests/cqf/computed_projection.js
new file mode 100644
--- /dev/null	(date 1666037665883)
+++ b/jstests/cqf/computed_projection.js	(date 1666037665883)
@@ -0,0 +1,62 @@
+(function() {
+"use strict";
+
+const coll = db.computed_projection;
+coll.drop();
+
+assert.commandWorked(coll.insert({_id: 0, b: 7, c: {d: 5}, z: 1}));
+
+// $project rename through addFields: since '$z' is a single element, it will be considered a
+// renamed path
+let res = coll.aggregate([{$project: {'c.d': '$z'}}]).toArray();
+assert.eq([{_id: 0, c: {d: 1}}], res);
+
+// Inclusion $project with computed field
+res = coll.aggregate([{$project: {a: {$add: ['$c.d', 2]}, 'c.d': 1}}]).toArray();
+assert.docEq([{_id: 0, a: 7, c: {d: 5}}], res);
+
+// $project rename dotted paths
+coll.drop();
+assert.commandWorked(coll.insert({_id: 0, a: {b: {c: 1}}, x: {y: {z: 2}}}));
+res = coll.aggregate([{$project: {'a.b.c': '$x.y.z'}}]).toArray();
+assert.eq([{_id: 0, a: {b: {c: 2}}}], res);
+
+// computed $project to an array
+res = coll.aggregate([
+              {$project: {a: {$literal: [1, 2, 3, 4]}}},
+              {$match: {a: {$elemMatch: {$gte: 2, $lte: 3}}}}
+          ])
+          .toArray();
+assert.eq([{_id: 0, a: [1, 2, 3, 4]}], res);
+
+// $project then match
+coll.drop();
+assert.commandWorked(coll.insert({_id: 0, a: 1, b: 2, c: 3}));    // neither case in the or is true
+assert.commandWorked(coll.insert({_id: 1, a: 1, b: 2, c: 2}));    // c = 2
+assert.commandWorked(coll.insert({_id: 2, a: 10, b: 10, c: 3}));  // s >= 10
+assert.commandWorked(coll.insert({_id: 3, a: 10, b: 10, c: 2}));  // c = 2 and s >= 10
+res = coll.aggregate([
+              {$project: {s: {$add: ['$a', '$b']}, c: 1}},
+              {$match: {$or: [{c: 2}, {s: {$gte: 10}}]}}
+          ])
+          .toArray();
+assert.eq([{_id: 1, c: 2, s: 3}, {_id: 2, c: 3, s: 20}, {_id: 3, c: 2, s: 20}], res);
+
+// $project with deeply nested path that exists (i.e. will be overwritten)
+coll.drop();
+assert.commandWorked(coll.insert({_id: 0, 'a1': {b: {c: 4}}, a: {b: {c: {d: {e: "foo"}}}}}));
+res = coll.aggregate([{$project: {'a1.b.c': 1, 'a.b.c.d.e': 'str'}}]).toArray();
+assert.docEq([{_id: 0, a: {b: {c: {d: {e: "str"}}}}, 'a1': {b: {c: 4}}}], res);
+
+// $project with deeply nested path that doesn't exist
+coll.drop();
+assert.commandWorked(coll.insert({_id: 0, 'a1': {b: {c: 4}}}));
+res = coll.aggregate([{$project: {'a1.b.c': 1, 'a.b.c.d.e': 'str'}}]).toArray();
+assert.docEq([{_id: 0, a: {b: {c: {d: {e: "str"}}}}, 'a1': {b: {c: 4}}}], res);
+
+// $project with computed array
+coll.drop();
+assert.commandWorked(coll.insert({_id: 0, b: 1, c: 2}));
+res = coll.aggregate([{$project: {a: ['$b', '$c']}}]).toArray();
+assert.eq([{_id: 0, a: [1, 2]}], res);
+}());
Index: src/mongo/db/pipeline/abt/field_map_builder.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/mongo/db/pipeline/abt/field_map_builder.cpp b/src/mongo/db/pipeline/abt/field_map_builder.cpp
--- a/src/mongo/db/pipeline/abt/field_map_builder.cpp	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
+++ b/src/mongo/db/pipeline/abt/field_map_builder.cpp	(date 1666037665951)
@@ -31,28 +31,49 @@
 
 namespace mongo::optimizer {
 
+/**
+ * Returns the field map entry with the given path, if it exists in the given entries vector.
+ **/
+boost::optional<FieldMapEntry*> findInChildren(std::string path,
+                                               std::vector<FieldMapEntry*> entries) {
+    for (const auto& elem : entries) {
+        if (elem->_fieldName == path) {
+            return elem;
+        }
+    }
+    return {};
+}
+
 void FieldMapBuilder::integrateFieldPath(
     const FieldPath& fieldPath, const std::function<void(const bool, FieldMapEntry&)>& fn) {
-    std::string path = kRootElement;
-    auto it = _fieldMap.emplace(path, kRootElement);
+    if (!_root.has_value()) {
+        _root.emplace(new FieldMapEntry(kRootElement));
+    }
+    auto currPathElem = _root.value();
+
     const size_t fieldPathLength = fieldPath.getPathLength();
 
     for (size_t i = 0; i < fieldPathLength; i++) {
-        const std::string& fieldName = fieldPath.getFieldName(i).toString();
-        path += '.' + fieldName;
+        const std::string fieldName = fieldPath.getFieldName(i).toString();
 
-        it.first->second._childPaths.insert(path);
-        it = _fieldMap.emplace(path, fieldName);
-        fn(i == fieldPathLength - 1, it.first->second);
+        auto maybeChild = findInChildren(fieldName, currPathElem->_children);
+
+        if (maybeChild.has_value()) {
+            currPathElem = maybeChild.value();
+        } else {
+            currPathElem->_children.push_back(new FieldMapEntry(fieldName));
+            currPathElem->_childPaths.insert(fieldName);
+            currPathElem = (currPathElem->_children.back());
+            fn(i == fieldPathLength - 1, *currPathElem);
+        }
     }
 }
 
 boost::optional<ABT> FieldMapBuilder::generateABT() const {
-    auto it = _fieldMap.find(kRootElement);
-    if (it == _fieldMap.cend()) {
+    if (!_root.has_value()) {
         return {};
     }
-    return generateABTForField(it->second);
+    return generateABTForField(*_root.value());
 }
 
 ABT FieldMapBuilder::generateABTForField(const FieldMapEntry& entry) const {
@@ -65,7 +86,7 @@
     std::map<std::string, std::string> varMap;
 
     for (const std::string& childField : entry._childPaths) {
-        const FieldMapEntry& childEntry = _fieldMap.at(childField);
+        FieldMapEntry childEntry = *findInChildren(childField, entry._children).value();
         const std::string& childFieldName = childEntry._fieldName;
 
         if (childEntry._hasKeep) {
@@ -107,9 +128,8 @@
     // By this point we have constructed an ABT which contains the appropriate keep/drop logic up to
     // and including the child paths of 'entry'. For example, if 'entry' represents path 'a' with
     // children 'b' and 'c', paths 'a.b' and 'a.c' are appropriately kept or dropped.
-    for (const std::string& childPath : entry._childPaths) {
-        const FieldMapEntry& childEntry = _fieldMap.at(childPath);
-
+    for (const std::string& childField : entry._childPaths) {
+        FieldMapEntry childEntry = *findInChildren(childField, entry._children).value();
         // Recursively construct ABTs for the paths below each child entry.
         ABT childResult = generateABTForField(childEntry);
         if (!childResult.is<PathIdentity>()) {
Index: src/mongo/db/pipeline/abt/field_map_builder.h
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/mongo/db/pipeline/abt/field_map_builder.h b/src/mongo/db/pipeline/abt/field_map_builder.h
--- a/src/mongo/db/pipeline/abt/field_map_builder.h	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
+++ b/src/mongo/db/pipeline/abt/field_map_builder.h	(date 1666037665967)
@@ -57,10 +57,8 @@
     bool _hasDrop = false;
     std::string _constVarName;
 
-    // TODO SERVER-68516: Consider maintaining children as a vector of FieldMapEntry's. Then we can
-    // remove the _fieldMap member of FieldMapBuilder.
-    // Child paths are potentially dotted field paths.
     OrderedPathSet _childPaths;
+    std::vector<FieldMapEntry*> _children = {};
 };
 
 class FieldMapBuilder {
@@ -100,8 +98,7 @@
     const ProjectionName& _rootProjName;
     const bool _isRootSameAsScanProj;
 
-    // Maps from potentially dotted field path to FieldMapEntry.
-    opt::unordered_map<std::string, FieldMapEntry> _fieldMap;
+    boost::optional<FieldMapEntry*> _root;
 };
 
 }  // namespace mongo::optimizer
Index: src/mongo/db/pipeline/document_source_single_document_transformation.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/mongo/db/pipeline/document_source_single_document_transformation.cpp b/src/mongo/db/pipeline/document_source_single_document_transformation.cpp
--- a/src/mongo/db/pipeline/document_source_single_document_transformation.cpp	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
+++ b/src/mongo/db/pipeline/document_source_single_document_transformation.cpp	(date 1666037666091)
@@ -65,6 +65,30 @@
       _isIndependentOfAnyCollection(isIndependentOfAnyCollection),
       _projectionAST(std::move(projectionAST)) {}
 
+DocumentSourceSingleDocumentTransformation::DocumentSourceSingleDocumentTransformation(
+    const boost::intrusive_ptr<ExpressionContext>& pExpCtx,
+    std::unique_ptr<TransformerInterface> parsedTransform,
+    StringData name,
+    bool isIndependentOfAnyCollection,
+    boost::intrusive_ptr<Expression> expression)
+    : DocumentSource(name, pExpCtx),
+      _parsedTransform(std::move(parsedTransform)),
+      _name(name.toString()),
+      _isIndependentOfAnyCollection(isIndependentOfAnyCollection),
+      _expression(std::move(expression)) {}
+
+DocumentSourceSingleDocumentTransformation::DocumentSourceSingleDocumentTransformation(
+    const boost::intrusive_ptr<ExpressionContext>& pExpCtx,
+    std::unique_ptr<TransformerInterface> parsedTransform,
+    StringData name,
+    bool isIndependentOfAnyCollection,
+    StringMap<boost::intrusive_ptr<Expression>> expressionMap)
+    : DocumentSource(name, pExpCtx),
+      _parsedTransform(std::move(parsedTransform)),
+      _name(name.toString()),
+      _isIndependentOfAnyCollection(isIndependentOfAnyCollection),
+      _addFieldsMap(std::move(expressionMap)) {}
+
 const char* DocumentSourceSingleDocumentTransformation::getSourceName() const {
     return _name.c_str();
 }
Index: jstests/cqf/add_fields.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/jstests/cqf/add_fields.js b/jstests/cqf/add_fields.js
new file mode 100644
--- /dev/null	(date 1666037665871)
+++ b/jstests/cqf/add_fields.js	(date 1666037665871)
@@ -0,0 +1,57 @@
+(function() {
+"use strict";
+
+const coll = db.add_fields;
+coll.drop();
+
+// Project rename through $addFields, renamed field exists
+assert.commandWorked(coll.insert({_id: 0, z: 5}));
+let res = coll.aggregate([{$addFields: {a: '$z'}}]).toArray();
+assert.eq([{_id: 0, z: 5, a: 5}], res);
+
+// $addFields simple, added field does not exist
+coll.drop();
+assert.commandWorked(coll.insert({_id: 0, a: 5}));
+res = coll.aggregate([{$addFields: {b: 6}}]).toArray();
+assert.eq([{_id: 0, a: 5, b: 6}], res);
+
+// $addFields simple, added field does exist: should overwrite value
+coll.drop();
+assert.commandWorked(coll.insert({_id: 0, a: 5}));
+res = coll.aggregate([{$addFields: {a: 6}}]).toArray();
+assert.eq([{_id: 0, a: 6}], res);
+
+// Nested field
+coll.drop();
+assert.commandWorked(coll.insert({_id: 0, c: 6}));
+res = coll.aggregate([{$addFields: {a: {b: 5}}}]).toArray();
+assert.eq([{_id: 0, c: 6, a: {b: 5}}], res);
+
+// Dotted field
+coll.drop();
+assert.commandWorked(coll.insert({_id: 0, c: 6}));
+res = coll.aggregate([{$addFields: {"a.b": 5}}]).toArray();
+assert.eq([{_id: 0, c: 6, a: {b: 5}}], res);
+
+// More than one nested field
+coll.drop();
+assert.commandWorked(coll.insert({_id: 0, c: 6}));
+res = coll.aggregate([{$addFields: {a: {b: 5, c: 6}}}]).toArray();
+assert.eq([{_id: 0, c: 6, a: {b: 5, c: 6}}], res);
+
+// More than one dotted field
+coll.drop();
+assert.commandWorked(coll.insert({_id: 0, c: 6}));
+res = coll.aggregate([{$addFields: {"a.b": 5, "a.c": 6}}]).toArray();
+assert.eq([{_id: 0, c: 6, a: {b: 5, c: 6}}], res);
+
+// Two added fields, one has nested field
+coll.drop();
+assert.commandWorked(coll.insert({_id: 0, d: 7}));
+res = coll.aggregate([{$addFields: {a: {b: 5}, c: 6}}]).toArray();
+assert.docEq([{_id: 0, d: 7, c: 6, a: {b: 5}}], res);
+
+// Three added fields
+res = coll.aggregate([{$addFields: {a: {b: 5}, c: 6, e: 7}}]).toArray();
+assert.docEq([{_id: 0, d: 7, c: 6, e: 7, a: {b: 5}}], res);
+}());
Index: src/mongo/db/pipeline/abt/canonical_query_translation.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/mongo/db/pipeline/abt/canonical_query_translation.cpp b/src/mongo/db/pipeline/abt/canonical_query_translation.cpp
--- a/src/mongo/db/pipeline/abt/canonical_query_translation.cpp	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
+++ b/src/mongo/db/pipeline/abt/canonical_query_translation.cpp	(date 1666037665907)
@@ -33,7 +33,6 @@
 #include "mongo/db/pipeline/abt/collation_translation.h"
 #include "mongo/db/pipeline/abt/match_expression_visitor.h"
 #include "mongo/db/pipeline/abt/projection_ast_visitor.h"
-#include "mongo/db/pipeline/abt/transformer_visitor.h"
 
 namespace mongo::optimizer {
 
@@ -57,18 +56,7 @@
     }
 
     if (auto proj = canonicalQuery.getProj()) {
-        // TODO: SERVER-68516: this is a workaround to support computed projection
-        // in the short term. Remove this workaround and complete projection translation via
-        // projeciton_ast_visitor instead.
-        try {
-            translateProjection(ctx, proj);
-        } catch (const DBException& ex) {
-            if (ex.code() == ErrorCodes::InternalErrorNotSupported) {
-                translateProjection(ctx, scanProjName, canonicalQuery.getExpCtx(), proj);
-            } else {
-                throw;
-            }
-        }
+        translateProjection(ctx, proj);
     }
 
     auto skipAmount = canonicalQuery.getFindCommandRequest().getSkip();
Index: src/mongo/db/pipeline/document_source_add_fields.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/mongo/db/pipeline/document_source_add_fields.cpp b/src/mongo/db/pipeline/document_source_add_fields.cpp
--- a/src/mongo/db/pipeline/document_source_add_fields.cpp	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
+++ b/src/mongo/db/pipeline/document_source_add_fields.cpp	(date 1666037666059)
@@ -49,12 +49,44 @@
                          DocumentSourceAddFields::createFromBson,
                          AllowedWithApiStrict::kAlways);
 
+void parseBSONObjHelper(const BSONObj& spec,
+                        const intrusive_ptr<ExpressionContext>& expCtx,
+                        StringMap<boost::intrusive_ptr<Expression>>& addFieldsMap,
+                        std::string pathToObj) {
+    for (auto elem : spec) {
+        auto fieldPath =
+            pathToObj + (pathToObj == "" ? "" : ".") + elem.fieldNameStringData().toString();
+
+        if (elem.type() == BSONType::Object) {
+            bool objIsExpression = elem.Obj().firstElementFieldName()[0] == '$';
+            if (objIsExpression) {
+                addFieldsMap[fieldPath] = Expression::parseExpression(
+                    expCtx.get(), elem.Obj(), expCtx->variablesParseState);
+            } else {
+                parseBSONObjHelper(elem.Obj(), expCtx, addFieldsMap, fieldPath);
+            }
+        } else {
+            addFieldsMap[fieldPath] =
+                Expression::parseOperand(expCtx.get(), elem, expCtx->variablesParseState);
+        }
+    }
+}
+
+StringMap<boost::intrusive_ptr<Expression>> parseBSONObj(
+    const BSONObj& spec, const intrusive_ptr<ExpressionContext>& expCtx) {
+    StringMap<boost::intrusive_ptr<Expression>> addFieldsMap;
+    parseBSONObjHelper(spec, expCtx, addFieldsMap, "");
+    return addFieldsMap;
+}
+
 intrusive_ptr<DocumentSource> DocumentSourceAddFields::create(
     BSONObj addFieldsSpec,
     const intrusive_ptr<ExpressionContext>& expCtx,
     StringData userSpecifiedName) {
 
     const bool isIndependentOfAnyCollection = false;
+    StringMap<boost::intrusive_ptr<Expression>> addFieldsMap = parseBSONObj(addFieldsSpec, expCtx);
+
     intrusive_ptr<DocumentSourceSingleDocumentTransformation> addFields(
         new DocumentSourceSingleDocumentTransformation(
             expCtx,
@@ -68,7 +100,9 @@
                 }
             }(),
             userSpecifiedName == kStageName ? kStageName : kAliasNameSet,
-            isIndependentOfAnyCollection));
+            isIndependentOfAnyCollection,
+            addFieldsMap));
+
     return addFields;
 }
 
@@ -82,7 +116,8 @@
         expCtx,
         projection_executor::AddFieldsProjectionExecutor::create(expCtx, fieldPath, expr),
         kStageName,
-        isIndependentOfAnyCollection);
+        isIndependentOfAnyCollection,
+        expr);
 }
 
 intrusive_ptr<DocumentSource> DocumentSourceAddFields::createFromBson(
@@ -94,7 +129,6 @@
             str::stream() << specifiedName << " specification stage must be an object, got "
                           << typeName(elem.type()),
             elem.type() == Object);
-
     return DocumentSourceAddFields::create(elem.Obj(), expCtx, specifiedName);
 }
 }  // namespace mongo
Index: src/mongo/db/pipeline/document_source_replace_root.cpp
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/mongo/db/pipeline/document_source_replace_root.cpp b/src/mongo/db/pipeline/document_source_replace_root.cpp
--- a/src/mongo/db/pipeline/document_source_replace_root.cpp	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
+++ b/src/mongo/db/pipeline/document_source_replace_root.cpp	(date 1666037666075)
@@ -107,7 +107,8 @@
             newRootExpression,
             (stageName == kStageName) ? "'newRoot' expression " : "'replacement document' "),
         kStageName.rawData(),
-        isIndependentOfAnyCollection);
+        isIndependentOfAnyCollection,
+        newRootExpression);
 }
 
 boost::intrusive_ptr<DocumentSource> DocumentSourceReplaceRoot::create(
@@ -120,6 +121,7 @@
         std::make_unique<ReplaceRootTransformation>(
             expCtx, newRootExpression, std::move(errMsgContextForNonObjects)),
         kStageName.rawData(),
-        isIndependentOfAnyCollection);
+        isIndependentOfAnyCollection,
+        newRootExpression);
 }
 }  // namespace mongo
Index: src/mongo/db/pipeline/SConscript
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/mongo/db/pipeline/SConscript b/src/mongo/db/pipeline/SConscript
--- a/src/mongo/db/pipeline/SConscript	(revision 57746791113c36c0c6a010da2deb580ea3cb97ad)
+++ b/src/mongo/db/pipeline/SConscript	(date 1666037666143)
@@ -232,7 +232,6 @@
         'abt/field_map_builder.cpp',
         'abt/match_expression_visitor.cpp',
         'abt/projection_ast_visitor.cpp',
-        'abt/transformer_visitor.cpp',
         'abt/utils.cpp',
         'accumulator_internal_construct_stats.cpp',
         'document_source.cpp',
