Projection parse(boost::intrusive_ptr<ExpressionContext> expCtx,
                 const BSONObj& obj,
                 const MatchExpression* const query,
                 const BSONObj& queryObj,
                 ProjectionPolicies policies) {
    if (!policies.findOnlyFeaturesAllowed()) {
        // In agg-style syntax it is illegal to have an empty projection specification.
        uassert(51272, "projection specification must have at least one field", !obj.isEmpty());
    }

    ProjectionPathASTNode root;

    ParseContext ctx{expCtx, query, queryObj, obj, policies};

    for (auto&& elem : obj) {
        ctx.idSpecified |=
            elem.fieldNameStringData() == "_id" || elem.fieldNameStringData().startsWith("_id.");
        parseElement(&ctx, elem, boost::none, &root);
    }

    // If we have not yet determined the type, we must fall back to the defaults for ambiguous
    // projections.
    if (!ctx.type) {
        ctx.type = computeProjectionType(ctx.hasFindSlice,
                                         ctx.hasElemMatch,
                                         ctx.hasMeta,
                                         ctx.policies,
                                         ctx.idIncludedEntirely,
                                         ctx.idExcludedEntirely);
    }
    invariant(ctx.type);

    if (!ctx.idSpecified) {
        if (policies.idPolicy == ProjectionPolicies::DefaultIdPolicy::kIncludeId &&
            *ctx.type == ProjectType::kInclusion) {
            // Add a node to the root indicating that _id is included.
            addNodeAtPath(&root, "_id", std::make_unique<BooleanConstantASTNode>(true));
        } else if (policies.idPolicy == ProjectionPolicies::DefaultIdPolicy::kExcludeId &&
                   *ctx.type == ProjectType::kExclusion) {
            // Add a node to the root indicating that _id is not included.
            addNodeAtPath(&root, "_id", std::make_unique<BooleanConstantASTNode>(false));
        }
    }

    if (*ctx.type == ProjectType::kExclusion && ctx.idSpecified && ctx.idIncludedEntirely) {
        // The user explicitly included _id in an exclusion projection. This is legal syntax, but
        // the node indicating that _id is included doesn't need to be in the tree.
        invariant(root.removeChild("_id"));
    }

    return Projection{std::move(root), *ctx.type};
}
Projection parse(boost::intrusive_ptr<ExpressionContext> expCtx,
                 const BSONObj& obj,
                 const MatchExpression* const query,
                 const BSONObj& queryObj,
                 ProjectionPolicies policies) {
    if (!policies.findOnlyFeaturesAllowed()) {
         
        uassert(51272, "projection specification must have at least one field", !obj.isEmpty());
    }

    ProjectionPathASTNode root;

    ParseContext ctx{expCtx, query, queryObj, obj, policies};

    for (auto&& elem : obj) {
        ctx.idSpecified |=
            elem.fieldNameStringData() == "_id" || elem.fieldNameStringData().startsWith("_id.");
        parseElement(&ctx, elem, boost::none, &root);
    }

     
     
    if (!ctx.type) {
        ctx.type = computeProjectionType(ctx.hasFindSlice,
                                         ctx.hasElemMatch,
                                         ctx.hasMeta,
                                         ctx.policies,
                                         ctx.idIncludedEntirely,
                                         ctx.idExcludedEntirely);
    }
    invariant(ctx.type);

    if (!ctx.idSpecified) {
        if (policies.idPolicy == ProjectionPolicies::DefaultIdPolicy::kIncludeId &&
            *ctx.type == ProjectType::kInclusion) {
             
            addNodeAtPath(&root, "_id", std::make_unique<BooleanConstantASTNode>(true));
        } else if (policies.idPolicy == ProjectionPolicies::DefaultIdPolicy::kExcludeId &&
                   *ctx.type == ProjectType::kExclusion) {
             
            addNodeAtPath(&root, "_id", std::make_unique<BooleanConstantASTNode>(false));
        }
    }

    if (*ctx.type == ProjectType::kExclusion && ctx.idSpecified && ctx.idIncludedEntirely) {
         
         
        invariant(root.removeChild("_id"));
    }

    return Projection{std::move(root), *ctx.type};
}