jj_lib/
revset.rs

1// Copyright 2021 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![expect(missing_docs)]
16
17use std::any::Any;
18use std::collections::HashMap;
19use std::collections::hash_map;
20use std::convert::Infallible;
21use std::fmt;
22use std::ops::ControlFlow;
23use std::ops::Range;
24use std::sync::Arc;
25use std::sync::LazyLock;
26
27use itertools::Itertools as _;
28use thiserror::Error;
29
30use crate::backend::BackendError;
31use crate::backend::ChangeId;
32use crate::backend::CommitId;
33use crate::commit::Commit;
34use crate::dsl_util;
35use crate::dsl_util::collect_similar;
36use crate::fileset;
37use crate::fileset::FilesetDiagnostics;
38use crate::fileset::FilesetExpression;
39use crate::graph::GraphNode;
40use crate::id_prefix::IdPrefixContext;
41use crate::id_prefix::IdPrefixIndex;
42use crate::index::ResolvedChangeTargets;
43use crate::object_id::HexPrefix;
44use crate::object_id::PrefixResolution;
45use crate::op_store::RefTarget;
46use crate::op_store::RemoteRefState;
47use crate::op_walk;
48use crate::ref_name::RemoteName;
49use crate::ref_name::RemoteRefSymbol;
50use crate::ref_name::RemoteRefSymbolBuf;
51use crate::ref_name::WorkspaceName;
52use crate::ref_name::WorkspaceNameBuf;
53use crate::repo::ReadonlyRepo;
54use crate::repo::Repo;
55use crate::repo::RepoLoaderError;
56use crate::repo_path::RepoPathUiConverter;
57use crate::revset_parser;
58pub use crate::revset_parser::BinaryOp;
59pub use crate::revset_parser::ExpressionKind;
60pub use crate::revset_parser::ExpressionNode;
61pub use crate::revset_parser::FunctionCallNode;
62pub use crate::revset_parser::RevsetAliasesMap;
63pub use crate::revset_parser::RevsetDiagnostics;
64pub use crate::revset_parser::RevsetParseError;
65pub use crate::revset_parser::RevsetParseErrorKind;
66pub use crate::revset_parser::UnaryOp;
67pub use crate::revset_parser::expect_literal;
68pub use crate::revset_parser::parse_program;
69pub use crate::revset_parser::parse_program_with_modifier;
70pub use crate::revset_parser::parse_symbol;
71use crate::store::Store;
72use crate::str_util::StringExpression;
73use crate::str_util::StringPattern;
74use crate::time_util::DatePattern;
75use crate::time_util::DatePatternContext;
76
77/// Error occurred during symbol resolution.
78#[derive(Debug, Error)]
79pub enum RevsetResolutionError {
80    #[error("Revision `{name}` doesn't exist")]
81    NoSuchRevision {
82        name: String,
83        candidates: Vec<String>,
84    },
85    #[error("Workspace `{}` doesn't have a working-copy commit", name.as_symbol())]
86    WorkspaceMissingWorkingCopy { name: WorkspaceNameBuf },
87    #[error("An empty string is not a valid revision")]
88    EmptyString,
89    #[error("Commit ID prefix `{0}` is ambiguous")]
90    AmbiguousCommitIdPrefix(String),
91    #[error("Change ID prefix `{0}` is ambiguous")]
92    AmbiguousChangeIdPrefix(String),
93    #[error("Change ID `{symbol}` is divergent")]
94    DivergentChangeId {
95        symbol: String,
96        visible_targets: Vec<(usize, CommitId)>,
97    },
98    #[error("Name `{symbol}` is conflicted")]
99    ConflictedRef {
100        kind: &'static str,
101        symbol: String,
102        targets: Vec<CommitId>,
103    },
104    #[error("Unexpected error from commit backend")]
105    Backend(#[source] BackendError),
106    #[error(transparent)]
107    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
108}
109
110/// Error occurred during revset evaluation.
111#[derive(Debug, Error)]
112pub enum RevsetEvaluationError {
113    #[error("Unexpected error from commit backend")]
114    Backend(#[from] BackendError),
115    #[error(transparent)]
116    Other(Box<dyn std::error::Error + Send + Sync>),
117}
118
119impl RevsetEvaluationError {
120    // TODO: Create a higher-level error instead of putting non-BackendErrors in a
121    // BackendError
122    pub fn into_backend_error(self) -> BackendError {
123        match self {
124            Self::Backend(err) => err,
125            Self::Other(err) => BackendError::Other(err),
126        }
127    }
128}
129
130// assumes index has less than u64::MAX entries.
131pub const GENERATION_RANGE_FULL: Range<u64> = 0..u64::MAX;
132pub const GENERATION_RANGE_EMPTY: Range<u64> = 0..0;
133
134pub const PARENTS_RANGE_FULL: Range<u32> = 0..u32::MAX;
135
136/// Global flag applied to the entire expression.
137///
138/// The core revset engine doesn't use this value. It's up to caller to
139/// interpret it to change the evaluation behavior.
140#[derive(Clone, Copy, Debug, Eq, PartialEq)]
141pub enum RevsetModifier {
142    /// Expression can be evaluated to multiple revisions even if a single
143    /// revision is expected by default.
144    All,
145}
146
147/// Symbol or function to be resolved to `CommitId`s.
148#[derive(Clone, Debug)]
149pub enum RevsetCommitRef {
150    WorkingCopy(WorkspaceNameBuf),
151    WorkingCopies,
152    Symbol(String),
153    RemoteSymbol(RemoteRefSymbolBuf),
154    ChangeId(HexPrefix),
155    CommitId(HexPrefix),
156    Bookmarks(StringExpression),
157    RemoteBookmarks {
158        bookmark: StringExpression,
159        remote: StringExpression,
160        remote_ref_state: Option<RemoteRefState>,
161    },
162    Tags(StringExpression),
163    GitRefs,
164    GitHead,
165}
166
167/// A custom revset filter expression, defined by an extension.
168pub trait RevsetFilterExtension: std::fmt::Debug + Any + Send + Sync {
169    /// Returns true iff this filter matches the specified commit.
170    fn matches_commit(&self, commit: &Commit) -> bool;
171}
172
173impl dyn RevsetFilterExtension {
174    /// Returns reference of the implementation type.
175    pub fn downcast_ref<T: RevsetFilterExtension>(&self) -> Option<&T> {
176        (self as &dyn Any).downcast_ref()
177    }
178}
179
180#[derive(Clone, Debug)]
181pub enum RevsetFilterPredicate {
182    /// Commits with number of parents in the range.
183    ParentCount(Range<u32>),
184    /// Commits with description matching the pattern.
185    Description(StringExpression),
186    /// Commits with first line of the description matching the pattern.
187    Subject(StringExpression),
188    /// Commits with author name matching the pattern.
189    AuthorName(StringExpression),
190    /// Commits with author email matching the pattern.
191    AuthorEmail(StringExpression),
192    /// Commits with author dates matching the given date pattern.
193    AuthorDate(DatePattern),
194    /// Commits with committer name matching the pattern.
195    CommitterName(StringExpression),
196    /// Commits with committer email matching the pattern.
197    CommitterEmail(StringExpression),
198    /// Commits with committer dates matching the given date pattern.
199    CommitterDate(DatePattern),
200    /// Commits modifying the paths specified by the fileset.
201    File(FilesetExpression),
202    /// Commits containing diffs matching the `text` pattern within the `files`.
203    DiffContains {
204        text: StringExpression,
205        files: FilesetExpression,
206    },
207    /// Commits with conflicts
208    HasConflict,
209    /// Commits that are cryptographically signed.
210    Signed,
211    /// Custom predicates provided by extensions
212    Extension(Arc<dyn RevsetFilterExtension>),
213}
214
215mod private {
216    /// Defines [`RevsetExpression`] variants depending on resolution state.
217    pub trait ExpressionState {
218        type CommitRef: Clone;
219        type Operation: Clone;
220    }
221
222    // Not constructible because these state types just define associated types.
223    #[derive(Debug)]
224    pub enum UserExpressionState {}
225    #[derive(Debug)]
226    pub enum ResolvedExpressionState {}
227}
228
229use private::ExpressionState;
230use private::ResolvedExpressionState;
231use private::UserExpressionState;
232
233impl ExpressionState for UserExpressionState {
234    type CommitRef = RevsetCommitRef;
235    type Operation = String;
236}
237
238impl ExpressionState for ResolvedExpressionState {
239    type CommitRef = Infallible;
240    type Operation = Infallible;
241}
242
243/// [`RevsetExpression`] that may contain unresolved commit refs.
244pub type UserRevsetExpression = RevsetExpression<UserExpressionState>;
245/// [`RevsetExpression`] that never contains unresolved commit refs.
246pub type ResolvedRevsetExpression = RevsetExpression<ResolvedExpressionState>;
247
248/// Tree of revset expressions describing DAG operations.
249///
250/// Use [`UserRevsetExpression`] or [`ResolvedRevsetExpression`] to construct
251/// expression of that state.
252#[derive(Clone, Debug)]
253pub enum RevsetExpression<St: ExpressionState> {
254    None,
255    All,
256    VisibleHeads,
257    /// Visible heads and all referenced commits within the current expression
258    /// scope. Used as the default of `Range`/`DagRange` heads.
259    VisibleHeadsOrReferenced,
260    Root,
261    Commits(Vec<CommitId>),
262    CommitRef(St::CommitRef),
263    Ancestors {
264        heads: Arc<Self>,
265        generation: Range<u64>,
266        parents_range: Range<u32>,
267    },
268    Descendants {
269        roots: Arc<Self>,
270        generation: Range<u64>,
271    },
272    // Commits that are ancestors of "heads" but not ancestors of "roots"
273    Range {
274        roots: Arc<Self>,
275        heads: Arc<Self>,
276        generation: Range<u64>,
277        // Parents range is only used for traversing heads, not roots
278        parents_range: Range<u32>,
279    },
280    // Commits that are descendants of "roots" and ancestors of "heads"
281    DagRange {
282        roots: Arc<Self>,
283        heads: Arc<Self>,
284        // TODO: maybe add generation_from_roots/heads?
285    },
286    // Commits reachable from "sources" within "domain"
287    Reachable {
288        sources: Arc<Self>,
289        domain: Arc<Self>,
290    },
291    Heads(Arc<Self>),
292    /// Heads of the set of commits which are ancestors of `heads` but are not
293    /// ancestors of `roots`, and which also are contained in `filter`.
294    HeadsRange {
295        roots: Arc<Self>,
296        heads: Arc<Self>,
297        parents_range: Range<u32>,
298        filter: Arc<Self>,
299    },
300    Roots(Arc<Self>),
301    ForkPoint(Arc<Self>),
302    Bisect(Arc<Self>),
303    HasSize {
304        candidates: Arc<Self>,
305        count: usize,
306    },
307    Latest {
308        candidates: Arc<Self>,
309        count: usize,
310    },
311    Filter(RevsetFilterPredicate),
312    /// Marker for subtree that should be intersected as filter.
313    AsFilter(Arc<Self>),
314    /// Resolves symbols and visibility at the specified operation.
315    AtOperation {
316        operation: St::Operation,
317        candidates: Arc<Self>,
318    },
319    /// Makes `All` include the commits and their ancestors in addition to the
320    /// visible heads.
321    WithinReference {
322        candidates: Arc<Self>,
323        /// Commits explicitly referenced within the scope.
324        commits: Vec<CommitId>,
325    },
326    /// Resolves visibility within the specified repo state.
327    WithinVisibility {
328        candidates: Arc<Self>,
329        /// Copy of `repo.view().heads()` at the operation.
330        visible_heads: Vec<CommitId>,
331    },
332    Coalesce(Arc<Self>, Arc<Self>),
333    Present(Arc<Self>),
334    NotIn(Arc<Self>),
335    Union(Arc<Self>, Arc<Self>),
336    Intersection(Arc<Self>, Arc<Self>),
337    Difference(Arc<Self>, Arc<Self>),
338}
339
340// Leaf expression that never contains unresolved commit refs, which can be
341// either user or resolved expression
342impl<St: ExpressionState> RevsetExpression<St> {
343    pub fn none() -> Arc<Self> {
344        Arc::new(Self::None)
345    }
346
347    /// Ancestors of visible heads and all referenced commits within the current
348    /// expression scope, which may include hidden commits.
349    pub fn all() -> Arc<Self> {
350        Arc::new(Self::All)
351    }
352
353    pub fn visible_heads() -> Arc<Self> {
354        Arc::new(Self::VisibleHeads)
355    }
356
357    fn visible_heads_or_referenced() -> Arc<Self> {
358        Arc::new(Self::VisibleHeadsOrReferenced)
359    }
360
361    pub fn root() -> Arc<Self> {
362        Arc::new(Self::Root)
363    }
364
365    pub fn commit(commit_id: CommitId) -> Arc<Self> {
366        Self::commits(vec![commit_id])
367    }
368
369    pub fn commits(commit_ids: Vec<CommitId>) -> Arc<Self> {
370        Arc::new(Self::Commits(commit_ids))
371    }
372
373    pub fn filter(predicate: RevsetFilterPredicate) -> Arc<Self> {
374        Arc::new(Self::Filter(predicate))
375    }
376
377    /// Find any empty commits.
378    pub fn is_empty() -> Arc<Self> {
379        Self::filter(RevsetFilterPredicate::File(FilesetExpression::all())).negated()
380    }
381}
382
383// Leaf expression that represents unresolved commit refs
384impl<St: ExpressionState<CommitRef = RevsetCommitRef>> RevsetExpression<St> {
385    pub fn working_copy(name: WorkspaceNameBuf) -> Arc<Self> {
386        Arc::new(Self::CommitRef(RevsetCommitRef::WorkingCopy(name)))
387    }
388
389    pub fn working_copies() -> Arc<Self> {
390        Arc::new(Self::CommitRef(RevsetCommitRef::WorkingCopies))
391    }
392
393    pub fn symbol(value: String) -> Arc<Self> {
394        Arc::new(Self::CommitRef(RevsetCommitRef::Symbol(value)))
395    }
396
397    pub fn remote_symbol(value: RemoteRefSymbolBuf) -> Arc<Self> {
398        let commit_ref = RevsetCommitRef::RemoteSymbol(value);
399        Arc::new(Self::CommitRef(commit_ref))
400    }
401
402    pub fn change_id_prefix(prefix: HexPrefix) -> Arc<Self> {
403        let commit_ref = RevsetCommitRef::ChangeId(prefix);
404        Arc::new(Self::CommitRef(commit_ref))
405    }
406
407    pub fn commit_id_prefix(prefix: HexPrefix) -> Arc<Self> {
408        let commit_ref = RevsetCommitRef::CommitId(prefix);
409        Arc::new(Self::CommitRef(commit_ref))
410    }
411
412    pub fn bookmarks(expression: StringExpression) -> Arc<Self> {
413        Arc::new(Self::CommitRef(RevsetCommitRef::Bookmarks(expression)))
414    }
415
416    pub fn remote_bookmarks(
417        bookmark: StringExpression,
418        remote: StringExpression,
419        remote_ref_state: Option<RemoteRefState>,
420    ) -> Arc<Self> {
421        Arc::new(Self::CommitRef(RevsetCommitRef::RemoteBookmarks {
422            bookmark,
423            remote,
424            remote_ref_state,
425        }))
426    }
427
428    pub fn tags(expression: StringExpression) -> Arc<Self> {
429        Arc::new(Self::CommitRef(RevsetCommitRef::Tags(expression)))
430    }
431
432    pub fn git_refs() -> Arc<Self> {
433        Arc::new(Self::CommitRef(RevsetCommitRef::GitRefs))
434    }
435
436    pub fn git_head() -> Arc<Self> {
437        Arc::new(Self::CommitRef(RevsetCommitRef::GitHead))
438    }
439}
440
441// Compound expression
442impl<St: ExpressionState> RevsetExpression<St> {
443    pub fn latest(self: &Arc<Self>, count: usize) -> Arc<Self> {
444        Arc::new(Self::Latest {
445            candidates: self.clone(),
446            count,
447        })
448    }
449
450    /// Commits in `self` that don't have descendants in `self`.
451    pub fn heads(self: &Arc<Self>) -> Arc<Self> {
452        Arc::new(Self::Heads(self.clone()))
453    }
454
455    /// Commits in `self` that don't have ancestors in `self`.
456    pub fn roots(self: &Arc<Self>) -> Arc<Self> {
457        Arc::new(Self::Roots(self.clone()))
458    }
459
460    /// Parents of `self`.
461    pub fn parents(self: &Arc<Self>) -> Arc<Self> {
462        self.ancestors_at(1)
463    }
464
465    /// Ancestors of `self`, including `self`.
466    pub fn ancestors(self: &Arc<Self>) -> Arc<Self> {
467        self.ancestors_range(GENERATION_RANGE_FULL)
468    }
469
470    /// Ancestors of `self` at an offset of `generation` behind `self`.
471    /// The `generation` offset is zero-based starting from `self`.
472    pub fn ancestors_at(self: &Arc<Self>, generation: u64) -> Arc<Self> {
473        self.ancestors_range(generation..generation.saturating_add(1))
474    }
475
476    /// Ancestors of `self` in the given range.
477    pub fn ancestors_range(self: &Arc<Self>, generation_range: Range<u64>) -> Arc<Self> {
478        Arc::new(Self::Ancestors {
479            heads: self.clone(),
480            generation: generation_range,
481            parents_range: PARENTS_RANGE_FULL,
482        })
483    }
484
485    /// First-parent ancestors of `self`, including `self`.
486    pub fn first_ancestors(self: &Arc<Self>) -> Arc<Self> {
487        self.first_ancestors_range(GENERATION_RANGE_FULL)
488    }
489
490    /// First-parent ancestors of `self` at an offset of `generation` behind
491    /// `self`. The `generation` offset is zero-based starting from `self`.
492    pub fn first_ancestors_at(self: &Arc<Self>, generation: u64) -> Arc<Self> {
493        self.first_ancestors_range(generation..generation.saturating_add(1))
494    }
495
496    /// First-parent ancestors of `self` in the given range.
497    pub fn first_ancestors_range(self: &Arc<Self>, generation_range: Range<u64>) -> Arc<Self> {
498        Arc::new(Self::Ancestors {
499            heads: self.clone(),
500            generation: generation_range,
501            parents_range: 0..1,
502        })
503    }
504
505    /// Children of `self`.
506    pub fn children(self: &Arc<Self>) -> Arc<Self> {
507        self.descendants_at(1)
508    }
509
510    /// Descendants of `self`, including `self`.
511    pub fn descendants(self: &Arc<Self>) -> Arc<Self> {
512        self.descendants_range(GENERATION_RANGE_FULL)
513    }
514
515    /// Descendants of `self` at an offset of `generation` ahead of `self`.
516    /// The `generation` offset is zero-based starting from `self`.
517    pub fn descendants_at(self: &Arc<Self>, generation: u64) -> Arc<Self> {
518        self.descendants_range(generation..generation.saturating_add(1))
519    }
520
521    /// Descendants of `self` in the given range.
522    pub fn descendants_range(self: &Arc<Self>, generation_range: Range<u64>) -> Arc<Self> {
523        Arc::new(Self::Descendants {
524            roots: self.clone(),
525            generation: generation_range,
526        })
527    }
528
529    /// Fork point (best common ancestors) of `self`.
530    pub fn fork_point(self: &Arc<Self>) -> Arc<Self> {
531        Arc::new(Self::ForkPoint(self.clone()))
532    }
533
534    /// Commits with ~half of the descendants in `self`.
535    pub fn bisect(self: &Arc<Self>) -> Arc<Self> {
536        Arc::new(Self::Bisect(self.clone()))
537    }
538
539    /// Commits in `self`, the number of which must be exactly equal to `count`.
540    pub fn has_size(self: &Arc<Self>, count: usize) -> Arc<Self> {
541        Arc::new(Self::HasSize {
542            candidates: self.clone(),
543            count,
544        })
545    }
546
547    /// Filter all commits by `predicate` in `self`.
548    pub fn filtered(self: &Arc<Self>, predicate: RevsetFilterPredicate) -> Arc<Self> {
549        self.intersection(&Self::filter(predicate))
550    }
551
552    /// Commits that are descendants of `self` and ancestors of `heads`, both
553    /// inclusive.
554    pub fn dag_range_to(self: &Arc<Self>, heads: &Arc<Self>) -> Arc<Self> {
555        Arc::new(Self::DagRange {
556            roots: self.clone(),
557            heads: heads.clone(),
558        })
559    }
560
561    /// Connects any ancestors and descendants in the set by adding the commits
562    /// between them.
563    pub fn connected(self: &Arc<Self>) -> Arc<Self> {
564        self.dag_range_to(self)
565    }
566
567    /// All commits within `domain` reachable from this set of commits, by
568    /// traversing either parent or child edges.
569    pub fn reachable(self: &Arc<Self>, domain: &Arc<Self>) -> Arc<Self> {
570        Arc::new(Self::Reachable {
571            sources: self.clone(),
572            domain: domain.clone(),
573        })
574    }
575
576    /// Commits reachable from `heads` but not from `self`.
577    pub fn range(self: &Arc<Self>, heads: &Arc<Self>) -> Arc<Self> {
578        Arc::new(Self::Range {
579            roots: self.clone(),
580            heads: heads.clone(),
581            generation: GENERATION_RANGE_FULL,
582            parents_range: PARENTS_RANGE_FULL,
583        })
584    }
585
586    /// Suppresses name resolution error within `self`.
587    pub fn present(self: &Arc<Self>) -> Arc<Self> {
588        Arc::new(Self::Present(self.clone()))
589    }
590
591    /// Commits that are not in `self`, i.e. the complement of `self`.
592    pub fn negated(self: &Arc<Self>) -> Arc<Self> {
593        Arc::new(Self::NotIn(self.clone()))
594    }
595
596    /// Commits that are in `self` or in `other` (or both).
597    pub fn union(self: &Arc<Self>, other: &Arc<Self>) -> Arc<Self> {
598        Arc::new(Self::Union(self.clone(), other.clone()))
599    }
600
601    /// Commits that are in any of the `expressions`.
602    pub fn union_all(expressions: &[Arc<Self>]) -> Arc<Self> {
603        to_binary_expression(expressions, &Self::none, &Self::union)
604    }
605
606    /// Commits that are in `self` and in `other`.
607    pub fn intersection(self: &Arc<Self>, other: &Arc<Self>) -> Arc<Self> {
608        Arc::new(Self::Intersection(self.clone(), other.clone()))
609    }
610
611    /// Commits that are in `self` but not in `other`.
612    pub fn minus(self: &Arc<Self>, other: &Arc<Self>) -> Arc<Self> {
613        Arc::new(Self::Difference(self.clone(), other.clone()))
614    }
615
616    /// Commits that are in the first expression in `expressions` that is not
617    /// `none()`.
618    pub fn coalesce(expressions: &[Arc<Self>]) -> Arc<Self> {
619        to_binary_expression(expressions, &Self::none, &Self::coalesce2)
620    }
621
622    fn coalesce2(self: &Arc<Self>, other: &Arc<Self>) -> Arc<Self> {
623        Arc::new(Self::Coalesce(self.clone(), other.clone()))
624    }
625}
626
627impl<St: ExpressionState<CommitRef = RevsetCommitRef>> RevsetExpression<St> {
628    /// Returns symbol string if this expression is of that type.
629    pub fn as_symbol(&self) -> Option<&str> {
630        match self {
631            Self::CommitRef(RevsetCommitRef::Symbol(name)) => Some(name),
632            _ => None,
633        }
634    }
635}
636
637impl UserRevsetExpression {
638    /// Resolve a user-provided expression. Symbols will be resolved using the
639    /// provided [`SymbolResolver`].
640    pub fn resolve_user_expression(
641        &self,
642        repo: &dyn Repo,
643        symbol_resolver: &SymbolResolver,
644    ) -> Result<Arc<ResolvedRevsetExpression>, RevsetResolutionError> {
645        resolve_symbols(repo, self, symbol_resolver)
646    }
647}
648
649impl ResolvedRevsetExpression {
650    /// Optimizes and evaluates this expression.
651    pub fn evaluate<'index>(
652        self: Arc<Self>,
653        repo: &'index dyn Repo,
654    ) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
655        let expr = optimize(self).to_backend_expression(repo);
656        repo.index().evaluate_revset(&expr, repo.store())
657    }
658
659    /// Evaluates this expression without optimizing it.
660    ///
661    /// Use this function if `self` is already optimized, or to debug
662    /// optimization pass.
663    pub fn evaluate_unoptimized<'index>(
664        self: &Arc<Self>,
665        repo: &'index dyn Repo,
666    ) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
667        // Since referenced commits change the evaluation result, they must be
668        // collected no matter if optimization is disabled.
669        let expr = resolve_referenced_commits(self)
670            .as_ref()
671            .unwrap_or(self)
672            .to_backend_expression(repo);
673        repo.index().evaluate_revset(&expr, repo.store())
674    }
675
676    /// Transforms this expression to the form which the `Index` backend will
677    /// process.
678    pub fn to_backend_expression(&self, repo: &dyn Repo) -> ResolvedExpression {
679        resolve_visibility(repo, self)
680    }
681}
682
683#[derive(Clone, Debug)]
684pub enum ResolvedPredicateExpression {
685    /// Pure filter predicate.
686    Filter(RevsetFilterPredicate),
687    /// Set expression to be evaluated as filter. This is typically a subtree
688    /// node of `Union` with a pure filter predicate.
689    Set(Box<ResolvedExpression>),
690    NotIn(Box<Self>),
691    Union(Box<Self>, Box<Self>),
692    Intersection(Box<Self>, Box<Self>),
693}
694
695/// Describes evaluation plan of revset expression.
696///
697/// Unlike `RevsetExpression`, this doesn't contain unresolved symbols or `View`
698/// properties.
699///
700/// Use `RevsetExpression` API to build a query programmatically.
701// TODO: rename to BackendExpression?
702#[derive(Clone, Debug)]
703pub enum ResolvedExpression {
704    Commits(Vec<CommitId>),
705    Ancestors {
706        heads: Box<Self>,
707        generation: Range<u64>,
708        parents_range: Range<u32>,
709    },
710    /// Commits that are ancestors of `heads` but not ancestors of `roots`.
711    Range {
712        roots: Box<Self>,
713        heads: Box<Self>,
714        generation: Range<u64>,
715        // Parents range is only used for traversing heads, not roots
716        parents_range: Range<u32>,
717    },
718    /// Commits that are descendants of `roots` and ancestors of `heads`.
719    DagRange {
720        roots: Box<Self>,
721        heads: Box<Self>,
722        generation_from_roots: Range<u64>,
723    },
724    /// Commits reachable from `sources` within `domain`.
725    Reachable {
726        sources: Box<Self>,
727        domain: Box<Self>,
728    },
729    Heads(Box<Self>),
730    /// Heads of the set of commits which are ancestors of `heads` but are not
731    /// ancestors of `roots`, and which also are contained in `filter`.
732    HeadsRange {
733        roots: Box<Self>,
734        heads: Box<Self>,
735        parents_range: Range<u32>,
736        filter: Option<ResolvedPredicateExpression>,
737    },
738    Roots(Box<Self>),
739    ForkPoint(Box<Self>),
740    Bisect(Box<Self>),
741    HasSize {
742        candidates: Box<Self>,
743        count: usize,
744    },
745    Latest {
746        candidates: Box<Self>,
747        count: usize,
748    },
749    Coalesce(Box<Self>, Box<Self>),
750    Union(Box<Self>, Box<Self>),
751    /// Intersects `candidates` with `predicate` by filtering.
752    FilterWithin {
753        candidates: Box<Self>,
754        predicate: ResolvedPredicateExpression,
755    },
756    /// Intersects expressions by merging.
757    Intersection(Box<Self>, Box<Self>),
758    Difference(Box<Self>, Box<Self>),
759}
760
761pub type RevsetFunction = fn(
762    &mut RevsetDiagnostics,
763    &FunctionCallNode,
764    &LoweringContext,
765) -> Result<Arc<UserRevsetExpression>, RevsetParseError>;
766
767static BUILTIN_FUNCTION_MAP: LazyLock<HashMap<&str, RevsetFunction>> = LazyLock::new(|| {
768    // Not using maplit::hashmap!{} or custom declarative macro here because
769    // code completion inside macro is quite restricted.
770    let mut map: HashMap<&str, RevsetFunction> = HashMap::new();
771    map.insert("parents", |diagnostics, function, context| {
772        let ([arg], [depth_opt_arg]) = function.expect_arguments()?;
773        let expression = lower_expression(diagnostics, arg, context)?;
774        if let Some(depth_arg) = depth_opt_arg {
775            let depth = expect_literal("integer", depth_arg)?;
776            Ok(expression.ancestors_at(depth))
777        } else {
778            Ok(expression.parents())
779        }
780    });
781    map.insert("children", |diagnostics, function, context| {
782        let ([arg], [depth_opt_arg]) = function.expect_arguments()?;
783        let expression = lower_expression(diagnostics, arg, context)?;
784        if let Some(depth_arg) = depth_opt_arg {
785            let depth = expect_literal("integer", depth_arg)?;
786            Ok(expression.descendants_at(depth))
787        } else {
788            Ok(expression.children())
789        }
790    });
791    map.insert("ancestors", |diagnostics, function, context| {
792        let ([heads_arg], [depth_opt_arg]) = function.expect_arguments()?;
793        let heads = lower_expression(diagnostics, heads_arg, context)?;
794        let generation = if let Some(depth_arg) = depth_opt_arg {
795            let depth = expect_literal("integer", depth_arg)?;
796            0..depth
797        } else {
798            GENERATION_RANGE_FULL
799        };
800        Ok(heads.ancestors_range(generation))
801    });
802    map.insert("descendants", |diagnostics, function, context| {
803        let ([roots_arg], [depth_opt_arg]) = function.expect_arguments()?;
804        let roots = lower_expression(diagnostics, roots_arg, context)?;
805        let generation = if let Some(depth_arg) = depth_opt_arg {
806            let depth = expect_literal("integer", depth_arg)?;
807            0..depth
808        } else {
809            GENERATION_RANGE_FULL
810        };
811        Ok(roots.descendants_range(generation))
812    });
813    map.insert("first_parent", |diagnostics, function, context| {
814        let ([arg], [depth_opt_arg]) = function.expect_arguments()?;
815        let expression = lower_expression(diagnostics, arg, context)?;
816        let depth = if let Some(depth_arg) = depth_opt_arg {
817            expect_literal("integer", depth_arg)?
818        } else {
819            1
820        };
821        Ok(expression.first_ancestors_at(depth))
822    });
823    map.insert("first_ancestors", |diagnostics, function, context| {
824        let ([heads_arg], [depth_opt_arg]) = function.expect_arguments()?;
825        let heads = lower_expression(diagnostics, heads_arg, context)?;
826        let generation = if let Some(depth_arg) = depth_opt_arg {
827            let depth = expect_literal("integer", depth_arg)?;
828            0..depth
829        } else {
830            GENERATION_RANGE_FULL
831        };
832        Ok(heads.first_ancestors_range(generation))
833    });
834    map.insert("connected", |diagnostics, function, context| {
835        let [arg] = function.expect_exact_arguments()?;
836        let candidates = lower_expression(diagnostics, arg, context)?;
837        Ok(candidates.connected())
838    });
839    map.insert("reachable", |diagnostics, function, context| {
840        let [source_arg, domain_arg] = function.expect_exact_arguments()?;
841        let sources = lower_expression(diagnostics, source_arg, context)?;
842        let domain = lower_expression(diagnostics, domain_arg, context)?;
843        Ok(sources.reachable(&domain))
844    });
845    map.insert("none", |_diagnostics, function, _context| {
846        function.expect_no_arguments()?;
847        Ok(RevsetExpression::none())
848    });
849    map.insert("all", |_diagnostics, function, _context| {
850        function.expect_no_arguments()?;
851        Ok(RevsetExpression::all())
852    });
853    map.insert("working_copies", |_diagnostics, function, _context| {
854        function.expect_no_arguments()?;
855        Ok(RevsetExpression::working_copies())
856    });
857    map.insert("heads", |diagnostics, function, context| {
858        let [arg] = function.expect_exact_arguments()?;
859        let candidates = lower_expression(diagnostics, arg, context)?;
860        Ok(candidates.heads())
861    });
862    map.insert("roots", |diagnostics, function, context| {
863        let [arg] = function.expect_exact_arguments()?;
864        let candidates = lower_expression(diagnostics, arg, context)?;
865        Ok(candidates.roots())
866    });
867    map.insert("visible_heads", |_diagnostics, function, _context| {
868        function.expect_no_arguments()?;
869        Ok(RevsetExpression::visible_heads())
870    });
871    map.insert("root", |_diagnostics, function, _context| {
872        function.expect_no_arguments()?;
873        Ok(RevsetExpression::root())
874    });
875    map.insert("change_id", |diagnostics, function, _context| {
876        let [arg] = function.expect_exact_arguments()?;
877        let prefix = revset_parser::catch_aliases(diagnostics, arg, |_diagnostics, arg| {
878            let value = revset_parser::expect_string_literal("change ID prefix", arg)?;
879            HexPrefix::try_from_reverse_hex(value)
880                .ok_or_else(|| RevsetParseError::expression("Invalid change ID prefix", arg.span))
881        })?;
882        Ok(RevsetExpression::change_id_prefix(prefix))
883    });
884    map.insert("commit_id", |diagnostics, function, _context| {
885        let [arg] = function.expect_exact_arguments()?;
886        let prefix = revset_parser::catch_aliases(diagnostics, arg, |_diagnostics, arg| {
887            let value = revset_parser::expect_string_literal("commit ID prefix", arg)?;
888            HexPrefix::try_from_hex(value)
889                .ok_or_else(|| RevsetParseError::expression("Invalid commit ID prefix", arg.span))
890        })?;
891        Ok(RevsetExpression::commit_id_prefix(prefix))
892    });
893    map.insert("bookmarks", |diagnostics, function, context| {
894        let ([], [opt_arg]) = function.expect_arguments()?;
895        let expr = if let Some(arg) = opt_arg {
896            expect_string_expression(diagnostics, arg, context)?
897        } else {
898            StringExpression::all()
899        };
900        Ok(RevsetExpression::bookmarks(expr))
901    });
902    map.insert("remote_bookmarks", |diagnostics, function, context| {
903        parse_remote_bookmarks_arguments(diagnostics, function, None, context)
904    });
905    map.insert(
906        "tracked_remote_bookmarks",
907        |diagnostics, function, context| {
908            parse_remote_bookmarks_arguments(
909                diagnostics,
910                function,
911                Some(RemoteRefState::Tracked),
912                context,
913            )
914        },
915    );
916    map.insert(
917        "untracked_remote_bookmarks",
918        |diagnostics, function, context| {
919            parse_remote_bookmarks_arguments(
920                diagnostics,
921                function,
922                Some(RemoteRefState::New),
923                context,
924            )
925        },
926    );
927    map.insert("tags", |diagnostics, function, context| {
928        let ([], [opt_arg]) = function.expect_arguments()?;
929        let expr = if let Some(arg) = opt_arg {
930            expect_string_expression(diagnostics, arg, context)?
931        } else {
932            StringExpression::all()
933        };
934        Ok(RevsetExpression::tags(expr))
935    });
936    // TODO: Remove in jj 0.43+
937    map.insert("git_refs", |diagnostics, function, _context| {
938        diagnostics.add_warning(RevsetParseError::expression(
939            "git_refs() is deprecated; use remote_bookmarks()/tags() instead",
940            function.name_span,
941        ));
942        function.expect_no_arguments()?;
943        Ok(RevsetExpression::git_refs())
944    });
945    // TODO: Remove in jj 0.43+
946    map.insert("git_head", |diagnostics, function, _context| {
947        diagnostics.add_warning(RevsetParseError::expression(
948            "git_head() is deprecated; use first_parent(@) instead",
949            function.name_span,
950        ));
951        function.expect_no_arguments()?;
952        Ok(RevsetExpression::git_head())
953    });
954    map.insert("latest", |diagnostics, function, context| {
955        let ([candidates_arg], [count_opt_arg]) = function.expect_arguments()?;
956        let candidates = lower_expression(diagnostics, candidates_arg, context)?;
957        let count = if let Some(count_arg) = count_opt_arg {
958            expect_literal("integer", count_arg)?
959        } else {
960            1
961        };
962        Ok(candidates.latest(count))
963    });
964    map.insert("fork_point", |diagnostics, function, context| {
965        let [expression_arg] = function.expect_exact_arguments()?;
966        let expression = lower_expression(diagnostics, expression_arg, context)?;
967        Ok(RevsetExpression::fork_point(&expression))
968    });
969    map.insert("bisect", |diagnostics, function, context| {
970        let [expression_arg] = function.expect_exact_arguments()?;
971        let expression = lower_expression(diagnostics, expression_arg, context)?;
972        Ok(RevsetExpression::bisect(&expression))
973    });
974    map.insert("exactly", |diagnostics, function, context| {
975        let ([candidates_arg, count_arg], []) = function.expect_arguments()?;
976        let candidates = lower_expression(diagnostics, candidates_arg, context)?;
977        let count = expect_literal("integer", count_arg)?;
978        Ok(candidates.has_size(count))
979    });
980    map.insert("merges", |_diagnostics, function, _context| {
981        function.expect_no_arguments()?;
982        Ok(RevsetExpression::filter(
983            RevsetFilterPredicate::ParentCount(2..u32::MAX),
984        ))
985    });
986    map.insert("description", |diagnostics, function, context| {
987        let [arg] = function.expect_exact_arguments()?;
988        let expr = expect_string_expression(diagnostics, arg, context)?;
989        let predicate = RevsetFilterPredicate::Description(expr);
990        Ok(RevsetExpression::filter(predicate))
991    });
992    map.insert("subject", |diagnostics, function, context| {
993        let [arg] = function.expect_exact_arguments()?;
994        let expr = expect_string_expression(diagnostics, arg, context)?;
995        let predicate = RevsetFilterPredicate::Subject(expr);
996        Ok(RevsetExpression::filter(predicate))
997    });
998    map.insert("author", |diagnostics, function, context| {
999        let [arg] = function.expect_exact_arguments()?;
1000        let expr = expect_string_expression(diagnostics, arg, context)?;
1001        let name_predicate = RevsetFilterPredicate::AuthorName(expr.clone());
1002        let email_predicate = RevsetFilterPredicate::AuthorEmail(expr);
1003        Ok(RevsetExpression::filter(name_predicate)
1004            .union(&RevsetExpression::filter(email_predicate)))
1005    });
1006    map.insert("author_name", |diagnostics, function, context| {
1007        let [arg] = function.expect_exact_arguments()?;
1008        let expr = expect_string_expression(diagnostics, arg, context)?;
1009        let predicate = RevsetFilterPredicate::AuthorName(expr);
1010        Ok(RevsetExpression::filter(predicate))
1011    });
1012    map.insert("author_email", |diagnostics, function, context| {
1013        let [arg] = function.expect_exact_arguments()?;
1014        let expr = expect_string_expression(diagnostics, arg, context)?;
1015        let predicate = RevsetFilterPredicate::AuthorEmail(expr);
1016        Ok(RevsetExpression::filter(predicate))
1017    });
1018    map.insert("author_date", |diagnostics, function, context| {
1019        let [arg] = function.expect_exact_arguments()?;
1020        let pattern = expect_date_pattern(diagnostics, arg, context.date_pattern_context())?;
1021        Ok(RevsetExpression::filter(RevsetFilterPredicate::AuthorDate(
1022            pattern,
1023        )))
1024    });
1025    map.insert("signed", |_diagnostics, function, _context| {
1026        function.expect_no_arguments()?;
1027        let predicate = RevsetFilterPredicate::Signed;
1028        Ok(RevsetExpression::filter(predicate))
1029    });
1030    map.insert("mine", |_diagnostics, function, context| {
1031        function.expect_no_arguments()?;
1032        // Email address domains are inherently case‐insensitive, and the local‐parts
1033        // are generally (although not universally) treated as case‐insensitive too, so
1034        // we use a case‐insensitive match here.
1035        let pattern = StringPattern::exact_i(context.user_email);
1036        let predicate = RevsetFilterPredicate::AuthorEmail(StringExpression::pattern(pattern));
1037        Ok(RevsetExpression::filter(predicate))
1038    });
1039    map.insert("committer", |diagnostics, function, context| {
1040        let [arg] = function.expect_exact_arguments()?;
1041        let expr = expect_string_expression(diagnostics, arg, context)?;
1042        let name_predicate = RevsetFilterPredicate::CommitterName(expr.clone());
1043        let email_predicate = RevsetFilterPredicate::CommitterEmail(expr);
1044        Ok(RevsetExpression::filter(name_predicate)
1045            .union(&RevsetExpression::filter(email_predicate)))
1046    });
1047    map.insert("committer_name", |diagnostics, function, context| {
1048        let [arg] = function.expect_exact_arguments()?;
1049        let expr = expect_string_expression(diagnostics, arg, context)?;
1050        let predicate = RevsetFilterPredicate::CommitterName(expr);
1051        Ok(RevsetExpression::filter(predicate))
1052    });
1053    map.insert("committer_email", |diagnostics, function, context| {
1054        let [arg] = function.expect_exact_arguments()?;
1055        let expr = expect_string_expression(diagnostics, arg, context)?;
1056        let predicate = RevsetFilterPredicate::CommitterEmail(expr);
1057        Ok(RevsetExpression::filter(predicate))
1058    });
1059    map.insert("committer_date", |diagnostics, function, context| {
1060        let [arg] = function.expect_exact_arguments()?;
1061        let pattern = expect_date_pattern(diagnostics, arg, context.date_pattern_context())?;
1062        Ok(RevsetExpression::filter(
1063            RevsetFilterPredicate::CommitterDate(pattern),
1064        ))
1065    });
1066    map.insert("empty", |_diagnostics, function, _context| {
1067        function.expect_no_arguments()?;
1068        Ok(RevsetExpression::is_empty())
1069    });
1070    map.insert("files", |diagnostics, function, context| {
1071        let ctx = context.workspace.as_ref().ok_or_else(|| {
1072            RevsetParseError::with_span(
1073                RevsetParseErrorKind::FsPathWithoutWorkspace,
1074                function.args_span, // TODO: better to use name_span?
1075            )
1076        })?;
1077        let [arg] = function.expect_exact_arguments()?;
1078        let expr = expect_fileset_expression(diagnostics, arg, ctx.path_converter)?;
1079        Ok(RevsetExpression::filter(RevsetFilterPredicate::File(expr)))
1080    });
1081    map.insert("diff_contains", |diagnostics, function, context| {
1082        let ([text_arg], [files_opt_arg]) = function.expect_arguments()?;
1083        let text = expect_string_expression(diagnostics, text_arg, context)?;
1084        let files = if let Some(files_arg) = files_opt_arg {
1085            let ctx = context.workspace.as_ref().ok_or_else(|| {
1086                RevsetParseError::with_span(
1087                    RevsetParseErrorKind::FsPathWithoutWorkspace,
1088                    files_arg.span,
1089                )
1090            })?;
1091            expect_fileset_expression(diagnostics, files_arg, ctx.path_converter)?
1092        } else {
1093            // TODO: defaults to CLI path arguments?
1094            // https://github.com/jj-vcs/jj/issues/2933#issuecomment-1925870731
1095            FilesetExpression::all()
1096        };
1097        Ok(RevsetExpression::filter(
1098            RevsetFilterPredicate::DiffContains { text, files },
1099        ))
1100    });
1101    map.insert("conflicts", |_diagnostics, function, _context| {
1102        function.expect_no_arguments()?;
1103        Ok(RevsetExpression::filter(RevsetFilterPredicate::HasConflict))
1104    });
1105    map.insert("present", |diagnostics, function, context| {
1106        let [arg] = function.expect_exact_arguments()?;
1107        let expression = lower_expression(diagnostics, arg, context)?;
1108        Ok(expression.present())
1109    });
1110    map.insert("at_operation", |diagnostics, function, context| {
1111        let [op_arg, cand_arg] = function.expect_exact_arguments()?;
1112        // TODO: Parse "opset" here if we add proper language support.
1113        let operation = revset_parser::catch_aliases(diagnostics, op_arg, |_diagnostics, node| {
1114            Ok(node.span.as_str().to_owned())
1115        })?;
1116        let candidates = lower_expression(diagnostics, cand_arg, context)?;
1117        Ok(Arc::new(RevsetExpression::AtOperation {
1118            operation,
1119            candidates,
1120        }))
1121    });
1122    map.insert("coalesce", |diagnostics, function, context| {
1123        let ([], args) = function.expect_some_arguments()?;
1124        let expressions: Vec<_> = args
1125            .iter()
1126            .map(|arg| lower_expression(diagnostics, arg, context))
1127            .try_collect()?;
1128        Ok(RevsetExpression::coalesce(&expressions))
1129    });
1130    map
1131});
1132
1133/// Parses the given `node` as a fileset expression.
1134pub fn expect_fileset_expression(
1135    diagnostics: &mut RevsetDiagnostics,
1136    node: &ExpressionNode,
1137    path_converter: &RepoPathUiConverter,
1138) -> Result<FilesetExpression, RevsetParseError> {
1139    // Alias handling is a bit tricky. The outermost expression `alias` is
1140    // substituted, but inner expressions `x & alias` aren't. If this seemed
1141    // weird, we can either transform AST or turn off revset aliases completely.
1142    revset_parser::catch_aliases(diagnostics, node, |diagnostics, node| {
1143        let mut inner_diagnostics = FilesetDiagnostics::new();
1144        let expression = fileset::parse(&mut inner_diagnostics, node.span.as_str(), path_converter)
1145            .map_err(|err| {
1146                RevsetParseError::expression("In fileset expression", node.span).with_source(err)
1147            })?;
1148        diagnostics.extend_with(inner_diagnostics, |diag| {
1149            RevsetParseError::expression("In fileset expression", node.span).with_source(diag)
1150        });
1151        Ok(expression)
1152    })
1153}
1154
1155/// Transforms the given `node` into a string expression.
1156pub fn expect_string_expression(
1157    diagnostics: &mut RevsetDiagnostics,
1158    node: &ExpressionNode,
1159    context: &LoweringContext,
1160) -> Result<StringExpression, RevsetParseError> {
1161    let default_kind = if context.use_glob_by_default {
1162        "glob"
1163    } else {
1164        "substring"
1165    };
1166    expect_string_expression_inner(diagnostics, node, default_kind)
1167}
1168
1169fn expect_string_expression_inner(
1170    diagnostics: &mut RevsetDiagnostics,
1171    node: &ExpressionNode,
1172    // TODO: remove this parameter with ui.revsets-use-glob-by-default
1173    default_kind: &str,
1174) -> Result<StringExpression, RevsetParseError> {
1175    revset_parser::catch_aliases(diagnostics, node, |diagnostics, node| {
1176        let expr_error = || RevsetParseError::expression("Invalid string expression", node.span);
1177        let pattern_error = || RevsetParseError::expression("Invalid string pattern", node.span);
1178        let default_pattern = |diagnostics: &mut RevsetDiagnostics, value: &str| {
1179            if default_kind == "substring" {
1180                diagnostics.add_warning(RevsetParseError::expression(
1181                    "ui.revsets-use-glob-by-default=false will be removed in a future release",
1182                    node.span,
1183                ));
1184            }
1185            let pattern = StringPattern::from_str_kind(value, default_kind)
1186                .map_err(|err| pattern_error().with_source(err))?;
1187            Ok(StringExpression::pattern(pattern))
1188        };
1189        match &node.kind {
1190            ExpressionKind::Identifier(value) => default_pattern(diagnostics, value),
1191            ExpressionKind::String(value) => default_pattern(diagnostics, value),
1192            ExpressionKind::StringPattern { kind, value } => {
1193                let pattern = StringPattern::from_str_kind(value, kind)
1194                    .map_err(|err| pattern_error().with_source(err))?;
1195                Ok(StringExpression::pattern(pattern))
1196            }
1197            ExpressionKind::RemoteSymbol(_)
1198            | ExpressionKind::AtWorkspace(_)
1199            | ExpressionKind::AtCurrentWorkspace
1200            | ExpressionKind::DagRangeAll
1201            | ExpressionKind::RangeAll => Err(expr_error()),
1202            ExpressionKind::Unary(op, arg_node) => {
1203                let arg = expect_string_expression_inner(diagnostics, arg_node, default_kind)?;
1204                match op {
1205                    UnaryOp::Negate => Ok(arg.negated()),
1206                    UnaryOp::DagRangePre
1207                    | UnaryOp::DagRangePost
1208                    | UnaryOp::RangePre
1209                    | UnaryOp::RangePost
1210                    | UnaryOp::Parents
1211                    | UnaryOp::Children => Err(expr_error()),
1212                }
1213            }
1214            ExpressionKind::Binary(op, lhs_node, rhs_node) => {
1215                let lhs = expect_string_expression_inner(diagnostics, lhs_node, default_kind)?;
1216                let rhs = expect_string_expression_inner(diagnostics, rhs_node, default_kind)?;
1217                match op {
1218                    BinaryOp::Intersection => Ok(lhs.intersection(rhs)),
1219                    BinaryOp::Difference => Ok(lhs.intersection(rhs.negated())),
1220                    BinaryOp::DagRange | BinaryOp::Range => Err(expr_error()),
1221                }
1222            }
1223            ExpressionKind::UnionAll(nodes) => {
1224                let expressions = nodes
1225                    .iter()
1226                    .map(|node| expect_string_expression_inner(diagnostics, node, default_kind))
1227                    .try_collect()?;
1228                Ok(StringExpression::union_all(expressions))
1229            }
1230            ExpressionKind::FunctionCall(_) | ExpressionKind::Modifier(_) => Err(expr_error()),
1231            ExpressionKind::AliasExpanded(..) => unreachable!(),
1232        }
1233    })
1234}
1235
1236pub fn expect_date_pattern(
1237    diagnostics: &mut RevsetDiagnostics,
1238    node: &ExpressionNode,
1239    context: &DatePatternContext,
1240) -> Result<DatePattern, RevsetParseError> {
1241    revset_parser::catch_aliases(diagnostics, node, |_diagnostics, node| {
1242        let (value, kind) = revset_parser::expect_string_pattern("date pattern", node)?;
1243        let kind = kind.ok_or_else(|| {
1244            RevsetParseError::expression("Date pattern must specify 'after' or 'before'", node.span)
1245        })?;
1246        context.parse_relative(value, kind).map_err(|err| {
1247            RevsetParseError::expression("Invalid date pattern", node.span).with_source(err)
1248        })
1249    })
1250}
1251
1252fn parse_remote_bookmarks_arguments(
1253    diagnostics: &mut RevsetDiagnostics,
1254    function: &FunctionCallNode,
1255    remote_ref_state: Option<RemoteRefState>,
1256    context: &LoweringContext,
1257) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
1258    let ([], [bookmark_opt_arg, remote_opt_arg]) =
1259        function.expect_named_arguments(&["", "remote"])?;
1260    let bookmark_expr = if let Some(bookmark_arg) = bookmark_opt_arg {
1261        expect_string_expression(diagnostics, bookmark_arg, context)?
1262    } else {
1263        StringExpression::all()
1264    };
1265    let remote_expr = if let Some(remote_arg) = remote_opt_arg {
1266        expect_string_expression(diagnostics, remote_arg, context)?
1267    } else if let Some(remote) = context.default_ignored_remote {
1268        StringExpression::exact(remote).negated()
1269    } else {
1270        StringExpression::all()
1271    };
1272    Ok(RevsetExpression::remote_bookmarks(
1273        bookmark_expr,
1274        remote_expr,
1275        remote_ref_state,
1276    ))
1277}
1278
1279/// Resolves function call by using the given function map.
1280fn lower_function_call(
1281    diagnostics: &mut RevsetDiagnostics,
1282    function: &FunctionCallNode,
1283    context: &LoweringContext,
1284) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
1285    let function_map = &context.extensions.function_map;
1286    if let Some(func) = function_map.get(function.name) {
1287        func(diagnostics, function, context)
1288    } else {
1289        Err(RevsetParseError::with_span(
1290            RevsetParseErrorKind::NoSuchFunction {
1291                name: function.name.to_owned(),
1292                candidates: collect_similar(function.name, function_map.keys()),
1293            },
1294            function.name_span,
1295        ))
1296    }
1297}
1298
1299/// Transforms the given AST `node` into expression that describes DAG
1300/// operation. Function calls will be resolved at this stage.
1301pub fn lower_expression(
1302    diagnostics: &mut RevsetDiagnostics,
1303    node: &ExpressionNode,
1304    context: &LoweringContext,
1305) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
1306    revset_parser::catch_aliases(diagnostics, node, |diagnostics, node| match &node.kind {
1307        ExpressionKind::Identifier(name) => Ok(RevsetExpression::symbol((*name).to_owned())),
1308        ExpressionKind::String(name) => Ok(RevsetExpression::symbol(name.to_owned())),
1309        ExpressionKind::StringPattern { .. } => Err(RevsetParseError::with_span(
1310            RevsetParseErrorKind::NotInfixOperator {
1311                op: ":".to_owned(),
1312                similar_op: "::".to_owned(),
1313                description: "DAG range".to_owned(),
1314            },
1315            node.span,
1316        )),
1317        ExpressionKind::RemoteSymbol(symbol) => Ok(RevsetExpression::remote_symbol(symbol.clone())),
1318        ExpressionKind::AtWorkspace(name) => Ok(RevsetExpression::working_copy(name.into())),
1319        ExpressionKind::AtCurrentWorkspace => {
1320            let ctx = context.workspace.as_ref().ok_or_else(|| {
1321                RevsetParseError::with_span(
1322                    RevsetParseErrorKind::WorkingCopyWithoutWorkspace,
1323                    node.span,
1324                )
1325            })?;
1326            Ok(RevsetExpression::working_copy(
1327                ctx.workspace_name.to_owned(),
1328            ))
1329        }
1330        ExpressionKind::DagRangeAll => Ok(RevsetExpression::all()),
1331        ExpressionKind::RangeAll => Ok(RevsetExpression::root().negated()),
1332        ExpressionKind::Unary(op, arg_node) => {
1333            let arg = lower_expression(diagnostics, arg_node, context)?;
1334            match op {
1335                UnaryOp::Negate => Ok(arg.negated()),
1336                UnaryOp::DagRangePre => Ok(arg.ancestors()),
1337                UnaryOp::DagRangePost => Ok(arg.descendants()),
1338                UnaryOp::RangePre => Ok(RevsetExpression::root().range(&arg)),
1339                UnaryOp::RangePost => Ok(arg.ancestors().negated()),
1340                UnaryOp::Parents => Ok(arg.parents()),
1341                UnaryOp::Children => Ok(arg.children()),
1342            }
1343        }
1344        ExpressionKind::Binary(op, lhs_node, rhs_node) => {
1345            let lhs = lower_expression(diagnostics, lhs_node, context)?;
1346            let rhs = lower_expression(diagnostics, rhs_node, context)?;
1347            match op {
1348                BinaryOp::Intersection => Ok(lhs.intersection(&rhs)),
1349                BinaryOp::Difference => Ok(lhs.minus(&rhs)),
1350                BinaryOp::DagRange => Ok(lhs.dag_range_to(&rhs)),
1351                BinaryOp::Range => Ok(lhs.range(&rhs)),
1352            }
1353        }
1354        ExpressionKind::UnionAll(nodes) => {
1355            let expressions: Vec<_> = nodes
1356                .iter()
1357                .map(|node| lower_expression(diagnostics, node, context))
1358                .try_collect()?;
1359            Ok(RevsetExpression::union_all(&expressions))
1360        }
1361        ExpressionKind::FunctionCall(function) => {
1362            lower_function_call(diagnostics, function, context)
1363        }
1364        ExpressionKind::Modifier(modifier) => {
1365            let name = modifier.name;
1366            Err(RevsetParseError::expression(
1367                format!("Modifier `{name}:` is not allowed in sub expression"),
1368                modifier.name_span,
1369            ))
1370        }
1371        ExpressionKind::AliasExpanded(..) => unreachable!(),
1372    })
1373}
1374
1375pub fn parse(
1376    diagnostics: &mut RevsetDiagnostics,
1377    revset_str: &str,
1378    context: &RevsetParseContext,
1379) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
1380    let node = parse_program(revset_str)?;
1381    let node =
1382        dsl_util::expand_aliases_with_locals(node, context.aliases_map, &context.local_variables)?;
1383    lower_expression(diagnostics, &node, &context.to_lowering_context())
1384        .map_err(|err| err.extend_function_candidates(context.aliases_map.function_names()))
1385}
1386
1387pub fn parse_with_modifier(
1388    diagnostics: &mut RevsetDiagnostics,
1389    revset_str: &str,
1390    context: &RevsetParseContext,
1391) -> Result<(Arc<UserRevsetExpression>, Option<RevsetModifier>), RevsetParseError> {
1392    let node = parse_program_with_modifier(revset_str)?;
1393    let node =
1394        dsl_util::expand_aliases_with_locals(node, context.aliases_map, &context.local_variables)?;
1395    revset_parser::catch_aliases(diagnostics, &node, |diagnostics, node| match &node.kind {
1396        ExpressionKind::Modifier(modifier) => {
1397            let parsed_modifier = match modifier.name {
1398                "all" => {
1399                    diagnostics.add_warning(RevsetParseError::expression(
1400                        "Multiple revisions are allowed by default; `all:` is planned for removal",
1401                        modifier.name_span,
1402                    ));
1403                    RevsetModifier::All
1404                }
1405                _ => {
1406                    return Err(RevsetParseError::with_span(
1407                        RevsetParseErrorKind::NoSuchModifier(modifier.name.to_owned()),
1408                        modifier.name_span,
1409                    ));
1410                }
1411            };
1412            let parsed_body =
1413                lower_expression(diagnostics, &modifier.body, &context.to_lowering_context())?;
1414            Ok((parsed_body, Some(parsed_modifier)))
1415        }
1416        _ => {
1417            let parsed_body = lower_expression(diagnostics, node, &context.to_lowering_context())?;
1418            Ok((parsed_body, None))
1419        }
1420    })
1421    .map_err(|err| err.extend_function_candidates(context.aliases_map.function_names()))
1422}
1423
1424/// Parses text into a string matcher expression.
1425pub fn parse_string_expression(
1426    diagnostics: &mut RevsetDiagnostics,
1427    text: &str,
1428) -> Result<StringExpression, RevsetParseError> {
1429    let node = parse_program(text)?;
1430    let default_kind = "glob";
1431    expect_string_expression_inner(diagnostics, &node, default_kind)
1432}
1433
1434/// Constructs binary tree from `expressions` list, `unit` node, and associative
1435/// `binary` operation.
1436fn to_binary_expression<T: Clone>(
1437    expressions: &[T],
1438    unit: &impl Fn() -> T,
1439    binary: &impl Fn(&T, &T) -> T,
1440) -> T {
1441    match expressions {
1442        [] => unit(),
1443        [expression] => expression.clone(),
1444        _ => {
1445            // Build balanced tree to minimize the recursion depth.
1446            let (left, right) = expressions.split_at(expressions.len() / 2);
1447            binary(
1448                &to_binary_expression(left, unit, binary),
1449                &to_binary_expression(right, unit, binary),
1450            )
1451        }
1452    }
1453}
1454
1455/// `Some` for rewritten expression, or `None` to reuse the original expression.
1456type TransformedExpression<St> = Option<Arc<RevsetExpression<St>>>;
1457/// `Break` to not transform subtree recursively. `Continue(Some(rewritten))`
1458/// isn't allowed because it could be a source of infinite substitution bugs.
1459type PreTransformedExpression<St> = ControlFlow<TransformedExpression<St>, ()>;
1460
1461/// Walks `expression` tree and applies `pre`/`post` transformation recursively.
1462fn transform_expression<St: ExpressionState>(
1463    expression: &Arc<RevsetExpression<St>>,
1464    mut pre: impl FnMut(&Arc<RevsetExpression<St>>) -> PreTransformedExpression<St>,
1465    mut post: impl FnMut(&Arc<RevsetExpression<St>>) -> TransformedExpression<St>,
1466) -> TransformedExpression<St> {
1467    let Ok(transformed) =
1468        try_transform_expression::<St, Infallible>(expression, |x| Ok(pre(x)), |x| Ok(post(x)));
1469    transformed
1470}
1471
1472/// Walks `expression` tree and applies `post` recursively from leaf nodes.
1473fn transform_expression_bottom_up<St: ExpressionState>(
1474    expression: &Arc<RevsetExpression<St>>,
1475    post: impl FnMut(&Arc<RevsetExpression<St>>) -> TransformedExpression<St>,
1476) -> TransformedExpression<St> {
1477    transform_expression(expression, |_| ControlFlow::Continue(()), post)
1478}
1479
1480/// Walks `expression` tree and applies transformation recursively.
1481///
1482/// `pre` is the callback to rewrite subtree including children. It is invoked
1483/// before visiting the child nodes. If returned `Break`, children won't be
1484/// visited.
1485///
1486/// `post` is the callback to rewrite from leaf nodes. If returned `None`,
1487/// the original expression node will be reused.
1488///
1489/// If no nodes rewritten, this function returns `None`.
1490/// `std::iter::successors()` could be used if the transformation needs to be
1491/// applied repeatedly until converged.
1492fn try_transform_expression<St: ExpressionState, E>(
1493    expression: &Arc<RevsetExpression<St>>,
1494    mut pre: impl FnMut(&Arc<RevsetExpression<St>>) -> Result<PreTransformedExpression<St>, E>,
1495    mut post: impl FnMut(&Arc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1496) -> Result<TransformedExpression<St>, E> {
1497    fn transform_child_rec<St: ExpressionState, E>(
1498        expression: &Arc<RevsetExpression<St>>,
1499        pre: &mut impl FnMut(&Arc<RevsetExpression<St>>) -> Result<PreTransformedExpression<St>, E>,
1500        post: &mut impl FnMut(&Arc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1501    ) -> Result<TransformedExpression<St>, E> {
1502        Ok(match expression.as_ref() {
1503            RevsetExpression::None => None,
1504            RevsetExpression::All => None,
1505            RevsetExpression::VisibleHeads => None,
1506            RevsetExpression::VisibleHeadsOrReferenced => None,
1507            RevsetExpression::Root => None,
1508            RevsetExpression::Commits(_) => None,
1509            RevsetExpression::CommitRef(_) => None,
1510            RevsetExpression::Ancestors {
1511                heads,
1512                generation,
1513                parents_range,
1514            } => transform_rec(heads, pre, post)?.map(|heads| RevsetExpression::Ancestors {
1515                heads,
1516                generation: generation.clone(),
1517                parents_range: parents_range.clone(),
1518            }),
1519            RevsetExpression::Descendants { roots, generation } => transform_rec(roots, pre, post)?
1520                .map(|roots| RevsetExpression::Descendants {
1521                    roots,
1522                    generation: generation.clone(),
1523                }),
1524            RevsetExpression::Range {
1525                roots,
1526                heads,
1527                generation,
1528                parents_range,
1529            } => transform_rec_pair((roots, heads), pre, post)?.map(|(roots, heads)| {
1530                RevsetExpression::Range {
1531                    roots,
1532                    heads,
1533                    generation: generation.clone(),
1534                    parents_range: parents_range.clone(),
1535                }
1536            }),
1537            RevsetExpression::DagRange { roots, heads } => {
1538                transform_rec_pair((roots, heads), pre, post)?
1539                    .map(|(roots, heads)| RevsetExpression::DagRange { roots, heads })
1540            }
1541            RevsetExpression::Reachable { sources, domain } => {
1542                transform_rec_pair((sources, domain), pre, post)?
1543                    .map(|(sources, domain)| RevsetExpression::Reachable { sources, domain })
1544            }
1545            RevsetExpression::Heads(candidates) => {
1546                transform_rec(candidates, pre, post)?.map(RevsetExpression::Heads)
1547            }
1548            RevsetExpression::HeadsRange {
1549                roots,
1550                heads,
1551                parents_range,
1552                filter,
1553            } => {
1554                let transformed_roots = transform_rec(roots, pre, post)?;
1555                let transformed_heads = transform_rec(heads, pre, post)?;
1556                let transformed_filter = transform_rec(filter, pre, post)?;
1557                (transformed_roots.is_some()
1558                    || transformed_heads.is_some()
1559                    || transformed_filter.is_some())
1560                .then(|| RevsetExpression::HeadsRange {
1561                    roots: transformed_roots.unwrap_or_else(|| roots.clone()),
1562                    heads: transformed_heads.unwrap_or_else(|| heads.clone()),
1563                    parents_range: parents_range.clone(),
1564                    filter: transformed_filter.unwrap_or_else(|| filter.clone()),
1565                })
1566            }
1567            RevsetExpression::Roots(candidates) => {
1568                transform_rec(candidates, pre, post)?.map(RevsetExpression::Roots)
1569            }
1570            RevsetExpression::ForkPoint(expression) => {
1571                transform_rec(expression, pre, post)?.map(RevsetExpression::ForkPoint)
1572            }
1573            RevsetExpression::Bisect(expression) => {
1574                transform_rec(expression, pre, post)?.map(RevsetExpression::Bisect)
1575            }
1576            RevsetExpression::HasSize { candidates, count } => {
1577                transform_rec(candidates, pre, post)?.map(|candidates| RevsetExpression::HasSize {
1578                    candidates,
1579                    count: *count,
1580                })
1581            }
1582            RevsetExpression::Latest { candidates, count } => transform_rec(candidates, pre, post)?
1583                .map(|candidates| RevsetExpression::Latest {
1584                    candidates,
1585                    count: *count,
1586                }),
1587            RevsetExpression::Filter(_) => None,
1588            RevsetExpression::AsFilter(candidates) => {
1589                transform_rec(candidates, pre, post)?.map(RevsetExpression::AsFilter)
1590            }
1591            RevsetExpression::AtOperation {
1592                operation,
1593                candidates,
1594            } => transform_rec(candidates, pre, post)?.map(|candidates| {
1595                RevsetExpression::AtOperation {
1596                    operation: operation.clone(),
1597                    candidates,
1598                }
1599            }),
1600            RevsetExpression::WithinReference {
1601                candidates,
1602                commits,
1603            } => transform_rec(candidates, pre, post)?.map(|candidates| {
1604                RevsetExpression::WithinReference {
1605                    candidates,
1606                    commits: commits.clone(),
1607                }
1608            }),
1609            RevsetExpression::WithinVisibility {
1610                candidates,
1611                visible_heads,
1612            } => transform_rec(candidates, pre, post)?.map(|candidates| {
1613                RevsetExpression::WithinVisibility {
1614                    candidates,
1615                    visible_heads: visible_heads.clone(),
1616                }
1617            }),
1618            RevsetExpression::Coalesce(expression1, expression2) => transform_rec_pair(
1619                (expression1, expression2),
1620                pre,
1621                post,
1622            )?
1623            .map(|(expression1, expression2)| RevsetExpression::Coalesce(expression1, expression2)),
1624            RevsetExpression::Present(candidates) => {
1625                transform_rec(candidates, pre, post)?.map(RevsetExpression::Present)
1626            }
1627            RevsetExpression::NotIn(complement) => {
1628                transform_rec(complement, pre, post)?.map(RevsetExpression::NotIn)
1629            }
1630            RevsetExpression::Union(expression1, expression2) => {
1631                transform_rec_pair((expression1, expression2), pre, post)?.map(
1632                    |(expression1, expression2)| RevsetExpression::Union(expression1, expression2),
1633                )
1634            }
1635            RevsetExpression::Intersection(expression1, expression2) => {
1636                transform_rec_pair((expression1, expression2), pre, post)?.map(
1637                    |(expression1, expression2)| {
1638                        RevsetExpression::Intersection(expression1, expression2)
1639                    },
1640                )
1641            }
1642            RevsetExpression::Difference(expression1, expression2) => {
1643                transform_rec_pair((expression1, expression2), pre, post)?.map(
1644                    |(expression1, expression2)| {
1645                        RevsetExpression::Difference(expression1, expression2)
1646                    },
1647                )
1648            }
1649        }
1650        .map(Arc::new))
1651    }
1652
1653    #[expect(clippy::type_complexity)]
1654    fn transform_rec_pair<St: ExpressionState, E>(
1655        (expression1, expression2): (&Arc<RevsetExpression<St>>, &Arc<RevsetExpression<St>>),
1656        pre: &mut impl FnMut(&Arc<RevsetExpression<St>>) -> Result<PreTransformedExpression<St>, E>,
1657        post: &mut impl FnMut(&Arc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1658    ) -> Result<Option<(Arc<RevsetExpression<St>>, Arc<RevsetExpression<St>>)>, E> {
1659        match (
1660            transform_rec(expression1, pre, post)?,
1661            transform_rec(expression2, pre, post)?,
1662        ) {
1663            (Some(new_expression1), Some(new_expression2)) => {
1664                Ok(Some((new_expression1, new_expression2)))
1665            }
1666            (Some(new_expression1), None) => Ok(Some((new_expression1, expression2.clone()))),
1667            (None, Some(new_expression2)) => Ok(Some((expression1.clone(), new_expression2))),
1668            (None, None) => Ok(None),
1669        }
1670    }
1671
1672    fn transform_rec<St: ExpressionState, E>(
1673        expression: &Arc<RevsetExpression<St>>,
1674        pre: &mut impl FnMut(&Arc<RevsetExpression<St>>) -> Result<PreTransformedExpression<St>, E>,
1675        post: &mut impl FnMut(&Arc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1676    ) -> Result<TransformedExpression<St>, E> {
1677        if let ControlFlow::Break(transformed) = pre(expression)? {
1678            return Ok(transformed);
1679        }
1680        if let Some(new_expression) = transform_child_rec(expression, pre, post)? {
1681            // must propagate new expression tree
1682            Ok(Some(post(&new_expression)?.unwrap_or(new_expression)))
1683        } else {
1684            post(expression)
1685        }
1686    }
1687
1688    transform_rec(expression, &mut pre, &mut post)
1689}
1690
1691/// Visitor-like interface to transform [`RevsetExpression`] state recursively.
1692///
1693/// This is similar to [`try_transform_expression()`], but is supposed to
1694/// transform the resolution state from `InSt` to `OutSt`.
1695trait ExpressionStateFolder<InSt: ExpressionState, OutSt: ExpressionState> {
1696    type Error;
1697
1698    /// Transforms the `expression`. By default, inner items are transformed
1699    /// recursively.
1700    fn fold_expression(
1701        &mut self,
1702        expression: &RevsetExpression<InSt>,
1703    ) -> Result<Arc<RevsetExpression<OutSt>>, Self::Error> {
1704        fold_child_expression_state(self, expression)
1705    }
1706
1707    /// Transforms commit ref such as symbol.
1708    fn fold_commit_ref(
1709        &mut self,
1710        commit_ref: &InSt::CommitRef,
1711    ) -> Result<Arc<RevsetExpression<OutSt>>, Self::Error>;
1712
1713    /// Transforms `at_operation(operation, candidates)` expression.
1714    fn fold_at_operation(
1715        &mut self,
1716        operation: &InSt::Operation,
1717        candidates: &RevsetExpression<InSt>,
1718    ) -> Result<Arc<RevsetExpression<OutSt>>, Self::Error>;
1719}
1720
1721/// Transforms inner items of the `expression` by using the `folder`.
1722fn fold_child_expression_state<InSt, OutSt, F>(
1723    folder: &mut F,
1724    expression: &RevsetExpression<InSt>,
1725) -> Result<Arc<RevsetExpression<OutSt>>, F::Error>
1726where
1727    InSt: ExpressionState,
1728    OutSt: ExpressionState,
1729    F: ExpressionStateFolder<InSt, OutSt> + ?Sized,
1730{
1731    let expression: Arc<_> = match expression {
1732        RevsetExpression::None => RevsetExpression::None.into(),
1733        RevsetExpression::All => RevsetExpression::All.into(),
1734        RevsetExpression::VisibleHeads => RevsetExpression::VisibleHeads.into(),
1735        RevsetExpression::VisibleHeadsOrReferenced => {
1736            RevsetExpression::VisibleHeadsOrReferenced.into()
1737        }
1738        RevsetExpression::Root => RevsetExpression::Root.into(),
1739        RevsetExpression::Commits(ids) => RevsetExpression::Commits(ids.clone()).into(),
1740        RevsetExpression::CommitRef(commit_ref) => folder.fold_commit_ref(commit_ref)?,
1741        RevsetExpression::Ancestors {
1742            heads,
1743            generation,
1744            parents_range,
1745        } => {
1746            let heads = folder.fold_expression(heads)?;
1747            let generation = generation.clone();
1748            let parents_range = parents_range.clone();
1749            RevsetExpression::Ancestors {
1750                heads,
1751                generation,
1752                parents_range,
1753            }
1754            .into()
1755        }
1756        RevsetExpression::Descendants { roots, generation } => {
1757            let roots = folder.fold_expression(roots)?;
1758            let generation = generation.clone();
1759            RevsetExpression::Descendants { roots, generation }.into()
1760        }
1761        RevsetExpression::Range {
1762            roots,
1763            heads,
1764            generation,
1765            parents_range,
1766        } => {
1767            let roots = folder.fold_expression(roots)?;
1768            let heads = folder.fold_expression(heads)?;
1769            let generation = generation.clone();
1770            let parents_range = parents_range.clone();
1771            RevsetExpression::Range {
1772                roots,
1773                heads,
1774                generation,
1775                parents_range,
1776            }
1777            .into()
1778        }
1779        RevsetExpression::DagRange { roots, heads } => {
1780            let roots = folder.fold_expression(roots)?;
1781            let heads = folder.fold_expression(heads)?;
1782            RevsetExpression::DagRange { roots, heads }.into()
1783        }
1784        RevsetExpression::Reachable { sources, domain } => {
1785            let sources = folder.fold_expression(sources)?;
1786            let domain = folder.fold_expression(domain)?;
1787            RevsetExpression::Reachable { sources, domain }.into()
1788        }
1789        RevsetExpression::Heads(heads) => {
1790            let heads = folder.fold_expression(heads)?;
1791            RevsetExpression::Heads(heads).into()
1792        }
1793        RevsetExpression::HeadsRange {
1794            roots,
1795            heads,
1796            parents_range,
1797            filter,
1798        } => {
1799            let roots = folder.fold_expression(roots)?;
1800            let heads = folder.fold_expression(heads)?;
1801            let parents_range = parents_range.clone();
1802            let filter = folder.fold_expression(filter)?;
1803            RevsetExpression::HeadsRange {
1804                roots,
1805                heads,
1806                parents_range,
1807                filter,
1808            }
1809            .into()
1810        }
1811        RevsetExpression::Roots(roots) => {
1812            let roots = folder.fold_expression(roots)?;
1813            RevsetExpression::Roots(roots).into()
1814        }
1815        RevsetExpression::ForkPoint(expression) => {
1816            let expression = folder.fold_expression(expression)?;
1817            RevsetExpression::ForkPoint(expression).into()
1818        }
1819        RevsetExpression::Bisect(expression) => {
1820            let expression = folder.fold_expression(expression)?;
1821            RevsetExpression::Bisect(expression).into()
1822        }
1823        RevsetExpression::HasSize { candidates, count } => {
1824            let candidates = folder.fold_expression(candidates)?;
1825            RevsetExpression::HasSize {
1826                candidates,
1827                count: *count,
1828            }
1829            .into()
1830        }
1831        RevsetExpression::Latest { candidates, count } => {
1832            let candidates = folder.fold_expression(candidates)?;
1833            let count = *count;
1834            RevsetExpression::Latest { candidates, count }.into()
1835        }
1836        RevsetExpression::Filter(predicate) => RevsetExpression::Filter(predicate.clone()).into(),
1837        RevsetExpression::AsFilter(candidates) => {
1838            let candidates = folder.fold_expression(candidates)?;
1839            RevsetExpression::AsFilter(candidates).into()
1840        }
1841        RevsetExpression::AtOperation {
1842            operation,
1843            candidates,
1844        } => folder.fold_at_operation(operation, candidates)?,
1845        RevsetExpression::WithinReference {
1846            candidates,
1847            commits,
1848        } => {
1849            let candidates = folder.fold_expression(candidates)?;
1850            let commits = commits.clone();
1851            RevsetExpression::WithinReference {
1852                candidates,
1853                commits,
1854            }
1855            .into()
1856        }
1857        RevsetExpression::WithinVisibility {
1858            candidates,
1859            visible_heads,
1860        } => {
1861            let candidates = folder.fold_expression(candidates)?;
1862            let visible_heads = visible_heads.clone();
1863            RevsetExpression::WithinVisibility {
1864                candidates,
1865                visible_heads,
1866            }
1867            .into()
1868        }
1869        RevsetExpression::Coalesce(expression1, expression2) => {
1870            let expression1 = folder.fold_expression(expression1)?;
1871            let expression2 = folder.fold_expression(expression2)?;
1872            RevsetExpression::Coalesce(expression1, expression2).into()
1873        }
1874        RevsetExpression::Present(candidates) => {
1875            let candidates = folder.fold_expression(candidates)?;
1876            RevsetExpression::Present(candidates).into()
1877        }
1878        RevsetExpression::NotIn(complement) => {
1879            let complement = folder.fold_expression(complement)?;
1880            RevsetExpression::NotIn(complement).into()
1881        }
1882        RevsetExpression::Union(expression1, expression2) => {
1883            let expression1 = folder.fold_expression(expression1)?;
1884            let expression2 = folder.fold_expression(expression2)?;
1885            RevsetExpression::Union(expression1, expression2).into()
1886        }
1887        RevsetExpression::Intersection(expression1, expression2) => {
1888            let expression1 = folder.fold_expression(expression1)?;
1889            let expression2 = folder.fold_expression(expression2)?;
1890            RevsetExpression::Intersection(expression1, expression2).into()
1891        }
1892        RevsetExpression::Difference(expression1, expression2) => {
1893            let expression1 = folder.fold_expression(expression1)?;
1894            let expression2 = folder.fold_expression(expression2)?;
1895            RevsetExpression::Difference(expression1, expression2).into()
1896        }
1897    };
1898    Ok(expression)
1899}
1900
1901/// Collects explicitly-referenced commits, inserts marker nodes.
1902///
1903/// User symbols and `at_operation()` scopes should have been resolved.
1904fn resolve_referenced_commits<St: ExpressionState>(
1905    expression: &Arc<RevsetExpression<St>>,
1906) -> TransformedExpression<St> {
1907    // Trust precomputed value if any
1908    if matches!(
1909        expression.as_ref(),
1910        RevsetExpression::WithinReference { .. }
1911    ) {
1912        return None;
1913    }
1914
1915    // Use separate Vec to get around borrowing issue
1916    let mut inner_commits = Vec::new();
1917    let mut outer_commits = Vec::new();
1918    let transformed = transform_expression(
1919        expression,
1920        |expression| match expression.as_ref() {
1921            // Trust precomputed value
1922            RevsetExpression::WithinReference { commits, .. } => {
1923                inner_commits.extend_from_slice(commits);
1924                ControlFlow::Break(None)
1925            }
1926            // at_operation() scope shouldn't be affected by outer
1927            RevsetExpression::WithinVisibility {
1928                candidates,
1929                visible_heads,
1930            } => {
1931                // ::visible_heads shouldn't be filtered out by outer
1932                inner_commits.extend_from_slice(visible_heads);
1933                let transformed = resolve_referenced_commits(candidates);
1934                // Referenced commits shouldn't be filtered out by outer
1935                if let RevsetExpression::WithinReference { commits, .. } =
1936                    transformed.as_deref().unwrap_or(candidates)
1937                {
1938                    inner_commits.extend_from_slice(commits);
1939                }
1940                ControlFlow::Break(transformed.map(|candidates| {
1941                    Arc::new(RevsetExpression::WithinVisibility {
1942                        candidates,
1943                        visible_heads: visible_heads.clone(),
1944                    })
1945                }))
1946            }
1947            _ => ControlFlow::Continue(()),
1948        },
1949        |expression| {
1950            if let RevsetExpression::Commits(commits) = expression.as_ref() {
1951                outer_commits.extend_from_slice(commits);
1952            }
1953            None
1954        },
1955    );
1956
1957    // Commits could be deduplicated here, but they'll be concatenated with
1958    // the visible heads later, which may have duplicates.
1959    outer_commits.extend(inner_commits);
1960    if outer_commits.is_empty() {
1961        // Omit empty node to keep test/debug output concise
1962        return transformed;
1963    }
1964    Some(Arc::new(RevsetExpression::WithinReference {
1965        candidates: transformed.unwrap_or_else(|| expression.clone()),
1966        commits: outer_commits,
1967    }))
1968}
1969
1970/// Flatten all intersections to be left-recursive. For instance, transforms
1971/// `(a & b) & (c & d)` into `((a & b) & c) & d`.
1972fn flatten_intersections<St: ExpressionState>(
1973    expression: &Arc<RevsetExpression<St>>,
1974) -> TransformedExpression<St> {
1975    fn flatten<St: ExpressionState>(
1976        expression1: &Arc<RevsetExpression<St>>,
1977        expression2: &Arc<RevsetExpression<St>>,
1978    ) -> TransformedExpression<St> {
1979        let recurse = |a, b| flatten(a, b).unwrap_or_else(|| a.intersection(b));
1980
1981        match expression2.as_ref() {
1982            // flatten(a & (b & c)) -> flatten(a & b) & c
1983            RevsetExpression::Intersection(inner1, inner2) => {
1984                Some(recurse(expression1, inner1).intersection(inner2))
1985            }
1986            _ => None,
1987        }
1988    }
1989
1990    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1991        RevsetExpression::Intersection(expression1, expression2) => {
1992            flatten(expression1, expression2)
1993        }
1994        _ => None,
1995    })
1996}
1997
1998/// Intersects `expression` with `base`, maintaining sorted order using the
1999/// provided key. If `base` is an intersection, it must be left-recursive, and
2000/// it must already be in sorted order.
2001fn sort_intersection_by_key<St: ExpressionState, T: Ord>(
2002    base: &Arc<RevsetExpression<St>>,
2003    expression: &Arc<RevsetExpression<St>>,
2004    mut get_key: impl FnMut(&RevsetExpression<St>) -> T,
2005) -> TransformedExpression<St> {
2006    // We only want to compute the key for `expression` once instead of computing it
2007    // on every iteration.
2008    fn sort_intersection_helper<St: ExpressionState, T: Ord>(
2009        base: &Arc<RevsetExpression<St>>,
2010        expression: &Arc<RevsetExpression<St>>,
2011        expression_key: T,
2012        mut get_key: impl FnMut(&RevsetExpression<St>) -> T,
2013    ) -> TransformedExpression<St> {
2014        if let RevsetExpression::Intersection(inner1, inner2) = base.as_ref() {
2015            // sort_intersection(a & b, c) -> sort_intersection(a, c) & b
2016            (expression_key < get_key(inner2)).then(|| {
2017                sort_intersection_helper(inner1, expression, expression_key, get_key)
2018                    .unwrap_or_else(|| inner1.intersection(expression))
2019                    .intersection(inner2)
2020            })
2021        } else {
2022            // a & b -> b & a
2023            (expression_key < get_key(base)).then(|| expression.intersection(base))
2024        }
2025    }
2026
2027    sort_intersection_helper(base, expression, get_key(expression), get_key)
2028}
2029
2030/// Push `ancestors(x)` and `~ancestors(x)` down (to the left) in intersections.
2031/// All `~ancestors(x)` will be moved before `ancestors(x)`, since negated
2032/// ancestors can be converted to ranges. All other negations are moved to the
2033/// right, since these negations can usually be evaluated better as differences.
2034fn sort_negations_and_ancestors<St: ExpressionState>(
2035    expression: &Arc<RevsetExpression<St>>,
2036) -> TransformedExpression<St> {
2037    #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
2038    enum AncestorsOrder {
2039        NegatedAncestors,
2040        Ancestors,
2041        Other,
2042        NegatedOther,
2043    }
2044
2045    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2046        RevsetExpression::Intersection(expression1, expression2) => {
2047            sort_intersection_by_key(expression1, expression2, |expression| match expression {
2048                RevsetExpression::Ancestors {
2049                    heads: _,
2050                    generation: Range { end: u64::MAX, .. },
2051                    parents_range: _,
2052                } => AncestorsOrder::Ancestors,
2053                RevsetExpression::NotIn(complement) => match complement.as_ref() {
2054                    RevsetExpression::Ancestors {
2055                        heads: _,
2056                        generation: Range { end: u64::MAX, .. },
2057                        // We only want to move negated ancestors with a full parents range, since
2058                        // these are the only negated ancestors which can be converted to a range.
2059                        parents_range: PARENTS_RANGE_FULL,
2060                    } => AncestorsOrder::NegatedAncestors,
2061                    _ => AncestorsOrder::NegatedOther,
2062                },
2063                _ => AncestorsOrder::Other,
2064            })
2065        }
2066        _ => None,
2067    })
2068}
2069
2070/// Transforms filter expressions, by applying the following rules.
2071///
2072/// a. Moves as many sets to left of filter intersection as possible, to
2073///    minimize the filter inputs.
2074/// b. TODO: Rewrites set operations to and/or/not of predicates, to
2075///    help further optimization (e.g. combine `file(_)` matchers.)
2076/// c. Wraps union of filter and set (e.g. `author(_) | heads()`), to
2077///    ensure inner filter wouldn't need to evaluate all the input sets.
2078fn internalize_filter<St: ExpressionState>(
2079    expression: &Arc<RevsetExpression<St>>,
2080) -> TransformedExpression<St> {
2081    fn get_filter<St: ExpressionState>(
2082        expression: &Arc<RevsetExpression<St>>,
2083    ) -> Option<&Arc<RevsetExpression<St>>> {
2084        match expression.as_ref() {
2085            RevsetExpression::Filter(_) => Some(expression),
2086            RevsetExpression::AsFilter(candidates) => Some(candidates),
2087            _ => None,
2088        }
2089    }
2090
2091    fn mark_filter<St: ExpressionState>(
2092        expression: Arc<RevsetExpression<St>>,
2093    ) -> Arc<RevsetExpression<St>> {
2094        Arc::new(RevsetExpression::AsFilter(expression))
2095    }
2096
2097    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2098        // Mark expression as filter if any of the child nodes are filter.
2099        RevsetExpression::Present(e) => get_filter(e).map(|f| mark_filter(f.present())),
2100        RevsetExpression::NotIn(e) => get_filter(e).map(|f| mark_filter(f.negated())),
2101        RevsetExpression::Union(e1, e2) => {
2102            let f1 = get_filter(e1);
2103            let f2 = get_filter(e2);
2104            (f1.is_some() || f2.is_some())
2105                .then(|| mark_filter(f1.unwrap_or(e1).union(f2.unwrap_or(e2))))
2106        }
2107        // Bottom-up pass pulls up-right filter node from leaf '(c & f) & e' ->
2108        // '(c & e) & f', so that an intersection of filter node can be found as
2109        // a direct child of another intersection node. Suppose intersection is
2110        // left-recursive, e2 shouldn't be an intersection node. e1 may be set,
2111        // filter, (set & filter), ((set & set) & filter), ...
2112        RevsetExpression::Intersection(e1, e2) => match (get_filter(e1), get_filter(e2)) {
2113            // f1 & f2 -> filter(f1 & f2)
2114            (Some(f1), Some(f2)) => Some(mark_filter(f1.intersection(f2))),
2115            // f1 & s2 -> s2 & filter(f1)
2116            (Some(_), None) => Some(e2.intersection(e1)),
2117            // (s1a & f1b) & f2 -> s1a & filter(f1b & f2)
2118            (None, Some(f2)) => match e1.as_ref() {
2119                RevsetExpression::Intersection(e1a, e1b) => {
2120                    get_filter(e1b).map(|f1b| e1a.intersection(&mark_filter(f1b.intersection(f2))))
2121                }
2122                _ => None,
2123            },
2124            // (s1a & f1b) & s2 -> (s1a & s2) & filter(f1b)
2125            (None, None) => match e1.as_ref() {
2126                RevsetExpression::Intersection(e1a, e1b) => {
2127                    get_filter(e1b).map(|_| e1a.intersection(e2).intersection(e1b))
2128                }
2129                _ => None,
2130            },
2131        },
2132        // Difference(e1, e2) should have been unfolded to Intersection(e1, NotIn(e2)).
2133        _ => None,
2134    })
2135}
2136
2137/// Eliminates redundant nodes like `x & all()`, `~~x`.
2138///
2139/// Since this function rewrites `x & none()` to `none()`, user symbols should
2140/// have been resolved. Otherwise, an invalid symbol could be optimized out.
2141fn fold_redundant_expression<St: ExpressionState>(
2142    expression: &Arc<RevsetExpression<St>>,
2143) -> TransformedExpression<St> {
2144    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2145        RevsetExpression::Commits(commits) if commits.is_empty() => Some(RevsetExpression::none()),
2146        RevsetExpression::NotIn(outer) => match outer.as_ref() {
2147            RevsetExpression::NotIn(inner) => Some(inner.clone()),
2148            RevsetExpression::None => Some(RevsetExpression::all()),
2149            RevsetExpression::All => Some(RevsetExpression::none()),
2150            _ => None,
2151        },
2152        RevsetExpression::Union(expression1, expression2) => {
2153            match (expression1.as_ref(), expression2.as_ref()) {
2154                (_, RevsetExpression::None) => Some(expression1.clone()),
2155                (RevsetExpression::None, _) => Some(expression2.clone()),
2156                (RevsetExpression::All, _) => Some(RevsetExpression::all()),
2157                (_, RevsetExpression::All) => Some(RevsetExpression::all()),
2158                _ => None,
2159            }
2160        }
2161        RevsetExpression::Intersection(expression1, expression2) => {
2162            match (expression1.as_ref(), expression2.as_ref()) {
2163                (RevsetExpression::None, _) => Some(RevsetExpression::none()),
2164                (_, RevsetExpression::None) => Some(RevsetExpression::none()),
2165                (_, RevsetExpression::All) => Some(expression1.clone()),
2166                (RevsetExpression::All, _) => Some(expression2.clone()),
2167                _ => None,
2168            }
2169        }
2170        _ => None,
2171    })
2172}
2173
2174/// Extracts `heads` from a revset expression `ancestors(heads)`. Unfolds
2175/// generations as necessary, so `ancestors(heads, 2..)` would return
2176/// `ancestors(heads, 2..3)`, which is equivalent to `heads--`.
2177fn ancestors_to_heads<St: ExpressionState>(
2178    expression: &RevsetExpression<St>,
2179) -> Result<Arc<RevsetExpression<St>>, ()> {
2180    match ancestors_to_heads_and_parents_range(expression) {
2181        Ok((heads, PARENTS_RANGE_FULL)) => Ok(heads),
2182        _ => Err(()),
2183    }
2184}
2185
2186fn ancestors_to_heads_and_parents_range<St: ExpressionState>(
2187    expression: &RevsetExpression<St>,
2188) -> Result<(Arc<RevsetExpression<St>>, Range<u32>), ()> {
2189    match expression {
2190        RevsetExpression::Ancestors {
2191            heads,
2192            generation: GENERATION_RANGE_FULL,
2193            parents_range,
2194        } => Ok((heads.clone(), parents_range.clone())),
2195        RevsetExpression::Ancestors {
2196            heads,
2197            generation: Range {
2198                start,
2199                end: u64::MAX,
2200            },
2201            parents_range,
2202        } => Ok((
2203            Arc::new(RevsetExpression::Ancestors {
2204                heads: heads.clone(),
2205                generation: (*start)..start.saturating_add(1),
2206                parents_range: parents_range.clone(),
2207            }),
2208            parents_range.clone(),
2209        )),
2210        _ => Err(()),
2211    }
2212}
2213
2214/// Folds `::x | ::y` into `::(x | y)`, and `~::x & ~::y` into `~::(x | y)`.
2215/// Does not fold intersections of negations involving non-ancestors
2216/// expressions, since this can result in less efficient evaluation, such as for
2217/// `~::x & ~y`, which should be `x.. ~ y` instead of `~(::x | y)`.
2218fn fold_ancestors_union<St: ExpressionState>(
2219    expression: &Arc<RevsetExpression<St>>,
2220) -> TransformedExpression<St> {
2221    fn union_ancestors<St: ExpressionState>(
2222        expression1: &Arc<RevsetExpression<St>>,
2223        expression2: &Arc<RevsetExpression<St>>,
2224    ) -> TransformedExpression<St> {
2225        let heads1 = ancestors_to_heads(expression1).ok()?;
2226        let heads2 = ancestors_to_heads(expression2).ok()?;
2227        Some(heads1.union(&heads2).ancestors())
2228    }
2229
2230    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2231        RevsetExpression::Union(expression1, expression2) => {
2232            // ::x | ::y -> ::(x | y)
2233            union_ancestors(expression1, expression2)
2234        }
2235        RevsetExpression::Intersection(expression1, expression2) => {
2236            match (expression1.as_ref(), expression2.as_ref()) {
2237                // ~::x & ~::y -> ~(::x | ::y) -> ~::(x | y)
2238                (RevsetExpression::NotIn(complement1), RevsetExpression::NotIn(complement2)) => {
2239                    union_ancestors(complement1, complement2).map(|expression| expression.negated())
2240                }
2241                _ => None,
2242            }
2243        }
2244        _ => None,
2245    })
2246}
2247
2248/// Transforms expressions like `heads(roots..heads & filters)` into a combined
2249/// operation where possible. Also optimizes the heads of ancestors expressions
2250/// involving ranges or filters such as `::(foo..bar)` or `::mine()`.
2251///
2252/// Ancestors and negated ancestors should have already been moved to the left
2253/// in intersections, and negated ancestors should have been combined already.
2254fn fold_heads_range<St: ExpressionState>(
2255    expression: &Arc<RevsetExpression<St>>,
2256) -> TransformedExpression<St> {
2257    // Represents `roots..heads & filter`
2258    struct FilteredRange<St: ExpressionState> {
2259        roots: Arc<RevsetExpression<St>>,
2260        heads_and_parents_range: Option<(Arc<RevsetExpression<St>>, Range<u32>)>,
2261        filter: Arc<RevsetExpression<St>>,
2262    }
2263
2264    impl<St: ExpressionState> FilteredRange<St> {
2265        fn new(roots: Arc<RevsetExpression<St>>) -> Self {
2266            // roots.. & all()
2267            Self {
2268                roots,
2269                heads_and_parents_range: None,
2270                filter: RevsetExpression::all(),
2271            }
2272        }
2273
2274        fn add(mut self, expression: &Arc<RevsetExpression<St>>) -> Self {
2275            if self.heads_and_parents_range.is_none() {
2276                // x.. & ::y -> x..y
2277                if let Ok(heads_and_parents_range) =
2278                    ancestors_to_heads_and_parents_range(expression)
2279                {
2280                    self.heads_and_parents_range = Some(heads_and_parents_range);
2281                    return self;
2282                }
2283            }
2284            self.add_filter(expression)
2285        }
2286
2287        fn add_filter(mut self, expression: &Arc<RevsetExpression<St>>) -> Self {
2288            self.filter = if let RevsetExpression::All = self.filter.as_ref() {
2289                // x..y & all() & f -> x..y & f
2290                expression.clone()
2291            } else {
2292                self.filter.intersection(expression)
2293            };
2294            self
2295        }
2296    }
2297
2298    fn to_filtered_range<St: ExpressionState>(
2299        expression: &Arc<RevsetExpression<St>>,
2300    ) -> Option<FilteredRange<St>> {
2301        // If the first expression is `ancestors(x)`, then we already know the range
2302        // must be `none()..x`, since any roots would've been moved to the left by an
2303        // earlier pass.
2304        if let Ok(heads_and_parents_range) = ancestors_to_heads_and_parents_range(expression) {
2305            return Some(FilteredRange {
2306                roots: RevsetExpression::none(),
2307                heads_and_parents_range: Some(heads_and_parents_range),
2308                filter: RevsetExpression::all(),
2309            });
2310        }
2311        match expression.as_ref() {
2312            // All roots should have been moved to the start of the intersection by an earlier pass,
2313            // so we can set the roots based on the first expression in the intersection.
2314            RevsetExpression::NotIn(complement) => {
2315                if let Ok(roots) = ancestors_to_heads(complement) {
2316                    Some(FilteredRange::new(roots))
2317                } else {
2318                    // If the first expression is a non-ancestors negation, we still want to use
2319                    // `HeadsRange` since `~x` is equivalent to `::visible_heads() ~ x`.
2320                    Some(FilteredRange::new(RevsetExpression::none()).add_filter(expression))
2321                }
2322            }
2323            // We also want to optimize `heads()` if the first expression is `all()` or a filter.
2324            RevsetExpression::All | RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
2325                Some(FilteredRange::new(RevsetExpression::none()).add_filter(expression))
2326            }
2327            // We only need to handle intersections recursively. Differences will have been
2328            // unfolded already.
2329            RevsetExpression::Intersection(expression1, expression2) => {
2330                to_filtered_range(expression1).map(|filtered_range| filtered_range.add(expression2))
2331            }
2332            _ => None,
2333        }
2334    }
2335
2336    fn to_heads_range<St: ExpressionState>(
2337        candidates: &Arc<RevsetExpression<St>>,
2338    ) -> Option<Arc<RevsetExpression<St>>> {
2339        to_filtered_range(candidates).map(|filtered_range| {
2340            let (heads, parents_range) =
2341                filtered_range.heads_and_parents_range.unwrap_or_else(|| {
2342                    (
2343                        RevsetExpression::visible_heads_or_referenced(),
2344                        PARENTS_RANGE_FULL,
2345                    )
2346                });
2347            RevsetExpression::HeadsRange {
2348                roots: filtered_range.roots,
2349                heads,
2350                parents_range,
2351                filter: filtered_range.filter,
2352            }
2353            .into()
2354        })
2355    }
2356
2357    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2358        // ::(x..y & filter) -> ::heads_range(x, y, filter)
2359        // ::filter -> ::heads_range(none(), visible_heads_or_referenced(), filter)
2360        RevsetExpression::Ancestors {
2361            heads,
2362            // This optimization is only valid for full generation and parents ranges, since
2363            // otherwise adding `heads()` would change the result.
2364            generation: GENERATION_RANGE_FULL,
2365            parents_range: PARENTS_RANGE_FULL,
2366        } => to_heads_range(heads).map(|heads| heads.ancestors()),
2367        // heads(x..y & filter) -> heads_range(x, y, filter)
2368        // heads(filter) -> heads_range(none(), visible_heads_or_referenced(), filter)
2369        RevsetExpression::Heads(candidates) => to_heads_range(candidates),
2370        _ => None,
2371    })
2372}
2373
2374fn to_difference_range<St: ExpressionState>(
2375    expression: &Arc<RevsetExpression<St>>,
2376    complement: &Arc<RevsetExpression<St>>,
2377) -> TransformedExpression<St> {
2378    let RevsetExpression::Ancestors {
2379        heads,
2380        generation,
2381        parents_range,
2382    } = expression.as_ref()
2383    else {
2384        return None;
2385    };
2386    let roots = ancestors_to_heads(complement).ok()?;
2387    // ::heads & ~(::roots) -> roots..heads
2388    // ::heads & ~(::roots-) -> ::heads & ~ancestors(roots, 1..) -> roots-..heads
2389    Some(Arc::new(RevsetExpression::Range {
2390        roots,
2391        heads: heads.clone(),
2392        generation: generation.clone(),
2393        parents_range: parents_range.clone(),
2394    }))
2395}
2396
2397/// Transforms negative intersection to difference. Redundant intersections like
2398/// `all() & e` should have been removed.
2399fn fold_difference<St: ExpressionState>(
2400    expression: &Arc<RevsetExpression<St>>,
2401) -> TransformedExpression<St> {
2402    fn to_difference<St: ExpressionState>(
2403        expression: &Arc<RevsetExpression<St>>,
2404        complement: &Arc<RevsetExpression<St>>,
2405    ) -> Arc<RevsetExpression<St>> {
2406        to_difference_range(expression, complement).unwrap_or_else(|| expression.minus(complement))
2407    }
2408
2409    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2410        RevsetExpression::Intersection(expression1, expression2) => {
2411            match (expression1.as_ref(), expression2.as_ref()) {
2412                // For '~x & f', don't move filter node 'f' left
2413                (_, RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_)) => None,
2414                (_, RevsetExpression::NotIn(complement)) => {
2415                    Some(to_difference(expression1, complement))
2416                }
2417                (RevsetExpression::NotIn(complement), _) => {
2418                    Some(to_difference(expression2, complement))
2419                }
2420                _ => None,
2421            }
2422        }
2423        _ => None,
2424    })
2425}
2426
2427/// Transforms remaining negated ancestors `~(::h)` to range `h..`.
2428///
2429/// Since this rule inserts redundant `visible_heads()`, negative intersections
2430/// should have been transformed.
2431fn fold_not_in_ancestors<St: ExpressionState>(
2432    expression: &Arc<RevsetExpression<St>>,
2433) -> TransformedExpression<St> {
2434    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2435        RevsetExpression::NotIn(complement)
2436            if matches!(complement.as_ref(), RevsetExpression::Ancestors { .. }) =>
2437        {
2438            // ~(::heads) -> heads..
2439            // ~(::heads-) -> ~ancestors(heads, 1..) -> heads-..
2440            to_difference_range(
2441                &RevsetExpression::visible_heads_or_referenced().ancestors(),
2442                complement,
2443            )
2444        }
2445        _ => None,
2446    })
2447}
2448
2449/// Transforms binary difference to more primitive negative intersection.
2450///
2451/// For example, `all() ~ e` will become `all() & ~e`, which can be simplified
2452/// further by `fold_redundant_expression()`.
2453fn unfold_difference<St: ExpressionState>(
2454    expression: &Arc<RevsetExpression<St>>,
2455) -> TransformedExpression<St> {
2456    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2457        // roots..heads -> ::heads & ~(::roots)
2458        RevsetExpression::Range {
2459            roots,
2460            heads,
2461            parents_range,
2462            generation,
2463        } => {
2464            let heads_ancestors = Arc::new(RevsetExpression::Ancestors {
2465                heads: heads.clone(),
2466                generation: generation.clone(),
2467                parents_range: parents_range.clone(),
2468            });
2469            Some(heads_ancestors.intersection(&roots.ancestors().negated()))
2470        }
2471        RevsetExpression::Difference(expression1, expression2) => {
2472            Some(expression1.intersection(&expression2.negated()))
2473        }
2474        _ => None,
2475    })
2476}
2477
2478/// Transforms nested `ancestors()`/`parents()`/`descendants()`/`children()`
2479/// like `h---`/`r+++`.
2480fn fold_generation<St: ExpressionState>(
2481    expression: &Arc<RevsetExpression<St>>,
2482) -> TransformedExpression<St> {
2483    fn add_generation(generation1: &Range<u64>, generation2: &Range<u64>) -> Range<u64> {
2484        // For any (g1, g2) in (generation1, generation2), g1 + g2.
2485        if generation1.is_empty() || generation2.is_empty() {
2486            GENERATION_RANGE_EMPTY
2487        } else {
2488            let start = u64::saturating_add(generation1.start, generation2.start);
2489            let end = u64::saturating_add(generation1.end, generation2.end - 1);
2490            start..end
2491        }
2492    }
2493
2494    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
2495        RevsetExpression::Ancestors {
2496            heads,
2497            generation: generation1,
2498            parents_range: parents1,
2499        } => {
2500            match heads.as_ref() {
2501                // (h-)- -> ancestors(ancestors(h, 1), 1) -> ancestors(h, 2)
2502                // ::(h-) -> ancestors(ancestors(h, 1), ..) -> ancestors(h, 1..)
2503                // (::h)- -> ancestors(ancestors(h, ..), 1) -> ancestors(h, 1..)
2504                RevsetExpression::Ancestors {
2505                    heads,
2506                    generation: generation2,
2507                    parents_range: parents2,
2508                } if parents2 == parents1 => Some(Arc::new(RevsetExpression::Ancestors {
2509                    heads: heads.clone(),
2510                    generation: add_generation(generation1, generation2),
2511                    parents_range: parents1.clone(),
2512                })),
2513                _ => None,
2514            }
2515        }
2516        RevsetExpression::Descendants {
2517            roots,
2518            generation: generation1,
2519        } => {
2520            match roots.as_ref() {
2521                // (r+)+ -> descendants(descendants(r, 1), 1) -> descendants(r, 2)
2522                // (r+):: -> descendants(descendants(r, 1), ..) -> descendants(r, 1..)
2523                // (r::)+ -> descendants(descendants(r, ..), 1) -> descendants(r, 1..)
2524                RevsetExpression::Descendants {
2525                    roots,
2526                    generation: generation2,
2527                } => Some(Arc::new(RevsetExpression::Descendants {
2528                    roots: roots.clone(),
2529                    generation: add_generation(generation1, generation2),
2530                })),
2531                _ => None,
2532            }
2533        }
2534        // Range should have been unfolded to intersection of Ancestors.
2535        _ => None,
2536    })
2537}
2538
2539/// Rewrites the given `expression` tree to reduce evaluation cost. Returns new
2540/// tree.
2541pub fn optimize<St: ExpressionState>(
2542    expression: Arc<RevsetExpression<St>>,
2543) -> Arc<RevsetExpression<St>> {
2544    // Since fold_redundant_expression() can remove hidden commits that look
2545    // redundant, referenced commits should be collected earlier.
2546    let expression = resolve_referenced_commits(&expression).unwrap_or(expression);
2547    let expression = unfold_difference(&expression).unwrap_or(expression);
2548    let expression = fold_redundant_expression(&expression).unwrap_or(expression);
2549    let expression = fold_generation(&expression).unwrap_or(expression);
2550    let expression = flatten_intersections(&expression).unwrap_or(expression);
2551    let expression = sort_negations_and_ancestors(&expression).unwrap_or(expression);
2552    let expression = fold_ancestors_union(&expression).unwrap_or(expression);
2553    let expression = internalize_filter(&expression).unwrap_or(expression);
2554    let expression = fold_heads_range(&expression).unwrap_or(expression);
2555    let expression = fold_difference(&expression).unwrap_or(expression);
2556    fold_not_in_ancestors(&expression).unwrap_or(expression)
2557}
2558
2559// TODO: find better place to host this function (or add compile-time revset
2560// parsing and resolution like
2561// `revset!("{unwanted}..{wanted}").evaluate(repo)`?)
2562pub fn walk_revs<'index>(
2563    repo: &'index dyn Repo,
2564    wanted: &[CommitId],
2565    unwanted: &[CommitId],
2566) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
2567    RevsetExpression::commits(unwanted.to_vec())
2568        .range(&RevsetExpression::commits(wanted.to_vec()))
2569        .evaluate(repo)
2570}
2571
2572fn reload_repo_at_operation(
2573    repo: &dyn Repo,
2574    op_str: &str,
2575) -> Result<Arc<ReadonlyRepo>, RevsetResolutionError> {
2576    // TODO: Maybe we should ensure that the resolved operation is an ancestor
2577    // of the current operation. If it weren't, there might be commits unknown
2578    // to the outer repo.
2579    let base_repo = repo.base_repo();
2580    let operation = op_walk::resolve_op_with_repo(base_repo, op_str)
2581        .map_err(|err| RevsetResolutionError::Other(err.into()))?;
2582    base_repo.reload_at(&operation).map_err(|err| match err {
2583        RepoLoaderError::Backend(err) => RevsetResolutionError::Backend(err),
2584        RepoLoaderError::Index(_)
2585        | RepoLoaderError::IndexStore(_)
2586        | RepoLoaderError::OpHeadResolution(_)
2587        | RepoLoaderError::OpHeadsStoreError(_)
2588        | RepoLoaderError::OpStore(_)
2589        | RepoLoaderError::TransactionCommit(_) => RevsetResolutionError::Other(err.into()),
2590    })
2591}
2592
2593fn resolve_remote_bookmark(
2594    repo: &dyn Repo,
2595    symbol: RemoteRefSymbol<'_>,
2596) -> Result<CommitId, RevsetResolutionError> {
2597    let target = &repo.view().get_remote_bookmark(symbol).target;
2598    to_resolved_ref("remote_bookmark", symbol, target)?
2599        .ok_or_else(|| make_no_such_symbol_error(repo, symbol.to_string()))
2600}
2601
2602fn to_resolved_ref(
2603    kind: &'static str,
2604    symbol: impl ToString,
2605    target: &RefTarget,
2606) -> Result<Option<CommitId>, RevsetResolutionError> {
2607    match target.as_resolved() {
2608        Some(Some(id)) => Ok(Some(id.clone())),
2609        Some(None) => Ok(None),
2610        None => Err(RevsetResolutionError::ConflictedRef {
2611            kind,
2612            symbol: symbol.to_string(),
2613            targets: target.added_ids().cloned().collect(),
2614        }),
2615    }
2616}
2617
2618fn all_formatted_bookmark_symbols(
2619    repo: &dyn Repo,
2620    include_synced_remotes: bool,
2621) -> impl Iterator<Item = String> {
2622    let view = repo.view();
2623    view.bookmarks().flat_map(move |(name, bookmark_target)| {
2624        let local_target = bookmark_target.local_target;
2625        let local_symbol = local_target
2626            .is_present()
2627            .then(|| format_symbol(name.as_str()));
2628        let remote_symbols = bookmark_target
2629            .remote_refs
2630            .into_iter()
2631            .filter(move |&(_, remote_ref)| {
2632                include_synced_remotes
2633                    || !remote_ref.is_tracked()
2634                    || remote_ref.target != *local_target
2635            })
2636            .map(move |(remote, _)| format_remote_symbol(name.as_str(), remote.as_str()));
2637        local_symbol.into_iter().chain(remote_symbols)
2638    })
2639}
2640
2641fn make_no_such_symbol_error(repo: &dyn Repo, name: String) -> RevsetResolutionError {
2642    // TODO: include tags?
2643    let bookmark_names = all_formatted_bookmark_symbols(repo, name.contains('@'));
2644    let candidates = collect_similar(&name, bookmark_names);
2645    RevsetResolutionError::NoSuchRevision { name, candidates }
2646}
2647
2648/// A symbol resolver for a specific namespace of labels.
2649///
2650/// Returns None if it cannot handle the symbol.
2651pub trait PartialSymbolResolver {
2652    fn resolve_symbol(
2653        &self,
2654        repo: &dyn Repo,
2655        symbol: &str,
2656    ) -> Result<Option<CommitId>, RevsetResolutionError>;
2657}
2658
2659struct TagResolver;
2660
2661impl PartialSymbolResolver for TagResolver {
2662    fn resolve_symbol(
2663        &self,
2664        repo: &dyn Repo,
2665        symbol: &str,
2666    ) -> Result<Option<CommitId>, RevsetResolutionError> {
2667        let target = repo.view().get_local_tag(symbol.as_ref());
2668        to_resolved_ref("tag", symbol, target)
2669    }
2670}
2671
2672struct BookmarkResolver;
2673
2674impl PartialSymbolResolver for BookmarkResolver {
2675    fn resolve_symbol(
2676        &self,
2677        repo: &dyn Repo,
2678        symbol: &str,
2679    ) -> Result<Option<CommitId>, RevsetResolutionError> {
2680        let target = repo.view().get_local_bookmark(symbol.as_ref());
2681        to_resolved_ref("bookmark", symbol, target)
2682    }
2683}
2684
2685struct GitRefResolver;
2686
2687impl PartialSymbolResolver for GitRefResolver {
2688    fn resolve_symbol(
2689        &self,
2690        repo: &dyn Repo,
2691        symbol: &str,
2692    ) -> Result<Option<CommitId>, RevsetResolutionError> {
2693        let view = repo.view();
2694        for git_ref_prefix in &["", "refs/"] {
2695            let target = view.get_git_ref([git_ref_prefix, symbol].concat().as_ref());
2696            if let Some(id) = to_resolved_ref("git_ref", symbol, target)? {
2697                return Ok(Some(id));
2698            }
2699        }
2700        Ok(None)
2701    }
2702}
2703
2704const DEFAULT_RESOLVERS: &[&dyn PartialSymbolResolver] =
2705    &[&TagResolver, &BookmarkResolver, &GitRefResolver];
2706
2707struct CommitPrefixResolver<'a> {
2708    context_repo: &'a dyn Repo,
2709    context: Option<&'a IdPrefixContext>,
2710}
2711
2712impl CommitPrefixResolver<'_> {
2713    fn try_resolve(
2714        &self,
2715        repo: &dyn Repo,
2716        prefix: &HexPrefix,
2717    ) -> Result<Option<CommitId>, RevsetResolutionError> {
2718        let index = self
2719            .context
2720            .map(|ctx| ctx.populate(self.context_repo))
2721            .transpose()
2722            .map_err(|err| RevsetResolutionError::Other(err.into()))?
2723            .unwrap_or(IdPrefixIndex::empty());
2724        match index
2725            .resolve_commit_prefix(repo, prefix)
2726            .map_err(|err| RevsetResolutionError::Other(err.into()))?
2727        {
2728            PrefixResolution::AmbiguousMatch => {
2729                Err(RevsetResolutionError::AmbiguousCommitIdPrefix(prefix.hex()))
2730            }
2731            PrefixResolution::SingleMatch(id) => Ok(Some(id)),
2732            PrefixResolution::NoMatch => Ok(None),
2733        }
2734    }
2735}
2736
2737impl PartialSymbolResolver for CommitPrefixResolver<'_> {
2738    fn resolve_symbol(
2739        &self,
2740        repo: &dyn Repo,
2741        symbol: &str,
2742    ) -> Result<Option<CommitId>, RevsetResolutionError> {
2743        if let Some(prefix) = HexPrefix::try_from_hex(symbol) {
2744            self.try_resolve(repo, &prefix)
2745        } else {
2746            Ok(None)
2747        }
2748    }
2749}
2750
2751struct ChangePrefixResolver<'a> {
2752    context_repo: &'a dyn Repo,
2753    context: Option<&'a IdPrefixContext>,
2754}
2755
2756impl ChangePrefixResolver<'_> {
2757    fn try_resolve(
2758        &self,
2759        repo: &dyn Repo,
2760        prefix: &HexPrefix,
2761    ) -> Result<Option<ResolvedChangeTargets>, RevsetResolutionError> {
2762        let index = self
2763            .context
2764            .map(|ctx| ctx.populate(self.context_repo))
2765            .transpose()
2766            .map_err(|err| RevsetResolutionError::Other(err.into()))?
2767            .unwrap_or(IdPrefixIndex::empty());
2768        match index
2769            .resolve_change_prefix(repo, prefix)
2770            .map_err(|err| RevsetResolutionError::Other(err.into()))?
2771        {
2772            PrefixResolution::AmbiguousMatch => Err(
2773                RevsetResolutionError::AmbiguousChangeIdPrefix(prefix.reverse_hex()),
2774            ),
2775            PrefixResolution::SingleMatch(ids) => Ok(Some(ids)),
2776            PrefixResolution::NoMatch => Ok(None),
2777        }
2778    }
2779}
2780
2781impl PartialSymbolResolver for ChangePrefixResolver<'_> {
2782    fn resolve_symbol(
2783        &self,
2784        repo: &dyn Repo,
2785        symbol: &str,
2786    ) -> Result<Option<CommitId>, RevsetResolutionError> {
2787        let (change_id, offset) = if let Some((prefix, suffix)) = symbol.split_once('/') {
2788            if prefix.is_empty() || suffix.is_empty() {
2789                return Ok(None);
2790            }
2791            let Ok(offset) = suffix.parse() else {
2792                return Ok(None);
2793            };
2794            (prefix, Some(offset))
2795        } else {
2796            (symbol, None)
2797        };
2798        let Some(prefix) = HexPrefix::try_from_reverse_hex(change_id) else {
2799            return Ok(None);
2800        };
2801        let Some(targets) = self.try_resolve(repo, &prefix)? else {
2802            return Ok(None);
2803        };
2804        if let Some(offset) = offset {
2805            return Ok(targets.at_offset(offset).cloned());
2806        }
2807        match targets.visible_with_offsets().at_most_one() {
2808            Ok(maybe_resolved) => Ok(maybe_resolved.map(|(_, target)| target.clone())),
2809            Err(visible_targets) => Err(RevsetResolutionError::DivergentChangeId {
2810                symbol: change_id.to_owned(),
2811                visible_targets: visible_targets
2812                    .map(|(i, target)| (i, target.clone()))
2813                    .collect_vec(),
2814            }),
2815        }
2816    }
2817}
2818
2819/// An extension of the [`SymbolResolver`].
2820///
2821/// Each PartialSymbolResolver will be invoked in order, its result used if one
2822/// is provided. Native resolvers are always invoked first. In the future, we
2823/// may provide a way for extensions to override native resolvers like tags and
2824/// bookmarks.
2825pub trait SymbolResolverExtension: Send + Sync {
2826    /// PartialSymbolResolvers can initialize some global data by using the
2827    /// `context_repo`, but the `context_repo` may point to a different
2828    /// operation from the `repo` passed into `resolve_symbol()`. For
2829    /// resolution, the latter `repo` should be used.
2830    fn new_resolvers<'a>(
2831        &self,
2832        context_repo: &'a dyn Repo,
2833    ) -> Vec<Box<dyn PartialSymbolResolver + 'a>>;
2834}
2835
2836/// Resolves bookmarks, remote bookmarks, tags, git refs, and full and
2837/// abbreviated commit and change ids.
2838pub struct SymbolResolver<'a> {
2839    commit_id_resolver: CommitPrefixResolver<'a>,
2840    change_id_resolver: ChangePrefixResolver<'a>,
2841    extensions: Vec<Box<dyn PartialSymbolResolver + 'a>>,
2842}
2843
2844impl<'a> SymbolResolver<'a> {
2845    /// Creates new symbol resolver that will first disambiguate short ID
2846    /// prefixes within the given `context_repo` if configured.
2847    pub fn new(
2848        context_repo: &'a dyn Repo,
2849        extensions: &[impl AsRef<dyn SymbolResolverExtension>],
2850    ) -> Self {
2851        SymbolResolver {
2852            commit_id_resolver: CommitPrefixResolver {
2853                context_repo,
2854                context: None,
2855            },
2856            change_id_resolver: ChangePrefixResolver {
2857                context_repo,
2858                context: None,
2859            },
2860            extensions: extensions
2861                .iter()
2862                .flat_map(|ext| ext.as_ref().new_resolvers(context_repo))
2863                .collect(),
2864        }
2865    }
2866
2867    pub fn with_id_prefix_context(mut self, id_prefix_context: &'a IdPrefixContext) -> Self {
2868        self.commit_id_resolver.context = Some(id_prefix_context);
2869        self.change_id_resolver.context = Some(id_prefix_context);
2870        self
2871    }
2872
2873    fn partial_resolvers(&self) -> impl Iterator<Item = &(dyn PartialSymbolResolver + 'a)> {
2874        let prefix_resolvers: [&dyn PartialSymbolResolver; 2] =
2875            [&self.commit_id_resolver, &self.change_id_resolver];
2876        itertools::chain!(
2877            DEFAULT_RESOLVERS.iter().copied(),
2878            prefix_resolvers,
2879            self.extensions.iter().map(|e| e.as_ref())
2880        )
2881    }
2882
2883    /// Looks up `symbol` in the given `repo`.
2884    pub fn resolve_symbol(
2885        &self,
2886        repo: &dyn Repo,
2887        symbol: &str,
2888    ) -> Result<CommitId, RevsetResolutionError> {
2889        if symbol.is_empty() {
2890            return Err(RevsetResolutionError::EmptyString);
2891        }
2892
2893        for partial_resolver in self.partial_resolvers() {
2894            if let Some(id) = partial_resolver.resolve_symbol(repo, symbol)? {
2895                return Ok(id);
2896            }
2897        }
2898
2899        Err(make_no_such_symbol_error(repo, format_symbol(symbol)))
2900    }
2901}
2902
2903fn resolve_commit_ref(
2904    repo: &dyn Repo,
2905    commit_ref: &RevsetCommitRef,
2906    symbol_resolver: &SymbolResolver,
2907) -> Result<Vec<CommitId>, RevsetResolutionError> {
2908    match commit_ref {
2909        RevsetCommitRef::Symbol(symbol) => {
2910            let commit_id = symbol_resolver.resolve_symbol(repo, symbol)?;
2911            Ok(vec![commit_id])
2912        }
2913        RevsetCommitRef::RemoteSymbol(symbol) => {
2914            let commit_id = resolve_remote_bookmark(repo, symbol.as_ref())?;
2915            Ok(vec![commit_id])
2916        }
2917        RevsetCommitRef::WorkingCopy(name) => {
2918            if let Some(commit_id) = repo.view().get_wc_commit_id(name) {
2919                Ok(vec![commit_id.clone()])
2920            } else {
2921                Err(RevsetResolutionError::WorkspaceMissingWorkingCopy { name: name.clone() })
2922            }
2923        }
2924        RevsetCommitRef::WorkingCopies => {
2925            let wc_commits = repo.view().wc_commit_ids().values().cloned().collect_vec();
2926            Ok(wc_commits)
2927        }
2928        RevsetCommitRef::ChangeId(prefix) => {
2929            let resolver = &symbol_resolver.change_id_resolver;
2930            Ok(resolver
2931                .try_resolve(repo, prefix)?
2932                .and_then(ResolvedChangeTargets::into_visible)
2933                .unwrap_or_else(Vec::new))
2934        }
2935        RevsetCommitRef::CommitId(prefix) => {
2936            let resolver = &symbol_resolver.commit_id_resolver;
2937            Ok(resolver.try_resolve(repo, prefix)?.into_iter().collect())
2938        }
2939        RevsetCommitRef::Bookmarks(expression) => {
2940            let commit_ids = repo
2941                .view()
2942                .local_bookmarks_matching(&expression.to_matcher())
2943                .flat_map(|(_, target)| target.added_ids())
2944                .cloned()
2945                .collect();
2946            Ok(commit_ids)
2947        }
2948        RevsetCommitRef::RemoteBookmarks {
2949            bookmark,
2950            remote,
2951            remote_ref_state,
2952        } => {
2953            let bookmark_matcher = bookmark.to_matcher();
2954            let remote_matcher = remote.to_matcher();
2955            let commit_ids = repo
2956                .view()
2957                .remote_bookmarks_matching(&bookmark_matcher, &remote_matcher)
2958                .filter(|(_, remote_ref)| {
2959                    remote_ref_state.is_none_or(|state| remote_ref.state == state)
2960                })
2961                .flat_map(|(_, remote_ref)| remote_ref.target.added_ids())
2962                .cloned()
2963                .collect();
2964            Ok(commit_ids)
2965        }
2966        RevsetCommitRef::Tags(expression) => {
2967            let commit_ids = repo
2968                .view()
2969                .local_tags_matching(&expression.to_matcher())
2970                .flat_map(|(_, target)| target.added_ids())
2971                .cloned()
2972                .collect();
2973            Ok(commit_ids)
2974        }
2975        RevsetCommitRef::GitRefs => {
2976            let mut commit_ids = vec![];
2977            for ref_target in repo.view().git_refs().values() {
2978                commit_ids.extend(ref_target.added_ids().cloned());
2979            }
2980            Ok(commit_ids)
2981        }
2982        RevsetCommitRef::GitHead => Ok(repo.view().git_head().added_ids().cloned().collect()),
2983    }
2984}
2985
2986/// Resolves symbols and commit refs recursively.
2987struct ExpressionSymbolResolver<'a, 'b> {
2988    base_repo: &'a dyn Repo,
2989    repo_stack: Vec<Arc<ReadonlyRepo>>,
2990    symbol_resolver: &'a SymbolResolver<'b>,
2991}
2992
2993impl<'a, 'b> ExpressionSymbolResolver<'a, 'b> {
2994    fn new(base_repo: &'a dyn Repo, symbol_resolver: &'a SymbolResolver<'b>) -> Self {
2995        Self {
2996            base_repo,
2997            repo_stack: vec![],
2998            symbol_resolver,
2999        }
3000    }
3001
3002    fn repo(&self) -> &dyn Repo {
3003        self.repo_stack
3004            .last()
3005            .map_or(self.base_repo, |repo| repo.as_ref())
3006    }
3007}
3008
3009impl ExpressionStateFolder<UserExpressionState, ResolvedExpressionState>
3010    for ExpressionSymbolResolver<'_, '_>
3011{
3012    type Error = RevsetResolutionError;
3013
3014    fn fold_expression(
3015        &mut self,
3016        expression: &UserRevsetExpression,
3017    ) -> Result<Arc<ResolvedRevsetExpression>, Self::Error> {
3018        match expression {
3019            // 'present(x)' opens new symbol resolution scope to map error to 'none()'
3020            RevsetExpression::Present(candidates) => {
3021                self.fold_expression(candidates).or_else(|err| match err {
3022                    RevsetResolutionError::NoSuchRevision { .. }
3023                    | RevsetResolutionError::WorkspaceMissingWorkingCopy { .. } => {
3024                        Ok(RevsetExpression::none())
3025                    }
3026                    RevsetResolutionError::EmptyString
3027                    | RevsetResolutionError::AmbiguousCommitIdPrefix(_)
3028                    | RevsetResolutionError::AmbiguousChangeIdPrefix(_)
3029                    | RevsetResolutionError::DivergentChangeId { .. }
3030                    | RevsetResolutionError::ConflictedRef { .. }
3031                    | RevsetResolutionError::Backend(_)
3032                    | RevsetResolutionError::Other(_) => Err(err),
3033                })
3034            }
3035            _ => fold_child_expression_state(self, expression),
3036        }
3037    }
3038
3039    fn fold_commit_ref(
3040        &mut self,
3041        commit_ref: &RevsetCommitRef,
3042    ) -> Result<Arc<ResolvedRevsetExpression>, Self::Error> {
3043        let commit_ids = resolve_commit_ref(self.repo(), commit_ref, self.symbol_resolver)?;
3044        Ok(RevsetExpression::commits(commit_ids))
3045    }
3046
3047    fn fold_at_operation(
3048        &mut self,
3049        operation: &String,
3050        candidates: &UserRevsetExpression,
3051    ) -> Result<Arc<ResolvedRevsetExpression>, Self::Error> {
3052        let repo = reload_repo_at_operation(self.repo(), operation)?;
3053        self.repo_stack.push(repo);
3054        let candidates = self.fold_expression(candidates)?;
3055        let visible_heads = self.repo().view().heads().iter().cloned().collect();
3056        self.repo_stack.pop();
3057        Ok(Arc::new(RevsetExpression::WithinVisibility {
3058            candidates,
3059            visible_heads,
3060        }))
3061    }
3062}
3063
3064fn resolve_symbols(
3065    repo: &dyn Repo,
3066    expression: &UserRevsetExpression,
3067    symbol_resolver: &SymbolResolver,
3068) -> Result<Arc<ResolvedRevsetExpression>, RevsetResolutionError> {
3069    let mut resolver = ExpressionSymbolResolver::new(repo, symbol_resolver);
3070    resolver.fold_expression(expression)
3071}
3072
3073/// Inserts implicit `all()` and `visible_heads()` nodes to the `expression`.
3074///
3075/// Symbols and commit refs in the `expression` should have been resolved.
3076///
3077/// This is a separate step because a symbol-resolved `expression` may be
3078/// transformed further to e.g. combine OR-ed `Commits(_)`, or to collect
3079/// commit ids to make `all()` include hidden-but-specified commits. The
3080/// return type `ResolvedExpression` is stricter than `RevsetExpression`,
3081/// and isn't designed for such transformation.
3082fn resolve_visibility(
3083    repo: &dyn Repo,
3084    expression: &ResolvedRevsetExpression,
3085) -> ResolvedExpression {
3086    let context = VisibilityResolutionContext {
3087        referenced_commits: &[],
3088        visible_heads: &repo.view().heads().iter().cloned().collect_vec(),
3089        root: repo.store().root_commit_id(),
3090    };
3091    context.resolve(expression)
3092}
3093
3094#[derive(Clone, Debug)]
3095struct VisibilityResolutionContext<'a> {
3096    referenced_commits: &'a [CommitId],
3097    visible_heads: &'a [CommitId],
3098    root: &'a CommitId,
3099}
3100
3101impl VisibilityResolutionContext<'_> {
3102    /// Resolves expression tree as set.
3103    fn resolve(&self, expression: &ResolvedRevsetExpression) -> ResolvedExpression {
3104        match expression {
3105            RevsetExpression::None => ResolvedExpression::Commits(vec![]),
3106            RevsetExpression::All => self.resolve_all(),
3107            RevsetExpression::VisibleHeads => self.resolve_visible_heads(),
3108            RevsetExpression::VisibleHeadsOrReferenced => {
3109                self.resolve_visible_heads_or_referenced()
3110            }
3111            RevsetExpression::Root => self.resolve_root(),
3112            RevsetExpression::Commits(commit_ids) => {
3113                ResolvedExpression::Commits(commit_ids.clone())
3114            }
3115            RevsetExpression::CommitRef(commit_ref) => match *commit_ref {},
3116            RevsetExpression::Ancestors {
3117                heads,
3118                generation,
3119                parents_range,
3120            } => ResolvedExpression::Ancestors {
3121                heads: self.resolve(heads).into(),
3122                generation: generation.clone(),
3123                parents_range: parents_range.clone(),
3124            },
3125            RevsetExpression::Descendants { roots, generation } => ResolvedExpression::DagRange {
3126                roots: self.resolve(roots).into(),
3127                heads: self.resolve_visible_heads_or_referenced().into(),
3128                generation_from_roots: generation.clone(),
3129            },
3130            RevsetExpression::Range {
3131                roots,
3132                heads,
3133                generation,
3134                parents_range,
3135            } => ResolvedExpression::Range {
3136                roots: self.resolve(roots).into(),
3137                heads: self.resolve(heads).into(),
3138                generation: generation.clone(),
3139                parents_range: parents_range.clone(),
3140            },
3141            RevsetExpression::DagRange { roots, heads } => ResolvedExpression::DagRange {
3142                roots: self.resolve(roots).into(),
3143                heads: self.resolve(heads).into(),
3144                generation_from_roots: GENERATION_RANGE_FULL,
3145            },
3146            RevsetExpression::Reachable { sources, domain } => ResolvedExpression::Reachable {
3147                sources: self.resolve(sources).into(),
3148                domain: self.resolve(domain).into(),
3149            },
3150            RevsetExpression::Heads(candidates) => {
3151                ResolvedExpression::Heads(self.resolve(candidates).into())
3152            }
3153            RevsetExpression::HeadsRange {
3154                roots,
3155                heads,
3156                parents_range,
3157                filter,
3158            } => ResolvedExpression::HeadsRange {
3159                roots: self.resolve(roots).into(),
3160                heads: self.resolve(heads).into(),
3161                parents_range: parents_range.clone(),
3162                filter: (!matches!(filter.as_ref(), RevsetExpression::All))
3163                    .then(|| self.resolve_predicate(filter)),
3164            },
3165            RevsetExpression::Roots(candidates) => {
3166                ResolvedExpression::Roots(self.resolve(candidates).into())
3167            }
3168            RevsetExpression::ForkPoint(expression) => {
3169                ResolvedExpression::ForkPoint(self.resolve(expression).into())
3170            }
3171            RevsetExpression::Bisect(expression) => {
3172                ResolvedExpression::Bisect(self.resolve(expression).into())
3173            }
3174            RevsetExpression::Latest { candidates, count } => ResolvedExpression::Latest {
3175                candidates: self.resolve(candidates).into(),
3176                count: *count,
3177            },
3178            RevsetExpression::HasSize { candidates, count } => ResolvedExpression::HasSize {
3179                candidates: self.resolve(candidates).into(),
3180                count: *count,
3181            },
3182            RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
3183                // Top-level filter without intersection: e.g. "~author(_)" is represented as
3184                // `AsFilter(NotIn(Filter(Author(_))))`.
3185                ResolvedExpression::FilterWithin {
3186                    candidates: self.resolve_all().into(),
3187                    predicate: self.resolve_predicate(expression),
3188                }
3189            }
3190            RevsetExpression::AtOperation { operation, .. } => match *operation {},
3191            RevsetExpression::WithinReference {
3192                candidates,
3193                commits,
3194            } => {
3195                let context = VisibilityResolutionContext {
3196                    referenced_commits: commits,
3197                    visible_heads: self.visible_heads,
3198                    root: self.root,
3199                };
3200                context.resolve(candidates)
3201            }
3202            RevsetExpression::WithinVisibility {
3203                candidates,
3204                visible_heads,
3205            } => {
3206                let context = VisibilityResolutionContext {
3207                    referenced_commits: self.referenced_commits,
3208                    visible_heads,
3209                    root: self.root,
3210                };
3211                context.resolve(candidates)
3212            }
3213            RevsetExpression::Coalesce(expression1, expression2) => ResolvedExpression::Coalesce(
3214                self.resolve(expression1).into(),
3215                self.resolve(expression2).into(),
3216            ),
3217            // present(x) is noop if x doesn't contain any commit refs.
3218            RevsetExpression::Present(candidates) => self.resolve(candidates),
3219            RevsetExpression::NotIn(complement) => ResolvedExpression::Difference(
3220                self.resolve_all().into(),
3221                self.resolve(complement).into(),
3222            ),
3223            RevsetExpression::Union(expression1, expression2) => ResolvedExpression::Union(
3224                self.resolve(expression1).into(),
3225                self.resolve(expression2).into(),
3226            ),
3227            RevsetExpression::Intersection(expression1, expression2) => {
3228                match expression2.as_ref() {
3229                    RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
3230                        ResolvedExpression::FilterWithin {
3231                            candidates: self.resolve(expression1).into(),
3232                            predicate: self.resolve_predicate(expression2),
3233                        }
3234                    }
3235                    _ => ResolvedExpression::Intersection(
3236                        self.resolve(expression1).into(),
3237                        self.resolve(expression2).into(),
3238                    ),
3239                }
3240            }
3241            RevsetExpression::Difference(expression1, expression2) => {
3242                ResolvedExpression::Difference(
3243                    self.resolve(expression1).into(),
3244                    self.resolve(expression2).into(),
3245                )
3246            }
3247        }
3248    }
3249
3250    fn resolve_all(&self) -> ResolvedExpression {
3251        ResolvedExpression::Ancestors {
3252            heads: self.resolve_visible_heads_or_referenced().into(),
3253            generation: GENERATION_RANGE_FULL,
3254            parents_range: PARENTS_RANGE_FULL,
3255        }
3256    }
3257
3258    fn resolve_visible_heads(&self) -> ResolvedExpression {
3259        ResolvedExpression::Commits(self.visible_heads.to_owned())
3260    }
3261
3262    fn resolve_visible_heads_or_referenced(&self) -> ResolvedExpression {
3263        // The referenced commits may be hidden. If they weren't included in
3264        // `all()`, some of the logical transformation rules might subtly change
3265        // the evaluated set. For example, `all() & x` wouldn't be `x` if `x`
3266        // were hidden and if not included in `all()`.
3267        let commits = itertools::chain(self.referenced_commits, self.visible_heads)
3268            .cloned()
3269            .collect();
3270        ResolvedExpression::Commits(commits)
3271    }
3272
3273    fn resolve_root(&self) -> ResolvedExpression {
3274        ResolvedExpression::Commits(vec![self.root.to_owned()])
3275    }
3276
3277    /// Resolves expression tree as filter predicate.
3278    ///
3279    /// For filter expression, this never inserts a hidden `all()` since a
3280    /// filter predicate doesn't need to produce revisions to walk.
3281    fn resolve_predicate(
3282        &self,
3283        expression: &ResolvedRevsetExpression,
3284    ) -> ResolvedPredicateExpression {
3285        match expression {
3286            RevsetExpression::None
3287            | RevsetExpression::All
3288            | RevsetExpression::VisibleHeads
3289            | RevsetExpression::VisibleHeadsOrReferenced
3290            | RevsetExpression::Root
3291            | RevsetExpression::Commits(_)
3292            | RevsetExpression::CommitRef(_)
3293            | RevsetExpression::Ancestors { .. }
3294            | RevsetExpression::Descendants { .. }
3295            | RevsetExpression::Range { .. }
3296            | RevsetExpression::DagRange { .. }
3297            | RevsetExpression::Reachable { .. }
3298            | RevsetExpression::Heads(_)
3299            | RevsetExpression::HeadsRange { .. }
3300            | RevsetExpression::Roots(_)
3301            | RevsetExpression::ForkPoint(_)
3302            | RevsetExpression::Bisect(_)
3303            | RevsetExpression::HasSize { .. }
3304            | RevsetExpression::Latest { .. } => {
3305                ResolvedPredicateExpression::Set(self.resolve(expression).into())
3306            }
3307            RevsetExpression::Filter(predicate) => {
3308                ResolvedPredicateExpression::Filter(predicate.clone())
3309            }
3310            RevsetExpression::AsFilter(candidates) => self.resolve_predicate(candidates),
3311            RevsetExpression::AtOperation { operation, .. } => match *operation {},
3312            // Filters should be intersected with all() within the at-op repo.
3313            RevsetExpression::WithinReference { .. }
3314            | RevsetExpression::WithinVisibility { .. } => {
3315                ResolvedPredicateExpression::Set(self.resolve(expression).into())
3316            }
3317            RevsetExpression::Coalesce(_, _) => {
3318                ResolvedPredicateExpression::Set(self.resolve(expression).into())
3319            }
3320            // present(x) is noop if x doesn't contain any commit refs.
3321            RevsetExpression::Present(candidates) => self.resolve_predicate(candidates),
3322            RevsetExpression::NotIn(complement) => {
3323                ResolvedPredicateExpression::NotIn(self.resolve_predicate(complement).into())
3324            }
3325            RevsetExpression::Union(expression1, expression2) => {
3326                let predicate1 = self.resolve_predicate(expression1);
3327                let predicate2 = self.resolve_predicate(expression2);
3328                ResolvedPredicateExpression::Union(predicate1.into(), predicate2.into())
3329            }
3330            RevsetExpression::Intersection(expression1, expression2) => {
3331                let predicate1 = self.resolve_predicate(expression1);
3332                let predicate2 = self.resolve_predicate(expression2);
3333                ResolvedPredicateExpression::Intersection(predicate1.into(), predicate2.into())
3334            }
3335            RevsetExpression::Difference(expression1, expression2) => {
3336                let predicate1 = self.resolve_predicate(expression1);
3337                let predicate2 = self.resolve_predicate(expression2);
3338                let predicate2 = ResolvedPredicateExpression::NotIn(predicate2.into());
3339                ResolvedPredicateExpression::Intersection(predicate1.into(), predicate2.into())
3340            }
3341        }
3342    }
3343}
3344
3345pub trait Revset: fmt::Debug {
3346    /// Iterate in topological order with children before parents.
3347    fn iter<'a>(&self) -> Box<dyn Iterator<Item = Result<CommitId, RevsetEvaluationError>> + 'a>
3348    where
3349        Self: 'a;
3350
3351    /// Iterates commit/change id pairs in topological order.
3352    fn commit_change_ids<'a>(
3353        &self,
3354    ) -> Box<dyn Iterator<Item = Result<(CommitId, ChangeId), RevsetEvaluationError>> + 'a>
3355    where
3356        Self: 'a;
3357
3358    fn iter_graph<'a>(
3359        &self,
3360    ) -> Box<dyn Iterator<Item = Result<GraphNode<CommitId>, RevsetEvaluationError>> + 'a>
3361    where
3362        Self: 'a;
3363
3364    /// Returns true if iterator will emit no commit nor error.
3365    fn is_empty(&self) -> bool;
3366
3367    /// Inclusive lower bound and, optionally, inclusive upper bound of how many
3368    /// commits are in the revset. The implementation can use its discretion as
3369    /// to how much effort should be put into the estimation, and how accurate
3370    /// the resulting estimate should be.
3371    fn count_estimate(&self) -> Result<(usize, Option<usize>), RevsetEvaluationError>;
3372
3373    /// Returns a closure that checks if a commit is contained within the
3374    /// revset.
3375    ///
3376    /// The implementation may construct and maintain any necessary internal
3377    /// context to optimize the performance of the check.
3378    fn containing_fn<'a>(&self) -> Box<RevsetContainingFn<'a>>
3379    where
3380        Self: 'a;
3381}
3382
3383/// Function that checks if a commit is contained within the revset.
3384pub type RevsetContainingFn<'a> = dyn Fn(&CommitId) -> Result<bool, RevsetEvaluationError> + 'a;
3385
3386pub trait RevsetIteratorExt<I> {
3387    fn commits(self, store: &Arc<Store>) -> RevsetCommitIterator<I>;
3388}
3389
3390impl<I: Iterator<Item = Result<CommitId, RevsetEvaluationError>>> RevsetIteratorExt<I> for I {
3391    fn commits(self, store: &Arc<Store>) -> RevsetCommitIterator<I> {
3392        RevsetCommitIterator {
3393            iter: self,
3394            store: store.clone(),
3395        }
3396    }
3397}
3398
3399pub struct RevsetCommitIterator<I> {
3400    store: Arc<Store>,
3401    iter: I,
3402}
3403
3404impl<I: Iterator<Item = Result<CommitId, RevsetEvaluationError>>> Iterator
3405    for RevsetCommitIterator<I>
3406{
3407    type Item = Result<Commit, RevsetEvaluationError>;
3408
3409    fn next(&mut self) -> Option<Self::Item> {
3410        self.iter.next().map(|commit_id| {
3411            let commit_id = commit_id?;
3412            self.store
3413                .get_commit(&commit_id)
3414                .map_err(RevsetEvaluationError::Backend)
3415        })
3416    }
3417}
3418
3419/// A set of extensions for revset evaluation.
3420pub struct RevsetExtensions {
3421    symbol_resolvers: Vec<Box<dyn SymbolResolverExtension>>,
3422    function_map: HashMap<&'static str, RevsetFunction>,
3423}
3424
3425impl Default for RevsetExtensions {
3426    fn default() -> Self {
3427        Self::new()
3428    }
3429}
3430
3431impl RevsetExtensions {
3432    pub fn new() -> Self {
3433        Self {
3434            symbol_resolvers: vec![],
3435            function_map: BUILTIN_FUNCTION_MAP.clone(),
3436        }
3437    }
3438
3439    pub fn symbol_resolvers(&self) -> &[Box<dyn SymbolResolverExtension>] {
3440        &self.symbol_resolvers
3441    }
3442
3443    pub fn add_symbol_resolver(&mut self, symbol_resolver: Box<dyn SymbolResolverExtension>) {
3444        self.symbol_resolvers.push(symbol_resolver);
3445    }
3446
3447    pub fn add_custom_function(&mut self, name: &'static str, func: RevsetFunction) {
3448        match self.function_map.entry(name) {
3449            hash_map::Entry::Occupied(_) => {
3450                panic!("Conflict registering revset function '{name}'")
3451            }
3452            hash_map::Entry::Vacant(v) => v.insert(func),
3453        };
3454    }
3455}
3456
3457/// Information needed to parse revset expression.
3458#[derive(Clone)]
3459pub struct RevsetParseContext<'a> {
3460    pub aliases_map: &'a RevsetAliasesMap,
3461    pub local_variables: HashMap<&'a str, ExpressionNode<'a>>,
3462    pub user_email: &'a str,
3463    pub date_pattern_context: DatePatternContext,
3464    /// Special remote that should be ignored by default. (e.g. "git")
3465    pub default_ignored_remote: Option<&'a RemoteName>,
3466    pub use_glob_by_default: bool,
3467    pub extensions: &'a RevsetExtensions,
3468    pub workspace: Option<RevsetWorkspaceContext<'a>>,
3469}
3470
3471impl<'a> RevsetParseContext<'a> {
3472    fn to_lowering_context(&self) -> LoweringContext<'a> {
3473        let RevsetParseContext {
3474            aliases_map: _,
3475            local_variables: _,
3476            user_email,
3477            date_pattern_context,
3478            default_ignored_remote,
3479            use_glob_by_default,
3480            extensions,
3481            workspace,
3482        } = *self;
3483        LoweringContext {
3484            user_email,
3485            date_pattern_context,
3486            default_ignored_remote,
3487            use_glob_by_default,
3488            extensions,
3489            workspace,
3490        }
3491    }
3492}
3493
3494/// Information needed to transform revset AST into `UserRevsetExpression`.
3495#[derive(Clone)]
3496pub struct LoweringContext<'a> {
3497    user_email: &'a str,
3498    date_pattern_context: DatePatternContext,
3499    default_ignored_remote: Option<&'a RemoteName>,
3500    use_glob_by_default: bool,
3501    extensions: &'a RevsetExtensions,
3502    workspace: Option<RevsetWorkspaceContext<'a>>,
3503}
3504
3505impl<'a> LoweringContext<'a> {
3506    pub fn user_email(&self) -> &'a str {
3507        self.user_email
3508    }
3509
3510    pub fn date_pattern_context(&self) -> &DatePatternContext {
3511        &self.date_pattern_context
3512    }
3513
3514    pub fn symbol_resolvers(&self) -> &'a [impl AsRef<dyn SymbolResolverExtension> + use<>] {
3515        self.extensions.symbol_resolvers()
3516    }
3517}
3518
3519/// Workspace information needed to parse revset expression.
3520#[derive(Clone, Copy, Debug)]
3521pub struct RevsetWorkspaceContext<'a> {
3522    pub path_converter: &'a RepoPathUiConverter,
3523    pub workspace_name: &'a WorkspaceName,
3524}
3525
3526/// Formats a string as symbol by quoting and escaping it if necessary.
3527///
3528/// Note that symbols may be substituted to user aliases. Use
3529/// [`format_string()`] to ensure that the provided string is resolved as a
3530/// tag/bookmark name, commit/change ID prefix, etc.
3531pub fn format_symbol(literal: &str) -> String {
3532    if revset_parser::is_identifier(literal) {
3533        literal.to_string()
3534    } else {
3535        format_string(literal)
3536    }
3537}
3538
3539/// Formats a string by quoting and escaping it.
3540pub fn format_string(literal: &str) -> String {
3541    format!(r#""{}""#, dsl_util::escape_string(literal))
3542}
3543
3544/// Formats a `name@remote` symbol, applies quoting and escaping if necessary.
3545pub fn format_remote_symbol(name: &str, remote: &str) -> String {
3546    let name = format_symbol(name);
3547    let remote = format_symbol(remote);
3548    format!("{name}@{remote}")
3549}
3550
3551#[cfg(test)]
3552#[rustversion::attr(
3553    since(1.89),
3554    expect(clippy::cloned_ref_to_slice_refs, reason = "makes tests more readable")
3555)]
3556mod tests {
3557    use std::path::PathBuf;
3558
3559    use assert_matches::assert_matches;
3560
3561    use super::*;
3562
3563    fn parse(revset_str: &str) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3564        parse_with_aliases(revset_str, [] as [(&str, &str); 0])
3565    }
3566
3567    fn parse_with_workspace(
3568        revset_str: &str,
3569        workspace_name: &WorkspaceName,
3570    ) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3571        parse_with_aliases_and_workspace(revset_str, [] as [(&str, &str); 0], workspace_name)
3572    }
3573
3574    fn parse_with_aliases(
3575        revset_str: &str,
3576        aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
3577    ) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3578        let mut aliases_map = RevsetAliasesMap::new();
3579        for (decl, defn) in aliases {
3580            aliases_map.insert(decl, defn).unwrap();
3581        }
3582        let context = RevsetParseContext {
3583            aliases_map: &aliases_map,
3584            local_variables: HashMap::new(),
3585            user_email: "test.user@example.com",
3586            date_pattern_context: chrono::Utc::now().fixed_offset().into(),
3587            default_ignored_remote: Some("ignored".as_ref()),
3588            use_glob_by_default: true,
3589            extensions: &RevsetExtensions::default(),
3590            workspace: None,
3591        };
3592        super::parse(&mut RevsetDiagnostics::new(), revset_str, &context)
3593    }
3594
3595    fn parse_with_aliases_and_workspace(
3596        revset_str: &str,
3597        aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
3598        workspace_name: &WorkspaceName,
3599    ) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3600        // Set up pseudo context to resolve `workspace_name@` and `file(path)`
3601        let path_converter = RepoPathUiConverter::Fs {
3602            cwd: PathBuf::from("/"),
3603            base: PathBuf::from("/"),
3604        };
3605        let workspace_ctx = RevsetWorkspaceContext {
3606            path_converter: &path_converter,
3607            workspace_name,
3608        };
3609        let mut aliases_map = RevsetAliasesMap::new();
3610        for (decl, defn) in aliases {
3611            aliases_map.insert(decl, defn).unwrap();
3612        }
3613        let context = RevsetParseContext {
3614            aliases_map: &aliases_map,
3615            local_variables: HashMap::new(),
3616            user_email: "test.user@example.com",
3617            date_pattern_context: chrono::Utc::now().fixed_offset().into(),
3618            default_ignored_remote: Some("ignored".as_ref()),
3619            use_glob_by_default: true,
3620            extensions: &RevsetExtensions::default(),
3621            workspace: Some(workspace_ctx),
3622        };
3623        super::parse(&mut RevsetDiagnostics::new(), revset_str, &context)
3624    }
3625
3626    fn parse_with_modifier(
3627        revset_str: &str,
3628    ) -> Result<(Arc<UserRevsetExpression>, Option<RevsetModifier>), RevsetParseError> {
3629        parse_with_aliases_and_modifier(revset_str, [] as [(&str, &str); 0])
3630    }
3631
3632    fn parse_with_aliases_and_modifier(
3633        revset_str: &str,
3634        aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
3635    ) -> Result<(Arc<UserRevsetExpression>, Option<RevsetModifier>), RevsetParseError> {
3636        let mut aliases_map = RevsetAliasesMap::new();
3637        for (decl, defn) in aliases {
3638            aliases_map.insert(decl, defn).unwrap();
3639        }
3640        let context = RevsetParseContext {
3641            aliases_map: &aliases_map,
3642            local_variables: HashMap::new(),
3643            user_email: "test.user@example.com",
3644            date_pattern_context: chrono::Utc::now().fixed_offset().into(),
3645            default_ignored_remote: Some("ignored".as_ref()),
3646            use_glob_by_default: true,
3647            extensions: &RevsetExtensions::default(),
3648            workspace: None,
3649        };
3650        super::parse_with_modifier(&mut RevsetDiagnostics::new(), revset_str, &context)
3651    }
3652
3653    fn insta_settings() -> insta::Settings {
3654        let mut settings = insta::Settings::clone_current();
3655        // Collapse short "Thing(_,)" repeatedly to save vertical space and make
3656        // the output more readable.
3657        for _ in 0..4 {
3658            settings.add_filter(
3659                r"(?x)
3660                \b([A-Z]\w*)\(\n
3661                    \s*(.{1,60}),\n
3662                \s*\)",
3663                "$1($2)",
3664            );
3665        }
3666        settings
3667    }
3668
3669    #[test]
3670    #[expect(clippy::redundant_clone)] // allow symbol.clone()
3671    fn test_revset_expression_building() {
3672        let settings = insta_settings();
3673        let _guard = settings.bind_to_scope();
3674        let current_wc = UserRevsetExpression::working_copy(WorkspaceName::DEFAULT.to_owned());
3675        let foo_symbol = UserRevsetExpression::symbol("foo".to_string());
3676        let bar_symbol = UserRevsetExpression::symbol("bar".to_string());
3677        let baz_symbol = UserRevsetExpression::symbol("baz".to_string());
3678
3679        insta::assert_debug_snapshot!(
3680            current_wc,
3681            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3682        insta::assert_debug_snapshot!(
3683            current_wc.heads(),
3684            @r#"Heads(CommitRef(WorkingCopy(WorkspaceNameBuf("default"))))"#);
3685        insta::assert_debug_snapshot!(
3686            current_wc.roots(),
3687            @r#"Roots(CommitRef(WorkingCopy(WorkspaceNameBuf("default"))))"#);
3688        insta::assert_debug_snapshot!(
3689            current_wc.parents(), @r#"
3690        Ancestors {
3691            heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3692            generation: 1..2,
3693            parents_range: 0..4294967295,
3694        }
3695        "#);
3696        insta::assert_debug_snapshot!(
3697            current_wc.ancestors(), @r#"
3698        Ancestors {
3699            heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3700            generation: 0..18446744073709551615,
3701            parents_range: 0..4294967295,
3702        }
3703        "#);
3704        insta::assert_debug_snapshot!(
3705            foo_symbol.children(), @r#"
3706        Descendants {
3707            roots: CommitRef(Symbol("foo")),
3708            generation: 1..2,
3709        }
3710        "#);
3711        insta::assert_debug_snapshot!(
3712            foo_symbol.descendants(), @r#"
3713        Descendants {
3714            roots: CommitRef(Symbol("foo")),
3715            generation: 0..18446744073709551615,
3716        }
3717        "#);
3718        insta::assert_debug_snapshot!(
3719            foo_symbol.dag_range_to(&current_wc), @r#"
3720        DagRange {
3721            roots: CommitRef(Symbol("foo")),
3722            heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3723        }
3724        "#);
3725        insta::assert_debug_snapshot!(
3726            foo_symbol.connected(), @r#"
3727        DagRange {
3728            roots: CommitRef(Symbol("foo")),
3729            heads: CommitRef(Symbol("foo")),
3730        }
3731        "#);
3732        insta::assert_debug_snapshot!(
3733            foo_symbol.range(&current_wc), @r#"
3734        Range {
3735            roots: CommitRef(Symbol("foo")),
3736            heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3737            generation: 0..18446744073709551615,
3738            parents_range: 0..4294967295,
3739        }
3740        "#);
3741        insta::assert_debug_snapshot!(
3742            foo_symbol.negated(),
3743            @r#"NotIn(CommitRef(Symbol("foo")))"#);
3744        insta::assert_debug_snapshot!(
3745            foo_symbol.union(&current_wc), @r#"
3746        Union(
3747            CommitRef(Symbol("foo")),
3748            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3749        )
3750        "#);
3751        insta::assert_debug_snapshot!(
3752            UserRevsetExpression::union_all(&[]),
3753            @"None");
3754        insta::assert_debug_snapshot!(
3755            RevsetExpression::union_all(&[current_wc.clone()]),
3756            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3757        insta::assert_debug_snapshot!(
3758            RevsetExpression::union_all(&[current_wc.clone(), foo_symbol.clone()]),
3759            @r#"
3760        Union(
3761            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3762            CommitRef(Symbol("foo")),
3763        )
3764        "#);
3765        insta::assert_debug_snapshot!(
3766            RevsetExpression::union_all(&[
3767                current_wc.clone(),
3768                foo_symbol.clone(),
3769                bar_symbol.clone(),
3770            ]),
3771            @r#"
3772        Union(
3773            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3774            Union(
3775                CommitRef(Symbol("foo")),
3776                CommitRef(Symbol("bar")),
3777            ),
3778        )
3779        "#);
3780        insta::assert_debug_snapshot!(
3781            RevsetExpression::union_all(&[
3782                current_wc.clone(),
3783                foo_symbol.clone(),
3784                bar_symbol.clone(),
3785                baz_symbol.clone(),
3786            ]),
3787            @r#"
3788        Union(
3789            Union(
3790                CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3791                CommitRef(Symbol("foo")),
3792            ),
3793            Union(
3794                CommitRef(Symbol("bar")),
3795                CommitRef(Symbol("baz")),
3796            ),
3797        )
3798        "#);
3799        insta::assert_debug_snapshot!(
3800            foo_symbol.intersection(&current_wc), @r#"
3801        Intersection(
3802            CommitRef(Symbol("foo")),
3803            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3804        )
3805        "#);
3806        insta::assert_debug_snapshot!(
3807            foo_symbol.minus(&current_wc), @r#"
3808        Difference(
3809            CommitRef(Symbol("foo")),
3810            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3811        )
3812        "#);
3813        insta::assert_debug_snapshot!(
3814            UserRevsetExpression::coalesce(&[]),
3815            @"None");
3816        insta::assert_debug_snapshot!(
3817            RevsetExpression::coalesce(&[current_wc.clone()]),
3818            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3819        insta::assert_debug_snapshot!(
3820            RevsetExpression::coalesce(&[current_wc.clone(), foo_symbol.clone()]),
3821            @r#"
3822        Coalesce(
3823            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3824            CommitRef(Symbol("foo")),
3825        )
3826        "#);
3827        insta::assert_debug_snapshot!(
3828            RevsetExpression::coalesce(&[
3829                current_wc.clone(),
3830                foo_symbol.clone(),
3831                bar_symbol.clone(),
3832            ]),
3833            @r#"
3834        Coalesce(
3835            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3836            Coalesce(
3837                CommitRef(Symbol("foo")),
3838                CommitRef(Symbol("bar")),
3839            ),
3840        )
3841        "#);
3842    }
3843
3844    #[test]
3845    fn test_parse_revset() {
3846        let settings = insta_settings();
3847        let _guard = settings.bind_to_scope();
3848        let main_workspace_name = WorkspaceNameBuf::from("main");
3849        let other_workspace_name = WorkspaceNameBuf::from("other");
3850
3851        // Parse "@" (the current working copy)
3852        insta::assert_debug_snapshot!(
3853            parse("@").unwrap_err().kind(),
3854            @"WorkingCopyWithoutWorkspace");
3855        insta::assert_debug_snapshot!(
3856            parse("main@").unwrap(),
3857            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3858        insta::assert_debug_snapshot!(
3859            parse_with_workspace("@", &main_workspace_name).unwrap(),
3860            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3861        insta::assert_debug_snapshot!(
3862            parse_with_workspace("main@", &other_workspace_name).unwrap(),
3863            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3864        // "@" in function argument must be quoted
3865        insta::assert_debug_snapshot!(
3866            parse("author_name(foo@)").unwrap_err().kind(),
3867            @r#"Expression("Invalid string expression")"#);
3868        insta::assert_debug_snapshot!(
3869            parse(r#"author_name("foo@")"#).unwrap(),
3870            @r#"Filter(AuthorName(Pattern(Exact("foo@"))))"#);
3871        // Parse a single symbol
3872        insta::assert_debug_snapshot!(
3873            parse("foo").unwrap(),
3874            @r#"CommitRef(Symbol("foo"))"#);
3875        // Default arguments for *bookmarks() are all ""
3876        insta::assert_debug_snapshot!(
3877            parse("bookmarks()").unwrap(),
3878            @r#"CommitRef(Bookmarks(Pattern(Substring(""))))"#);
3879        // Default argument for tags() is ""
3880        insta::assert_debug_snapshot!(
3881            parse("tags()").unwrap(),
3882            @r#"CommitRef(Tags(Pattern(Substring(""))))"#);
3883        insta::assert_debug_snapshot!(parse("remote_bookmarks()").unwrap(), @r#"
3884        CommitRef(
3885            RemoteBookmarks {
3886                bookmark: Pattern(Substring("")),
3887                remote: NotIn(Pattern(Exact("ignored"))),
3888                remote_ref_state: None,
3889            },
3890        )
3891        "#);
3892        insta::assert_debug_snapshot!(parse("tracked_remote_bookmarks()").unwrap(), @r#"
3893        CommitRef(
3894            RemoteBookmarks {
3895                bookmark: Pattern(Substring("")),
3896                remote: NotIn(Pattern(Exact("ignored"))),
3897                remote_ref_state: Some(Tracked),
3898            },
3899        )
3900        "#);
3901        insta::assert_debug_snapshot!(parse("untracked_remote_bookmarks()").unwrap(), @r#"
3902        CommitRef(
3903            RemoteBookmarks {
3904                bookmark: Pattern(Substring("")),
3905                remote: NotIn(Pattern(Exact("ignored"))),
3906                remote_ref_state: Some(New),
3907            },
3908        )
3909        "#);
3910        // Parse a quoted symbol
3911        insta::assert_debug_snapshot!(
3912            parse("'foo'").unwrap(),
3913            @r#"CommitRef(Symbol("foo"))"#);
3914        // Parse the "parents" operator
3915        insta::assert_debug_snapshot!(parse("foo-").unwrap(), @r#"
3916        Ancestors {
3917            heads: CommitRef(Symbol("foo")),
3918            generation: 1..2,
3919            parents_range: 0..4294967295,
3920        }
3921        "#);
3922        // Parse the "children" operator
3923        insta::assert_debug_snapshot!(parse("foo+").unwrap(), @r#"
3924        Descendants {
3925            roots: CommitRef(Symbol("foo")),
3926            generation: 1..2,
3927        }
3928        "#);
3929        // Parse the "ancestors" operator
3930        insta::assert_debug_snapshot!(parse("::foo").unwrap(), @r#"
3931        Ancestors {
3932            heads: CommitRef(Symbol("foo")),
3933            generation: 0..18446744073709551615,
3934            parents_range: 0..4294967295,
3935        }
3936        "#);
3937        // Parse the "descendants" operator
3938        insta::assert_debug_snapshot!(parse("foo::").unwrap(), @r#"
3939        Descendants {
3940            roots: CommitRef(Symbol("foo")),
3941            generation: 0..18446744073709551615,
3942        }
3943        "#);
3944        // Parse the "dag range" operator
3945        insta::assert_debug_snapshot!(parse("foo::bar").unwrap(), @r#"
3946        DagRange {
3947            roots: CommitRef(Symbol("foo")),
3948            heads: CommitRef(Symbol("bar")),
3949        }
3950        "#);
3951        // Parse the nullary "dag range" operator
3952        insta::assert_debug_snapshot!(parse("::").unwrap(), @"All");
3953        // Parse the "range" prefix operator
3954        insta::assert_debug_snapshot!(parse("..foo").unwrap(), @r#"
3955        Range {
3956            roots: Root,
3957            heads: CommitRef(Symbol("foo")),
3958            generation: 0..18446744073709551615,
3959            parents_range: 0..4294967295,
3960        }
3961        "#);
3962        insta::assert_debug_snapshot!(parse("foo..").unwrap(), @r#"
3963        NotIn(
3964            Ancestors {
3965                heads: CommitRef(Symbol("foo")),
3966                generation: 0..18446744073709551615,
3967                parents_range: 0..4294967295,
3968            },
3969        )
3970        "#);
3971        insta::assert_debug_snapshot!(parse("foo..bar").unwrap(), @r#"
3972        Range {
3973            roots: CommitRef(Symbol("foo")),
3974            heads: CommitRef(Symbol("bar")),
3975            generation: 0..18446744073709551615,
3976            parents_range: 0..4294967295,
3977        }
3978        "#);
3979        // Parse the nullary "range" operator
3980        insta::assert_debug_snapshot!(parse("..").unwrap(), @"NotIn(Root)");
3981        // Parse the "negate" operator
3982        insta::assert_debug_snapshot!(
3983            parse("~ foo").unwrap(),
3984            @r#"NotIn(CommitRef(Symbol("foo")))"#);
3985        // Parse the "intersection" operator
3986        insta::assert_debug_snapshot!(parse("foo & bar").unwrap(), @r#"
3987        Intersection(
3988            CommitRef(Symbol("foo")),
3989            CommitRef(Symbol("bar")),
3990        )
3991        "#);
3992        // Parse the "union" operator
3993        insta::assert_debug_snapshot!(parse("foo | bar").unwrap(), @r#"
3994        Union(
3995            CommitRef(Symbol("foo")),
3996            CommitRef(Symbol("bar")),
3997        )
3998        "#);
3999        // Parse the "difference" operator
4000        insta::assert_debug_snapshot!(parse("foo ~ bar").unwrap(), @r#"
4001        Difference(
4002            CommitRef(Symbol("foo")),
4003            CommitRef(Symbol("bar")),
4004        )
4005        "#);
4006    }
4007
4008    #[test]
4009    fn test_parse_revset_with_modifier() {
4010        let settings = insta_settings();
4011        let _guard = settings.bind_to_scope();
4012
4013        insta::assert_debug_snapshot!(
4014            parse_with_modifier("all:foo").unwrap(), @r#"
4015        (
4016            CommitRef(Symbol("foo")),
4017            Some(All),
4018        )
4019        "#);
4020
4021        // Top-level string pattern can't be parsed, which is an error anyway
4022        insta::assert_debug_snapshot!(
4023            parse_with_modifier(r#"exact:"foo""#).unwrap_err().kind(),
4024            @r#"NoSuchModifier("exact")"#);
4025    }
4026
4027    #[test]
4028    fn test_parse_string_pattern() {
4029        let settings = insta_settings();
4030        let _guard = settings.bind_to_scope();
4031
4032        insta::assert_debug_snapshot!(
4033            parse(r#"bookmarks("foo")"#).unwrap(),
4034            @r#"CommitRef(Bookmarks(Pattern(Exact("foo"))))"#);
4035        insta::assert_debug_snapshot!(
4036            parse(r#"bookmarks(exact:"foo")"#).unwrap(),
4037            @r#"CommitRef(Bookmarks(Pattern(Exact("foo"))))"#);
4038        insta::assert_debug_snapshot!(
4039            parse(r#"bookmarks(substring:"foo")"#).unwrap(),
4040            @r#"CommitRef(Bookmarks(Pattern(Substring("foo"))))"#);
4041        insta::assert_debug_snapshot!(
4042            parse(r#"bookmarks(bad:"foo")"#).unwrap_err().kind(),
4043            @r#"Expression("Invalid string pattern")"#);
4044        insta::assert_debug_snapshot!(
4045            parse(r#"bookmarks(exact::"foo")"#).unwrap_err().kind(),
4046            @r#"Expression("Invalid string expression")"#);
4047        insta::assert_debug_snapshot!(
4048            parse(r#"bookmarks(exact:"foo"+)"#).unwrap_err().kind(),
4049            @r#"Expression("Invalid string expression")"#);
4050
4051        insta::assert_debug_snapshot!(
4052            parse(r#"tags("foo")"#).unwrap(),
4053            @r#"CommitRef(Tags(Pattern(Exact("foo"))))"#);
4054        insta::assert_debug_snapshot!(
4055            parse(r#"tags(exact:"foo")"#).unwrap(),
4056            @r#"CommitRef(Tags(Pattern(Exact("foo"))))"#);
4057        insta::assert_debug_snapshot!(
4058            parse(r#"tags(substring:"foo")"#).unwrap(),
4059            @r#"CommitRef(Tags(Pattern(Substring("foo"))))"#);
4060        insta::assert_debug_snapshot!(
4061            parse(r#"tags(bad:"foo")"#).unwrap_err().kind(),
4062            @r#"Expression("Invalid string pattern")"#);
4063        insta::assert_debug_snapshot!(
4064            parse(r#"tags(exact::"foo")"#).unwrap_err().kind(),
4065            @r#"Expression("Invalid string expression")"#);
4066        insta::assert_debug_snapshot!(
4067            parse(r#"tags(exact:"foo"+)"#).unwrap_err().kind(),
4068            @r#"Expression("Invalid string expression")"#);
4069
4070        // String pattern isn't allowed at top level.
4071        assert_matches!(
4072            parse(r#"(exact:"foo")"#).unwrap_err().kind(),
4073            RevsetParseErrorKind::NotInfixOperator { .. }
4074        );
4075    }
4076
4077    #[test]
4078    fn test_parse_compound_string_expression() {
4079        let settings = insta_settings();
4080        let _guard = settings.bind_to_scope();
4081
4082        insta::assert_debug_snapshot!(
4083            parse(r#"tags(~a)"#).unwrap(),
4084            @r#"
4085        CommitRef(
4086            Tags(NotIn(Pattern(Exact("a")))),
4087        )
4088        "#);
4089        insta::assert_debug_snapshot!(
4090            parse(r#"tags(a|b&c)"#).unwrap(),
4091            @r#"
4092        CommitRef(
4093            Tags(
4094                Union(
4095                    Pattern(Exact("a")),
4096                    Intersection(
4097                        Pattern(Exact("b")),
4098                        Pattern(Exact("c")),
4099                    ),
4100                ),
4101            ),
4102        )
4103        "#);
4104        insta::assert_debug_snapshot!(
4105            parse(r#"tags(a|b|c)"#).unwrap(),
4106            @r#"
4107        CommitRef(
4108            Tags(
4109                Union(
4110                    Pattern(Exact("a")),
4111                    Union(
4112                        Pattern(Exact("b")),
4113                        Pattern(Exact("c")),
4114                    ),
4115                ),
4116            ),
4117        )
4118        "#);
4119        insta::assert_debug_snapshot!(
4120            parse(r#"tags(a~(b|c))"#).unwrap(),
4121            @r#"
4122        CommitRef(
4123            Tags(
4124                Intersection(
4125                    Pattern(Exact("a")),
4126                    NotIn(
4127                        Union(
4128                            Pattern(Exact("b")),
4129                            Pattern(Exact("c")),
4130                        ),
4131                    ),
4132                ),
4133            ),
4134        )
4135        "#);
4136    }
4137
4138    #[test]
4139    fn test_parse_revset_function() {
4140        let settings = insta_settings();
4141        let _guard = settings.bind_to_scope();
4142
4143        insta::assert_debug_snapshot!(
4144            parse("parents(foo)").unwrap(), @r#"
4145        Ancestors {
4146            heads: CommitRef(Symbol("foo")),
4147            generation: 1..2,
4148            parents_range: 0..4294967295,
4149        }
4150        "#);
4151        insta::assert_debug_snapshot!(
4152            parse("parents(\"foo\")").unwrap(), @r#"
4153        Ancestors {
4154            heads: CommitRef(Symbol("foo")),
4155            generation: 1..2,
4156            parents_range: 0..4294967295,
4157        }
4158        "#);
4159        insta::assert_debug_snapshot!(
4160            parse("ancestors(parents(foo))").unwrap(), @r#"
4161        Ancestors {
4162            heads: Ancestors {
4163                heads: CommitRef(Symbol("foo")),
4164                generation: 1..2,
4165                parents_range: 0..4294967295,
4166            },
4167            generation: 0..18446744073709551615,
4168            parents_range: 0..4294967295,
4169        }
4170        "#);
4171        insta::assert_debug_snapshot!(
4172            parse("parents(foo, bar, baz)").unwrap_err().kind(), @r#"
4173        InvalidFunctionArguments {
4174            name: "parents",
4175            message: "Expected 1 to 2 arguments",
4176        }
4177        "#);
4178        insta::assert_debug_snapshot!(
4179            parse("parents(foo, 2)").unwrap(), @r#"
4180        Ancestors {
4181            heads: CommitRef(Symbol("foo")),
4182            generation: 2..3,
4183            parents_range: 0..4294967295,
4184        }
4185        "#);
4186        insta::assert_debug_snapshot!(
4187            parse("root()").unwrap(),
4188            @"Root");
4189        assert!(parse("root(a)").is_err());
4190        insta::assert_debug_snapshot!(
4191            parse(r#"description("")"#).unwrap(),
4192            @r#"Filter(Description(Pattern(Exact(""))))"#);
4193        insta::assert_debug_snapshot!(
4194            parse("description(foo)").unwrap(),
4195            @r#"Filter(Description(Pattern(Exact("foo"))))"#);
4196        insta::assert_debug_snapshot!(
4197            parse("description(visible_heads())").unwrap_err().kind(),
4198            @r#"Expression("Invalid string expression")"#);
4199        insta::assert_debug_snapshot!(
4200            parse("description(\"(foo)\")").unwrap(),
4201            @r#"Filter(Description(Pattern(Exact("(foo)"))))"#);
4202        assert!(parse("mine(foo)").is_err());
4203        insta::assert_debug_snapshot!(
4204            parse_with_workspace("empty()", WorkspaceName::DEFAULT).unwrap(),
4205            @"NotIn(Filter(File(All)))");
4206        assert!(parse_with_workspace("empty(foo)", WorkspaceName::DEFAULT).is_err());
4207        assert!(parse_with_workspace("file()", WorkspaceName::DEFAULT).is_err());
4208        insta::assert_debug_snapshot!(
4209            parse_with_workspace("files(foo)", WorkspaceName::DEFAULT).unwrap(),
4210            @r#"Filter(File(Pattern(PrefixPath("foo"))))"#);
4211        insta::assert_debug_snapshot!(
4212            parse_with_workspace("files(all())", WorkspaceName::DEFAULT).unwrap(),
4213            @"Filter(File(All))");
4214        insta::assert_debug_snapshot!(
4215            parse_with_workspace(r#"files(file:"foo")"#, WorkspaceName::DEFAULT).unwrap(),
4216            @r#"Filter(File(Pattern(FilePath("foo"))))"#);
4217        insta::assert_debug_snapshot!(
4218            parse_with_workspace("files(foo|bar&baz)", WorkspaceName::DEFAULT).unwrap(), @r#"
4219        Filter(
4220            File(
4221                UnionAll(
4222                    [
4223                        Pattern(PrefixPath("foo")),
4224                        Intersection(
4225                            Pattern(PrefixPath("bar")),
4226                            Pattern(PrefixPath("baz")),
4227                        ),
4228                    ],
4229                ),
4230            ),
4231        )
4232        "#);
4233        insta::assert_debug_snapshot!(
4234            parse_with_workspace(r#"files(~(foo))"#, WorkspaceName::DEFAULT).unwrap(),
4235            @r#"
4236        Filter(
4237            File(
4238                Difference(
4239                    All,
4240                    Pattern(PrefixPath("foo")),
4241                ),
4242            ),
4243        )
4244        "#);
4245        insta::assert_debug_snapshot!(parse("signed()").unwrap(), @"Filter(Signed)");
4246    }
4247
4248    #[test]
4249    fn test_parse_revset_change_commit_id_functions() {
4250        let settings = insta_settings();
4251        let _guard = settings.bind_to_scope();
4252
4253        insta::assert_debug_snapshot!(
4254            parse("change_id(z)").unwrap(),
4255            @r#"CommitRef(ChangeId(HexPrefix("0")))"#);
4256        insta::assert_debug_snapshot!(
4257            parse("change_id('zk')").unwrap(),
4258            @r#"CommitRef(ChangeId(HexPrefix("0f")))"#);
4259        insta::assert_debug_snapshot!(
4260            parse("change_id(01234)").unwrap_err().kind(),
4261            @r#"Expression("Invalid change ID prefix")"#);
4262
4263        insta::assert_debug_snapshot!(
4264            parse("commit_id(0)").unwrap(),
4265            @r#"CommitRef(CommitId(HexPrefix("0")))"#);
4266        insta::assert_debug_snapshot!(
4267            parse("commit_id('0f')").unwrap(),
4268            @r#"CommitRef(CommitId(HexPrefix("0f")))"#);
4269        insta::assert_debug_snapshot!(
4270            parse("commit_id(xyzzy)").unwrap_err().kind(),
4271            @r#"Expression("Invalid commit ID prefix")"#);
4272    }
4273
4274    #[test]
4275    fn test_parse_revset_author_committer_functions() {
4276        let settings = insta_settings();
4277        let _guard = settings.bind_to_scope();
4278
4279        insta::assert_debug_snapshot!(
4280            parse("author(foo)").unwrap(), @r#"
4281        Union(
4282            Filter(AuthorName(Pattern(Exact("foo")))),
4283            Filter(AuthorEmail(Pattern(Exact("foo")))),
4284        )
4285        "#);
4286        insta::assert_debug_snapshot!(
4287            parse("author_name(foo)").unwrap(),
4288            @r#"Filter(AuthorName(Pattern(Exact("foo"))))"#);
4289        insta::assert_debug_snapshot!(
4290            parse("author_email(foo)").unwrap(),
4291            @r#"Filter(AuthorEmail(Pattern(Exact("foo"))))"#);
4292
4293        insta::assert_debug_snapshot!(
4294            parse("committer(foo)").unwrap(), @r#"
4295        Union(
4296            Filter(CommitterName(Pattern(Exact("foo")))),
4297            Filter(CommitterEmail(Pattern(Exact("foo")))),
4298        )
4299        "#);
4300        insta::assert_debug_snapshot!(
4301            parse("committer_name(foo)").unwrap(),
4302            @r#"Filter(CommitterName(Pattern(Exact("foo"))))"#);
4303        insta::assert_debug_snapshot!(
4304            parse("committer_email(foo)").unwrap(),
4305            @r#"Filter(CommitterEmail(Pattern(Exact("foo"))))"#);
4306
4307        insta::assert_debug_snapshot!(
4308            parse("mine()").unwrap(),
4309            @r#"Filter(AuthorEmail(Pattern(ExactI("test.user@example.com"))))"#);
4310    }
4311
4312    #[test]
4313    fn test_parse_revset_keyword_arguments() {
4314        let settings = insta_settings();
4315        let _guard = settings.bind_to_scope();
4316
4317        insta::assert_debug_snapshot!(
4318            parse("remote_bookmarks(remote=foo)").unwrap(), @r#"
4319        CommitRef(
4320            RemoteBookmarks {
4321                bookmark: Pattern(Substring("")),
4322                remote: Pattern(Exact("foo")),
4323                remote_ref_state: None,
4324            },
4325        )
4326        "#);
4327        insta::assert_debug_snapshot!(
4328            parse("remote_bookmarks(foo, remote=bar)").unwrap(), @r#"
4329        CommitRef(
4330            RemoteBookmarks {
4331                bookmark: Pattern(Exact("foo")),
4332                remote: Pattern(Exact("bar")),
4333                remote_ref_state: None,
4334            },
4335        )
4336        "#);
4337        insta::assert_debug_snapshot!(
4338            parse("tracked_remote_bookmarks(foo, remote=bar)").unwrap(), @r#"
4339        CommitRef(
4340            RemoteBookmarks {
4341                bookmark: Pattern(Exact("foo")),
4342                remote: Pattern(Exact("bar")),
4343                remote_ref_state: Some(Tracked),
4344            },
4345        )
4346        "#);
4347        insta::assert_debug_snapshot!(
4348            parse("untracked_remote_bookmarks(foo, remote=bar)").unwrap(), @r#"
4349        CommitRef(
4350            RemoteBookmarks {
4351                bookmark: Pattern(Exact("foo")),
4352                remote: Pattern(Exact("bar")),
4353                remote_ref_state: Some(New),
4354            },
4355        )
4356        "#);
4357        insta::assert_debug_snapshot!(
4358            parse(r#"remote_bookmarks(remote=foo, bar)"#).unwrap_err().kind(),
4359            @r#"
4360        InvalidFunctionArguments {
4361            name: "remote_bookmarks",
4362            message: "Positional argument follows keyword argument",
4363        }
4364        "#);
4365        insta::assert_debug_snapshot!(
4366            parse(r#"remote_bookmarks("", foo, remote=bar)"#).unwrap_err().kind(),
4367            @r#"
4368        InvalidFunctionArguments {
4369            name: "remote_bookmarks",
4370            message: "Got multiple values for keyword \"remote\"",
4371        }
4372        "#);
4373        insta::assert_debug_snapshot!(
4374            parse(r#"remote_bookmarks(remote=bar, remote=bar)"#).unwrap_err().kind(),
4375            @r#"
4376        InvalidFunctionArguments {
4377            name: "remote_bookmarks",
4378            message: "Got multiple values for keyword \"remote\"",
4379        }
4380        "#);
4381        insta::assert_debug_snapshot!(
4382            parse(r#"remote_bookmarks(unknown=bar)"#).unwrap_err().kind(),
4383            @r#"
4384        InvalidFunctionArguments {
4385            name: "remote_bookmarks",
4386            message: "Unexpected keyword argument \"unknown\"",
4387        }
4388        "#);
4389    }
4390
4391    #[test]
4392    fn test_expand_symbol_alias() {
4393        let settings = insta_settings();
4394        let _guard = settings.bind_to_scope();
4395
4396        insta::assert_debug_snapshot!(
4397            parse_with_aliases("AB|c", [("AB", "a|b")]).unwrap(), @r#"
4398        Union(
4399            Union(
4400                CommitRef(Symbol("a")),
4401                CommitRef(Symbol("b")),
4402            ),
4403            CommitRef(Symbol("c")),
4404        )
4405        "#);
4406
4407        // Alias can be substituted to string literal.
4408        insta::assert_debug_snapshot!(
4409            parse_with_aliases_and_workspace("files(A)", [("A", "a")], WorkspaceName::DEFAULT)
4410                .unwrap(),
4411            @r#"Filter(File(Pattern(PrefixPath("a"))))"#);
4412
4413        // Alias can be substituted to string pattern.
4414        insta::assert_debug_snapshot!(
4415            parse_with_aliases("author_name(A)", [("A", "a")]).unwrap(),
4416            @r#"Filter(AuthorName(Pattern(Exact("a"))))"#);
4417        // However, parentheses are required because top-level x:y is parsed as
4418        // program modifier.
4419        insta::assert_debug_snapshot!(
4420            parse_with_aliases("author_name(A)", [("A", "(exact:a)")]).unwrap(),
4421            @r#"Filter(AuthorName(Pattern(Exact("a"))))"#);
4422
4423        // Sub-expression alias cannot be substituted to modifier expression.
4424        insta::assert_debug_snapshot!(
4425            parse_with_aliases_and_modifier("A-", [("A", "all:a")]).unwrap_err().kind(),
4426            @r#"InAliasExpansion("A")"#);
4427    }
4428
4429    #[test]
4430    fn test_expand_function_alias() {
4431        let settings = insta_settings();
4432        let _guard = settings.bind_to_scope();
4433
4434        // Pass string literal as parameter.
4435        insta::assert_debug_snapshot!(
4436            parse_with_aliases("F(a)", [("F(x)", "author_name(x)|committer_name(x)")]).unwrap(),
4437            @r#"
4438        Union(
4439            Filter(AuthorName(Pattern(Exact("a")))),
4440            Filter(CommitterName(Pattern(Exact("a")))),
4441        )
4442        "#);
4443    }
4444
4445    #[test]
4446    fn test_transform_expression() {
4447        let settings = insta_settings();
4448        let _guard = settings.bind_to_scope();
4449
4450        // Break without pre transformation
4451        insta::assert_debug_snapshot!(
4452            transform_expression(
4453                &ResolvedRevsetExpression::root(),
4454                |_| ControlFlow::Break(None),
4455                |_| Some(RevsetExpression::none()),
4456            ), @"None");
4457
4458        // Break with pre transformation
4459        insta::assert_debug_snapshot!(
4460            transform_expression(
4461                &ResolvedRevsetExpression::root(),
4462                |_| ControlFlow::Break(Some(RevsetExpression::all())),
4463                |_| Some(RevsetExpression::none()),
4464            ), @"Some(All)");
4465
4466        // Continue without pre transformation, do transform child
4467        insta::assert_debug_snapshot!(
4468            transform_expression(
4469                &ResolvedRevsetExpression::root().heads(),
4470                |_| ControlFlow::Continue(()),
4471                |x| match x.as_ref() {
4472                    RevsetExpression::Root => Some(RevsetExpression::none()),
4473                    _ => None,
4474                },
4475            ), @"Some(Heads(None))");
4476
4477        // Continue without pre transformation, do transform self
4478        insta::assert_debug_snapshot!(
4479            transform_expression(
4480                &ResolvedRevsetExpression::root().heads(),
4481                |_| ControlFlow::Continue(()),
4482                |x| match x.as_ref() {
4483                    RevsetExpression::Heads(y) => Some(y.clone()),
4484                    _ => None,
4485                },
4486            ), @"Some(Root)");
4487    }
4488
4489    #[test]
4490    fn test_resolve_referenced_commits() {
4491        let settings = insta_settings();
4492        let _guard = settings.bind_to_scope();
4493
4494        let visibility1 = Arc::new(ResolvedRevsetExpression::WithinVisibility {
4495            candidates: RevsetExpression::commit(CommitId::from_hex("100001")),
4496            visible_heads: vec![CommitId::from_hex("100000")],
4497        });
4498        let visibility2 = Arc::new(ResolvedRevsetExpression::WithinVisibility {
4499            candidates: RevsetExpression::filter(RevsetFilterPredicate::HasConflict),
4500            visible_heads: vec![CommitId::from_hex("200000")],
4501        });
4502        let commit3 = RevsetExpression::commit(CommitId::from_hex("000003"));
4503
4504        // Inner commits should be scoped. Both inner commits and visible heads
4505        // should be added to the outer scope.
4506        insta::assert_debug_snapshot!(
4507            resolve_referenced_commits(&visibility1.intersection(&RevsetExpression::all())), @r#"
4508        Some(
4509            WithinReference {
4510                candidates: Intersection(
4511                    WithinVisibility {
4512                        candidates: WithinReference {
4513                            candidates: Commits(
4514                                [
4515                                    CommitId("100001"),
4516                                ],
4517                            ),
4518                            commits: [
4519                                CommitId("100001"),
4520                            ],
4521                        },
4522                        visible_heads: [
4523                            CommitId("100000"),
4524                        ],
4525                    },
4526                    All,
4527                ),
4528                commits: [
4529                    CommitId("100000"),
4530                    CommitId("100001"),
4531                ],
4532            },
4533        )
4534        "#);
4535
4536        // Inner scope has no references, so WithinReference should be omitted.
4537        insta::assert_debug_snapshot!(
4538            resolve_referenced_commits(
4539                &visibility2
4540                    .intersection(&RevsetExpression::all())
4541                    .union(&commit3),
4542            ), @r#"
4543        Some(
4544            WithinReference {
4545                candidates: Union(
4546                    Intersection(
4547                        WithinVisibility {
4548                            candidates: Filter(HasConflict),
4549                            visible_heads: [
4550                                CommitId("200000"),
4551                            ],
4552                        },
4553                        All,
4554                    ),
4555                    Commits(
4556                        [
4557                            CommitId("000003"),
4558                        ],
4559                    ),
4560                ),
4561                commits: [
4562                    CommitId("000003"),
4563                    CommitId("200000"),
4564                ],
4565            },
4566        )
4567        "#);
4568
4569        // Sibling scopes should track referenced commits individually.
4570        insta::assert_debug_snapshot!(
4571            resolve_referenced_commits(
4572                &visibility1
4573                    .union(&visibility2)
4574                    .union(&commit3)
4575                    .intersection(&RevsetExpression::all())
4576            ), @r#"
4577        Some(
4578            WithinReference {
4579                candidates: Intersection(
4580                    Union(
4581                        Union(
4582                            WithinVisibility {
4583                                candidates: WithinReference {
4584                                    candidates: Commits(
4585                                        [
4586                                            CommitId("100001"),
4587                                        ],
4588                                    ),
4589                                    commits: [
4590                                        CommitId("100001"),
4591                                    ],
4592                                },
4593                                visible_heads: [
4594                                    CommitId("100000"),
4595                                ],
4596                            },
4597                            WithinVisibility {
4598                                candidates: Filter(HasConflict),
4599                                visible_heads: [
4600                                    CommitId("200000"),
4601                                ],
4602                            },
4603                        ),
4604                        Commits(
4605                            [
4606                                CommitId("000003"),
4607                            ],
4608                        ),
4609                    ),
4610                    All,
4611                ),
4612                commits: [
4613                    CommitId("000003"),
4614                    CommitId("100000"),
4615                    CommitId("100001"),
4616                    CommitId("200000"),
4617                ],
4618            },
4619        )
4620        "#);
4621
4622        // Referenced commits should be propagated from the innermost scope.
4623        insta::assert_debug_snapshot!(
4624            resolve_referenced_commits(&Arc::new(ResolvedRevsetExpression::WithinVisibility {
4625                candidates: visibility1.clone(),
4626                visible_heads: vec![CommitId::from_hex("400000")],
4627            })), @r#"
4628        Some(
4629            WithinReference {
4630                candidates: WithinVisibility {
4631                    candidates: WithinReference {
4632                        candidates: WithinVisibility {
4633                            candidates: WithinReference {
4634                                candidates: Commits(
4635                                    [
4636                                        CommitId("100001"),
4637                                    ],
4638                                ),
4639                                commits: [
4640                                    CommitId("100001"),
4641                                ],
4642                            },
4643                            visible_heads: [
4644                                CommitId("100000"),
4645                            ],
4646                        },
4647                        commits: [
4648                            CommitId("100000"),
4649                            CommitId("100001"),
4650                        ],
4651                    },
4652                    visible_heads: [
4653                        CommitId("400000"),
4654                    ],
4655                },
4656                commits: [
4657                    CommitId("400000"),
4658                    CommitId("100000"),
4659                    CommitId("100001"),
4660                ],
4661            },
4662        )
4663        "#);
4664
4665        // Resolved expression should be reused.
4666        let resolved = Arc::new(ResolvedRevsetExpression::WithinReference {
4667            // No referenced commits within the scope to test whether the
4668            // precomputed value is reused.
4669            candidates: RevsetExpression::none(),
4670            commits: vec![CommitId::from_hex("100000")],
4671        });
4672        insta::assert_debug_snapshot!(
4673            resolve_referenced_commits(&resolved), @"None");
4674        insta::assert_debug_snapshot!(
4675            resolve_referenced_commits(&resolved.intersection(&RevsetExpression::all())), @r#"
4676        Some(
4677            WithinReference {
4678                candidates: Intersection(
4679                    WithinReference {
4680                        candidates: None,
4681                        commits: [
4682                            CommitId("100000"),
4683                        ],
4684                    },
4685                    All,
4686                ),
4687                commits: [
4688                    CommitId("100000"),
4689                ],
4690            },
4691        )
4692        "#);
4693        insta::assert_debug_snapshot!(
4694            resolve_referenced_commits(&Arc::new(ResolvedRevsetExpression::WithinVisibility {
4695                candidates: resolved.clone(),
4696                visible_heads: vec![CommitId::from_hex("400000")],
4697            })), @r#"
4698        Some(
4699            WithinReference {
4700                candidates: WithinVisibility {
4701                    candidates: WithinReference {
4702                        candidates: None,
4703                        commits: [
4704                            CommitId("100000"),
4705                        ],
4706                    },
4707                    visible_heads: [
4708                        CommitId("400000"),
4709                    ],
4710                },
4711                commits: [
4712                    CommitId("400000"),
4713                    CommitId("100000"),
4714                ],
4715            },
4716        )
4717        "#);
4718    }
4719
4720    #[test]
4721    fn test_optimize_subtree() {
4722        let settings = insta_settings();
4723        let _guard = settings.bind_to_scope();
4724
4725        // Check that transform_expression_bottom_up() never rewrites enum variant
4726        // (e.g. Range -> DagRange) nor reorders arguments unintentionally.
4727
4728        insta::assert_debug_snapshot!(
4729            optimize(parse("parents(bookmarks() & all())").unwrap()), @r#"
4730        Ancestors {
4731            heads: CommitRef(Bookmarks(Pattern(Substring("")))),
4732            generation: 1..2,
4733            parents_range: 0..4294967295,
4734        }
4735        "#);
4736        insta::assert_debug_snapshot!(
4737            optimize(parse("children(bookmarks() & all())").unwrap()), @r#"
4738        Descendants {
4739            roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4740            generation: 1..2,
4741        }
4742        "#);
4743        insta::assert_debug_snapshot!(
4744            optimize(parse("ancestors(bookmarks() & all())").unwrap()), @r#"
4745        Ancestors {
4746            heads: CommitRef(Bookmarks(Pattern(Substring("")))),
4747            generation: 0..18446744073709551615,
4748            parents_range: 0..4294967295,
4749        }
4750        "#);
4751        insta::assert_debug_snapshot!(
4752            optimize(parse("descendants(bookmarks() & all())").unwrap()), @r#"
4753        Descendants {
4754            roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4755            generation: 0..18446744073709551615,
4756        }
4757        "#);
4758
4759        insta::assert_debug_snapshot!(
4760            optimize(parse("(bookmarks() & all())..(all() & tags())").unwrap()), @r#"
4761        Range {
4762            roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4763            heads: CommitRef(Tags(Pattern(Substring("")))),
4764            generation: 0..18446744073709551615,
4765            parents_range: 0..4294967295,
4766        }
4767        "#);
4768        insta::assert_debug_snapshot!(
4769            optimize(parse("(bookmarks() & all())::(all() & tags())").unwrap()), @r#"
4770        DagRange {
4771            roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4772            heads: CommitRef(Tags(Pattern(Substring("")))),
4773        }
4774        "#);
4775
4776        insta::assert_debug_snapshot!(
4777            optimize(parse("heads(bookmarks() & all())").unwrap()),
4778            @r#"
4779        Heads(
4780            CommitRef(Bookmarks(Pattern(Substring("")))),
4781        )
4782        "#);
4783        insta::assert_debug_snapshot!(
4784            optimize(parse("roots(bookmarks() & all())").unwrap()),
4785            @r#"
4786        Roots(
4787            CommitRef(Bookmarks(Pattern(Substring("")))),
4788        )
4789        "#);
4790
4791        insta::assert_debug_snapshot!(
4792            optimize(parse("latest(bookmarks() & all(), 2)").unwrap()), @r#"
4793        Latest {
4794            candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4795            count: 2,
4796        }
4797        "#);
4798
4799        insta::assert_debug_snapshot!(
4800            optimize(parse("present(foo ~ bar)").unwrap()), @r#"
4801        Present(
4802            Difference(
4803                CommitRef(Symbol("foo")),
4804                CommitRef(Symbol("bar")),
4805            ),
4806        )
4807        "#);
4808        insta::assert_debug_snapshot!(
4809            optimize(parse("present(bookmarks() & all())").unwrap()),
4810            @r#"
4811        Present(
4812            CommitRef(Bookmarks(Pattern(Substring("")))),
4813        )
4814        "#);
4815
4816        insta::assert_debug_snapshot!(
4817            optimize(parse("at_operation(@-, bookmarks() & all())").unwrap()), @r#"
4818        AtOperation {
4819            operation: "@-",
4820            candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4821        }
4822        "#);
4823        insta::assert_debug_snapshot!(
4824            optimize(Arc::new(RevsetExpression::WithinReference {
4825                candidates: parse("bookmarks() & all()").unwrap(),
4826                commits: vec![CommitId::from_hex("012345")],
4827            })), @r#"
4828        WithinReference {
4829            candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4830            commits: [
4831                CommitId("012345"),
4832            ],
4833        }
4834        "#);
4835        insta::assert_debug_snapshot!(
4836            optimize(Arc::new(RevsetExpression::WithinVisibility {
4837                candidates: parse("bookmarks() & all()").unwrap(),
4838                visible_heads: vec![CommitId::from_hex("012345")],
4839            })), @r#"
4840        WithinReference {
4841            candidates: WithinVisibility {
4842                candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4843                visible_heads: [
4844                    CommitId("012345"),
4845                ],
4846            },
4847            commits: [
4848                CommitId("012345"),
4849            ],
4850        }
4851        "#);
4852
4853        insta::assert_debug_snapshot!(
4854            optimize(parse("~bookmarks() & all()").unwrap()),
4855            @r#"
4856        NotIn(
4857            CommitRef(Bookmarks(Pattern(Substring("")))),
4858        )
4859        "#);
4860        insta::assert_debug_snapshot!(
4861            optimize(parse("(bookmarks() & all()) | (all() & tags())").unwrap()), @r#"
4862        Union(
4863            CommitRef(Bookmarks(Pattern(Substring("")))),
4864            CommitRef(Tags(Pattern(Substring("")))),
4865        )
4866        "#);
4867        insta::assert_debug_snapshot!(
4868            optimize(parse("(bookmarks() & all()) & (all() & tags())").unwrap()), @r#"
4869        Intersection(
4870            CommitRef(Bookmarks(Pattern(Substring("")))),
4871            CommitRef(Tags(Pattern(Substring("")))),
4872        )
4873        "#);
4874        insta::assert_debug_snapshot!(
4875            optimize(parse("(bookmarks() & all()) ~ (all() & tags())").unwrap()), @r#"
4876        Difference(
4877            CommitRef(Bookmarks(Pattern(Substring("")))),
4878            CommitRef(Tags(Pattern(Substring("")))),
4879        )
4880        "#);
4881    }
4882
4883    #[test]
4884    fn test_optimize_unchanged_subtree() {
4885        fn unwrap_union(
4886            expression: &UserRevsetExpression,
4887        ) -> (&Arc<UserRevsetExpression>, &Arc<UserRevsetExpression>) {
4888            match expression {
4889                RevsetExpression::Union(left, right) => (left, right),
4890                _ => panic!("unexpected expression: {expression:?}"),
4891            }
4892        }
4893
4894        // transform_expression_bottom_up() should not recreate tree unnecessarily.
4895        let parsed = parse("foo-").unwrap();
4896        let optimized = optimize(parsed.clone());
4897        assert!(Arc::ptr_eq(&parsed, &optimized));
4898
4899        let parsed = parse("bookmarks() | tags()").unwrap();
4900        let optimized = optimize(parsed.clone());
4901        assert!(Arc::ptr_eq(&parsed, &optimized));
4902
4903        let parsed = parse("bookmarks() & tags()").unwrap();
4904        let optimized = optimize(parsed.clone());
4905        assert!(Arc::ptr_eq(&parsed, &optimized));
4906
4907        // Only left subtree should be rewritten.
4908        let parsed = parse("(bookmarks() & all()) | tags()").unwrap();
4909        let optimized = optimize(parsed.clone());
4910        assert_matches!(
4911            unwrap_union(&optimized).0.as_ref(),
4912            RevsetExpression::CommitRef(RevsetCommitRef::Bookmarks(_))
4913        );
4914        assert!(Arc::ptr_eq(
4915            unwrap_union(&parsed).1,
4916            unwrap_union(&optimized).1
4917        ));
4918
4919        // Only right subtree should be rewritten.
4920        let parsed = parse("bookmarks() | (all() & tags())").unwrap();
4921        let optimized = optimize(parsed.clone());
4922        assert!(Arc::ptr_eq(
4923            unwrap_union(&parsed).0,
4924            unwrap_union(&optimized).0
4925        ));
4926        assert_matches!(
4927            unwrap_union(&optimized).1.as_ref(),
4928            RevsetExpression::CommitRef(RevsetCommitRef::Tags(_))
4929        );
4930    }
4931
4932    #[test]
4933    fn test_optimize_basic() {
4934        let settings = insta_settings();
4935        let _guard = settings.bind_to_scope();
4936
4937        insta::assert_debug_snapshot!(optimize(parse("all() | none()").unwrap()), @"All");
4938        insta::assert_debug_snapshot!(optimize(parse("all() & none()").unwrap()), @"None");
4939        insta::assert_debug_snapshot!(optimize(parse("root() | all()").unwrap()), @"All");
4940        insta::assert_debug_snapshot!(optimize(parse("root() & all()").unwrap()), @"Root");
4941        insta::assert_debug_snapshot!(optimize(parse("none() | root()").unwrap()), @"Root");
4942        insta::assert_debug_snapshot!(optimize(parse("none() & root()").unwrap()), @"None");
4943        insta::assert_debug_snapshot!(optimize(parse("~none()").unwrap()), @"All");
4944        insta::assert_debug_snapshot!(optimize(parse("~~none()").unwrap()), @"None");
4945        insta::assert_debug_snapshot!(optimize(parse("~all()").unwrap()), @"None");
4946        insta::assert_debug_snapshot!(optimize(parse("~~all()").unwrap()), @"All");
4947        insta::assert_debug_snapshot!(optimize(parse("~~foo").unwrap()), @r#"CommitRef(Symbol("foo"))"#);
4948        insta::assert_debug_snapshot!(
4949            optimize(parse("(root() | none()) & (visible_heads() | ~~all())").unwrap()), @"Root");
4950        insta::assert_debug_snapshot!(
4951            optimize(UserRevsetExpression::commits(vec![])), @"None");
4952        insta::assert_debug_snapshot!(
4953            optimize(UserRevsetExpression::commits(vec![]).negated()), @"All");
4954    }
4955
4956    #[test]
4957    fn test_optimize_difference() {
4958        let settings = insta_settings();
4959        let _guard = settings.bind_to_scope();
4960
4961        insta::assert_debug_snapshot!(optimize(parse("foo & ~bar").unwrap()), @r#"
4962        Difference(
4963            CommitRef(Symbol("foo")),
4964            CommitRef(Symbol("bar")),
4965        )
4966        "#);
4967        insta::assert_debug_snapshot!(optimize(parse("~foo & bar").unwrap()), @r#"
4968        Difference(
4969            CommitRef(Symbol("bar")),
4970            CommitRef(Symbol("foo")),
4971        )
4972        "#);
4973        insta::assert_debug_snapshot!(optimize(parse("~foo & bar & ~baz").unwrap()), @r#"
4974        Difference(
4975            Difference(
4976                CommitRef(Symbol("bar")),
4977                CommitRef(Symbol("foo")),
4978            ),
4979            CommitRef(Symbol("baz")),
4980        )
4981        "#);
4982        insta::assert_debug_snapshot!(optimize(parse("(all() & ~foo) & bar").unwrap()), @r#"
4983        Difference(
4984            CommitRef(Symbol("bar")),
4985            CommitRef(Symbol("foo")),
4986        )
4987        "#);
4988
4989        // Binary difference operation should go through the same optimization passes.
4990        insta::assert_debug_snapshot!(
4991            optimize(parse("all() ~ foo").unwrap()),
4992            @r#"NotIn(CommitRef(Symbol("foo")))"#);
4993        insta::assert_debug_snapshot!(optimize(parse("foo ~ bar").unwrap()), @r#"
4994        Difference(
4995            CommitRef(Symbol("foo")),
4996            CommitRef(Symbol("bar")),
4997        )
4998        "#);
4999        insta::assert_debug_snapshot!(optimize(parse("(all() ~ foo) & bar").unwrap()), @r#"
5000        Difference(
5001            CommitRef(Symbol("bar")),
5002            CommitRef(Symbol("foo")),
5003        )
5004        "#);
5005
5006        // Range expression.
5007        insta::assert_debug_snapshot!(optimize(parse("::foo & ~::bar").unwrap()), @r#"
5008        Range {
5009            roots: CommitRef(Symbol("bar")),
5010            heads: CommitRef(Symbol("foo")),
5011            generation: 0..18446744073709551615,
5012            parents_range: 0..4294967295,
5013        }
5014        "#);
5015        insta::assert_debug_snapshot!(optimize(parse("~::foo & ::bar").unwrap()), @r#"
5016        Range {
5017            roots: CommitRef(Symbol("foo")),
5018            heads: CommitRef(Symbol("bar")),
5019            generation: 0..18446744073709551615,
5020            parents_range: 0..4294967295,
5021        }
5022        "#);
5023        insta::assert_debug_snapshot!(optimize(parse("foo..").unwrap()), @r#"
5024        Range {
5025            roots: CommitRef(Symbol("foo")),
5026            heads: VisibleHeadsOrReferenced,
5027            generation: 0..18446744073709551615,
5028            parents_range: 0..4294967295,
5029        }
5030        "#);
5031        insta::assert_debug_snapshot!(optimize(parse("foo..bar").unwrap()), @r#"
5032        Range {
5033            roots: CommitRef(Symbol("foo")),
5034            heads: CommitRef(Symbol("bar")),
5035            generation: 0..18446744073709551615,
5036            parents_range: 0..4294967295,
5037        }
5038        "#);
5039        insta::assert_debug_snapshot!(optimize(parse("foo.. & ::bar").unwrap()), @r#"
5040        Range {
5041            roots: CommitRef(Symbol("foo")),
5042            heads: CommitRef(Symbol("bar")),
5043            generation: 0..18446744073709551615,
5044            parents_range: 0..4294967295,
5045        }
5046        "#);
5047        insta::assert_debug_snapshot!(optimize(parse("foo.. & first_ancestors(bar)").unwrap()), @r#"
5048        Range {
5049            roots: CommitRef(Symbol("foo")),
5050            heads: CommitRef(Symbol("bar")),
5051            generation: 0..18446744073709551615,
5052            parents_range: 0..1,
5053        }
5054        "#);
5055
5056        // Double/triple negates.
5057        insta::assert_debug_snapshot!(optimize(parse("foo & ~~bar").unwrap()), @r#"
5058        Intersection(
5059            CommitRef(Symbol("foo")),
5060            CommitRef(Symbol("bar")),
5061        )
5062        "#);
5063        insta::assert_debug_snapshot!(optimize(parse("foo & ~~~bar").unwrap()), @r#"
5064        Difference(
5065            CommitRef(Symbol("foo")),
5066            CommitRef(Symbol("bar")),
5067        )
5068        "#);
5069        insta::assert_debug_snapshot!(optimize(parse("~(all() & ~foo) & bar").unwrap()), @r#"
5070        Intersection(
5071            CommitRef(Symbol("foo")),
5072            CommitRef(Symbol("bar")),
5073        )
5074        "#);
5075
5076        // Should be better than '(all() & ~foo) & (all() & ~bar)'.
5077        insta::assert_debug_snapshot!(optimize(parse("~foo & ~bar").unwrap()), @r#"
5078        Difference(
5079            NotIn(CommitRef(Symbol("foo"))),
5080            CommitRef(Symbol("bar")),
5081        )
5082        "#);
5083
5084        // The roots of multiple ranges can be folded after being unfolded.
5085        insta::assert_debug_snapshot!(optimize(parse("a..b & c..d").unwrap()), @r#"
5086        Intersection(
5087            Range {
5088                roots: Union(
5089                    CommitRef(Symbol("a")),
5090                    CommitRef(Symbol("c")),
5091                ),
5092                heads: CommitRef(Symbol("b")),
5093                generation: 0..18446744073709551615,
5094                parents_range: 0..4294967295,
5095            },
5096            Ancestors {
5097                heads: CommitRef(Symbol("d")),
5098                generation: 0..18446744073709551615,
5099                parents_range: 0..4294967295,
5100            },
5101        )
5102        "#);
5103
5104        // Negated `first_ancestors()` doesn't prevent re-folding.
5105        insta::assert_debug_snapshot!(optimize(parse("foo..bar ~ first_ancestors(baz)").unwrap()), @r#"
5106        Difference(
5107            Range {
5108                roots: CommitRef(Symbol("foo")),
5109                heads: CommitRef(Symbol("bar")),
5110                generation: 0..18446744073709551615,
5111                parents_range: 0..4294967295,
5112            },
5113            Ancestors {
5114                heads: CommitRef(Symbol("baz")),
5115                generation: 0..18446744073709551615,
5116                parents_range: 0..1,
5117            },
5118        )
5119        "#);
5120
5121        // Negated ancestors can be combined into a range regardless of intersection
5122        // grouping order and intervening expressions.
5123        insta::assert_debug_snapshot!(optimize(parse("foo ~ ::a & (::b & bar & ::c) & (baz ~ ::d)").unwrap()), @r#"
5124        Intersection(
5125            Intersection(
5126                Intersection(
5127                    Intersection(
5128                        Range {
5129                            roots: Union(
5130                                CommitRef(Symbol("a")),
5131                                CommitRef(Symbol("d")),
5132                            ),
5133                            heads: CommitRef(Symbol("b")),
5134                            generation: 0..18446744073709551615,
5135                            parents_range: 0..4294967295,
5136                        },
5137                        Ancestors {
5138                            heads: CommitRef(Symbol("c")),
5139                            generation: 0..18446744073709551615,
5140                            parents_range: 0..4294967295,
5141                        },
5142                    ),
5143                    CommitRef(Symbol("foo")),
5144                ),
5145                CommitRef(Symbol("bar")),
5146            ),
5147            CommitRef(Symbol("baz")),
5148        )
5149        "#);
5150    }
5151
5152    #[test]
5153    fn test_optimize_not_in_ancestors() {
5154        let settings = insta_settings();
5155        let _guard = settings.bind_to_scope();
5156
5157        // '~(::foo)' is equivalent to 'foo..'.
5158        insta::assert_debug_snapshot!(optimize(parse("~(::foo)").unwrap()), @r#"
5159        Range {
5160            roots: CommitRef(Symbol("foo")),
5161            heads: VisibleHeadsOrReferenced,
5162            generation: 0..18446744073709551615,
5163            parents_range: 0..4294967295,
5164        }
5165        "#);
5166
5167        // '~(::foo-)' is equivalent to 'foo-..'.
5168        insta::assert_debug_snapshot!(optimize(parse("~(::foo-)").unwrap()), @r#"
5169        Range {
5170            roots: Ancestors {
5171                heads: CommitRef(Symbol("foo")),
5172                generation: 1..2,
5173                parents_range: 0..4294967295,
5174            },
5175            heads: VisibleHeadsOrReferenced,
5176            generation: 0..18446744073709551615,
5177            parents_range: 0..4294967295,
5178        }
5179        "#);
5180        insta::assert_debug_snapshot!(optimize(parse("~(::foo--)").unwrap()), @r#"
5181        Range {
5182            roots: Ancestors {
5183                heads: CommitRef(Symbol("foo")),
5184                generation: 2..3,
5185                parents_range: 0..4294967295,
5186            },
5187            heads: VisibleHeadsOrReferenced,
5188            generation: 0..18446744073709551615,
5189            parents_range: 0..4294967295,
5190        }
5191        "#);
5192
5193        // Bounded ancestors shouldn't be substituted.
5194        insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo, 1)").unwrap()), @r#"
5195        NotIn(
5196            Ancestors {
5197                heads: CommitRef(Symbol("foo")),
5198                generation: 0..1,
5199                parents_range: 0..4294967295,
5200            },
5201        )
5202        "#);
5203        insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo-, 1)").unwrap()), @r#"
5204        NotIn(
5205            Ancestors {
5206                heads: CommitRef(Symbol("foo")),
5207                generation: 1..2,
5208                parents_range: 0..4294967295,
5209            },
5210        )
5211        "#);
5212    }
5213
5214    #[test]
5215    fn test_optimize_filter_difference() {
5216        let settings = insta_settings();
5217        let _guard = settings.bind_to_scope();
5218
5219        // '~empty()' -> '~~file(*)' -> 'file(*)'
5220        insta::assert_debug_snapshot!(optimize(parse("~empty()").unwrap()), @"Filter(File(All))");
5221
5222        // '& baz' can be moved into the filter node, and form a difference node.
5223        insta::assert_debug_snapshot!(
5224            optimize(parse("(author_name(foo) & ~bar) & baz").unwrap()), @r#"
5225        Intersection(
5226            Difference(
5227                CommitRef(Symbol("baz")),
5228                CommitRef(Symbol("bar")),
5229            ),
5230            Filter(AuthorName(Pattern(Exact("foo")))),
5231        )
5232        "#);
5233
5234        // '~set & filter()' shouldn't be substituted.
5235        insta::assert_debug_snapshot!(
5236            optimize(parse("~foo & author_name(bar)").unwrap()), @r#"
5237        Intersection(
5238            NotIn(CommitRef(Symbol("foo"))),
5239            Filter(AuthorName(Pattern(Exact("bar")))),
5240        )
5241        "#);
5242        insta::assert_debug_snapshot!(
5243            optimize(parse("~foo & (author_name(bar) | baz)").unwrap()), @r#"
5244        Intersection(
5245            NotIn(CommitRef(Symbol("foo"))),
5246            AsFilter(
5247                Union(
5248                    Filter(AuthorName(Pattern(Exact("bar")))),
5249                    CommitRef(Symbol("baz")),
5250                ),
5251            ),
5252        )
5253        "#);
5254
5255        // Filter should be moved right of the intersection.
5256        insta::assert_debug_snapshot!(
5257            optimize(parse("author_name(foo) ~ bar").unwrap()), @r#"
5258        Intersection(
5259            NotIn(CommitRef(Symbol("bar"))),
5260            Filter(AuthorName(Pattern(Exact("foo")))),
5261        )
5262        "#);
5263    }
5264
5265    #[test]
5266    fn test_optimize_filter_intersection() {
5267        let settings = insta_settings();
5268        let _guard = settings.bind_to_scope();
5269
5270        insta::assert_debug_snapshot!(
5271            optimize(parse("author_name(foo)").unwrap()),
5272            @r#"Filter(AuthorName(Pattern(Exact("foo"))))"#);
5273
5274        insta::assert_debug_snapshot!(optimize(parse("foo & description(bar)").unwrap()), @r#"
5275        Intersection(
5276            CommitRef(Symbol("foo")),
5277            Filter(Description(Pattern(Exact("bar")))),
5278        )
5279        "#);
5280        insta::assert_debug_snapshot!(optimize(parse("author_name(foo) & bar").unwrap()), @r#"
5281        Intersection(
5282            CommitRef(Symbol("bar")),
5283            Filter(AuthorName(Pattern(Exact("foo")))),
5284        )
5285        "#);
5286        insta::assert_debug_snapshot!(
5287            optimize(parse("author_name(foo) & committer_name(bar)").unwrap()), @r#"
5288        AsFilter(
5289            Intersection(
5290                Filter(AuthorName(Pattern(Exact("foo")))),
5291                Filter(CommitterName(Pattern(Exact("bar")))),
5292            ),
5293        )
5294        "#);
5295
5296        insta::assert_debug_snapshot!(
5297            optimize(parse("foo & description(bar) & author_name(baz)").unwrap()), @r#"
5298        Intersection(
5299            CommitRef(Symbol("foo")),
5300            AsFilter(
5301                Intersection(
5302                    Filter(Description(Pattern(Exact("bar")))),
5303                    Filter(AuthorName(Pattern(Exact("baz")))),
5304                ),
5305            ),
5306        )
5307        "#);
5308        insta::assert_debug_snapshot!(
5309            optimize(parse("committer_name(foo) & bar & author_name(baz)").unwrap()), @r#"
5310        Intersection(
5311            CommitRef(Symbol("bar")),
5312            AsFilter(
5313                Intersection(
5314                    Filter(CommitterName(Pattern(Exact("foo")))),
5315                    Filter(AuthorName(Pattern(Exact("baz")))),
5316                ),
5317            ),
5318        )
5319        "#);
5320        insta::assert_debug_snapshot!(
5321            optimize(parse_with_workspace(
5322                "committer_name(foo) & files(bar) & baz",
5323                WorkspaceName::DEFAULT).unwrap(),
5324            ), @r#"
5325        Intersection(
5326            CommitRef(Symbol("baz")),
5327            AsFilter(
5328                Intersection(
5329                    Filter(CommitterName(Pattern(Exact("foo")))),
5330                    Filter(File(Pattern(PrefixPath("bar")))),
5331                ),
5332            ),
5333        )
5334        "#);
5335        insta::assert_debug_snapshot!(
5336            optimize(parse_with_workspace(
5337                "committer_name(foo) & files(bar) & author_name(baz)",
5338                WorkspaceName::DEFAULT).unwrap(),
5339            ), @r#"
5340        AsFilter(
5341            Intersection(
5342                Intersection(
5343                    Filter(CommitterName(Pattern(Exact("foo")))),
5344                    Filter(File(Pattern(PrefixPath("bar")))),
5345                ),
5346                Filter(AuthorName(Pattern(Exact("baz")))),
5347            ),
5348        )
5349        "#);
5350        insta::assert_debug_snapshot!(
5351            optimize(parse_with_workspace(
5352                "foo & files(bar) & baz",
5353                WorkspaceName::DEFAULT).unwrap(),
5354            ), @r#"
5355        Intersection(
5356            Intersection(
5357                CommitRef(Symbol("foo")),
5358                CommitRef(Symbol("baz")),
5359            ),
5360            Filter(File(Pattern(PrefixPath("bar")))),
5361        )
5362        "#);
5363
5364        insta::assert_debug_snapshot!(
5365            optimize(parse("foo & description(bar) & author_name(baz) & qux").unwrap()), @r#"
5366        Intersection(
5367            Intersection(
5368                CommitRef(Symbol("foo")),
5369                CommitRef(Symbol("qux")),
5370            ),
5371            AsFilter(
5372                Intersection(
5373                    Filter(Description(Pattern(Exact("bar")))),
5374                    Filter(AuthorName(Pattern(Exact("baz")))),
5375                ),
5376            ),
5377        )
5378        "#);
5379        insta::assert_debug_snapshot!(
5380            optimize(parse("foo & description(bar) & parents(author_name(baz)) & qux").unwrap()),
5381            @r#"
5382        Intersection(
5383            Intersection(
5384                Intersection(
5385                    CommitRef(Symbol("foo")),
5386                    Ancestors {
5387                        heads: Filter(AuthorName(Pattern(Exact("baz")))),
5388                        generation: 1..2,
5389                        parents_range: 0..4294967295,
5390                    },
5391                ),
5392                CommitRef(Symbol("qux")),
5393            ),
5394            Filter(Description(Pattern(Exact("bar")))),
5395        )
5396        "#);
5397        insta::assert_debug_snapshot!(
5398            optimize(parse("foo & description(bar) & parents(author_name(baz) & qux)").unwrap()),
5399            @r#"
5400        Intersection(
5401            Intersection(
5402                CommitRef(Symbol("foo")),
5403                Ancestors {
5404                    heads: Intersection(
5405                        CommitRef(Symbol("qux")),
5406                        Filter(AuthorName(Pattern(Exact("baz")))),
5407                    ),
5408                    generation: 1..2,
5409                    parents_range: 0..4294967295,
5410                },
5411            ),
5412            Filter(Description(Pattern(Exact("bar")))),
5413        )
5414        "#);
5415
5416        // Symbols have to be pushed down to the innermost filter node.
5417        insta::assert_debug_snapshot!(
5418            optimize(parse("(a & author_name(A)) & (b & author_name(B)) & (c & author_name(C))").unwrap()),
5419            @r#"
5420        Intersection(
5421            Intersection(
5422                Intersection(
5423                    CommitRef(Symbol("a")),
5424                    CommitRef(Symbol("b")),
5425                ),
5426                CommitRef(Symbol("c")),
5427            ),
5428            AsFilter(
5429                Intersection(
5430                    Intersection(
5431                        Filter(AuthorName(Pattern(Exact("A")))),
5432                        Filter(AuthorName(Pattern(Exact("B")))),
5433                    ),
5434                    Filter(AuthorName(Pattern(Exact("C")))),
5435                ),
5436            ),
5437        )
5438        "#);
5439        insta::assert_debug_snapshot!(
5440            optimize(parse("(a & author_name(A)) & ((b & author_name(B)) & (c & author_name(C))) & d").unwrap()),
5441            @r#"
5442        Intersection(
5443            Intersection(
5444                Intersection(
5445                    Intersection(
5446                        CommitRef(Symbol("a")),
5447                        CommitRef(Symbol("b")),
5448                    ),
5449                    CommitRef(Symbol("c")),
5450                ),
5451                CommitRef(Symbol("d")),
5452            ),
5453            AsFilter(
5454                Intersection(
5455                    Intersection(
5456                        Filter(AuthorName(Pattern(Exact("A")))),
5457                        Filter(AuthorName(Pattern(Exact("B")))),
5458                    ),
5459                    Filter(AuthorName(Pattern(Exact("C")))),
5460                ),
5461            ),
5462        )
5463        "#);
5464
5465        // 'all()' moves in to 'filter()' first, so 'A & filter()' can be found.
5466        insta::assert_debug_snapshot!(
5467            optimize(parse("foo & (all() & description(bar)) & (author_name(baz) & all())").unwrap()),
5468            @r#"
5469        Intersection(
5470            CommitRef(Symbol("foo")),
5471            AsFilter(
5472                Intersection(
5473                    Filter(Description(Pattern(Exact("bar")))),
5474                    Filter(AuthorName(Pattern(Exact("baz")))),
5475                ),
5476            ),
5477        )
5478        "#);
5479
5480        // Filter node shouldn't move across at_operation() boundary.
5481        insta::assert_debug_snapshot!(
5482            optimize(parse("author_name(foo) & bar & at_operation(@-, committer_name(baz))").unwrap()),
5483            @r#"
5484        Intersection(
5485            Intersection(
5486                CommitRef(Symbol("bar")),
5487                AtOperation {
5488                    operation: "@-",
5489                    candidates: Filter(CommitterName(Pattern(Exact("baz")))),
5490                },
5491            ),
5492            Filter(AuthorName(Pattern(Exact("foo")))),
5493        )
5494        "#);
5495    }
5496
5497    #[test]
5498    fn test_optimize_filter_subtree() {
5499        let settings = insta_settings();
5500        let _guard = settings.bind_to_scope();
5501
5502        insta::assert_debug_snapshot!(
5503            optimize(parse("(author_name(foo) | bar) & baz").unwrap()), @r#"
5504        Intersection(
5505            CommitRef(Symbol("baz")),
5506            AsFilter(
5507                Union(
5508                    Filter(AuthorName(Pattern(Exact("foo")))),
5509                    CommitRef(Symbol("bar")),
5510                ),
5511            ),
5512        )
5513        "#);
5514
5515        // 'merges() & foo' can be evaluated independently
5516        insta::assert_debug_snapshot!(
5517            optimize(parse("merges() & foo | bar").unwrap()), @r#"
5518        Union(
5519            Intersection(
5520                CommitRef(Symbol("foo")),
5521                Filter(ParentCount(2..4294967295)),
5522            ),
5523            CommitRef(Symbol("bar")),
5524        )
5525        "#);
5526
5527        // 'merges() & foo' can be evaluated independently, but 'conflicts()'
5528        // can't. We'll need implicit 'all() & _' anyway.
5529        insta::assert_debug_snapshot!(
5530            optimize(parse("merges() & foo | conflicts()").unwrap()), @r#"
5531        AsFilter(
5532            Union(
5533                Intersection(
5534                    CommitRef(Symbol("foo")),
5535                    Filter(ParentCount(2..4294967295)),
5536                ),
5537                Filter(HasConflict),
5538            ),
5539        )
5540        "#);
5541
5542        // Nested filter intersection with union
5543        insta::assert_debug_snapshot!(
5544            optimize(parse("foo | conflicts() & merges() & signed()").unwrap()), @r#"
5545        AsFilter(
5546            Union(
5547                CommitRef(Symbol("foo")),
5548                Intersection(
5549                    Intersection(
5550                        Filter(HasConflict),
5551                        Filter(ParentCount(2..4294967295)),
5552                    ),
5553                    Filter(Signed),
5554                ),
5555            ),
5556        )
5557        "#);
5558
5559        insta::assert_debug_snapshot!(
5560            optimize(parse("(foo | committer_name(bar)) & description(baz) & qux").unwrap()), @r#"
5561        Intersection(
5562            CommitRef(Symbol("qux")),
5563            AsFilter(
5564                Intersection(
5565                    Union(
5566                        CommitRef(Symbol("foo")),
5567                        Filter(CommitterName(Pattern(Exact("bar")))),
5568                    ),
5569                    Filter(Description(Pattern(Exact("baz")))),
5570                ),
5571            ),
5572        )
5573        "#);
5574
5575        insta::assert_debug_snapshot!(
5576            optimize(parse(
5577                "(~present(author_name(foo) & description(bar)) | baz) & qux").unwrap()), @r#"
5578        Intersection(
5579            CommitRef(Symbol("qux")),
5580            AsFilter(
5581                Union(
5582                    NotIn(
5583                        Present(
5584                            Intersection(
5585                                Filter(AuthorName(Pattern(Exact("foo")))),
5586                                Filter(Description(Pattern(Exact("bar")))),
5587                            ),
5588                        ),
5589                    ),
5590                    CommitRef(Symbol("baz")),
5591                ),
5592            ),
5593        )
5594        "#);
5595
5596        // Symbols have to be pushed down to the innermost filter node.
5597        insta::assert_debug_snapshot!(
5598            optimize(parse(
5599                "(a & (author_name(A) | 0)) & (b & (author_name(B) | 1)) & (c & (author_name(C) | 2))").unwrap()),
5600            @r#"
5601        Intersection(
5602            Intersection(
5603                Intersection(
5604                    CommitRef(Symbol("a")),
5605                    CommitRef(Symbol("b")),
5606                ),
5607                CommitRef(Symbol("c")),
5608            ),
5609            AsFilter(
5610                Intersection(
5611                    Intersection(
5612                        Union(
5613                            Filter(AuthorName(Pattern(Exact("A")))),
5614                            CommitRef(Symbol("0")),
5615                        ),
5616                        Union(
5617                            Filter(AuthorName(Pattern(Exact("B")))),
5618                            CommitRef(Symbol("1")),
5619                        ),
5620                    ),
5621                    Union(
5622                        Filter(AuthorName(Pattern(Exact("C")))),
5623                        CommitRef(Symbol("2")),
5624                    ),
5625                ),
5626            ),
5627        )
5628        "#);
5629
5630        // Filters can be merged after ancestor unions are folded.
5631        insta::assert_debug_snapshot!(optimize(parse("::foo | ::author_name(bar)").unwrap()), @r#"
5632        Ancestors {
5633            heads: HeadsRange {
5634                roots: None,
5635                heads: VisibleHeadsOrReferenced,
5636                parents_range: 0..4294967295,
5637                filter: AsFilter(
5638                    Union(
5639                        CommitRef(Symbol("foo")),
5640                        Filter(AuthorName(Pattern(Exact("bar")))),
5641                    ),
5642                ),
5643            },
5644            generation: 0..18446744073709551615,
5645            parents_range: 0..4294967295,
5646        }
5647        "#);
5648    }
5649
5650    #[test]
5651    fn test_optimize_ancestors() {
5652        let settings = insta_settings();
5653        let _guard = settings.bind_to_scope();
5654
5655        // Typical scenario: fold nested parents()
5656        insta::assert_debug_snapshot!(optimize(parse("foo--").unwrap()), @r#"
5657        Ancestors {
5658            heads: CommitRef(Symbol("foo")),
5659            generation: 2..3,
5660            parents_range: 0..4294967295,
5661        }
5662        "#);
5663        insta::assert_debug_snapshot!(optimize(parse("::(foo---)").unwrap()), @r#"
5664        Ancestors {
5665            heads: CommitRef(Symbol("foo")),
5666            generation: 3..18446744073709551615,
5667            parents_range: 0..4294967295,
5668        }
5669        "#);
5670        insta::assert_debug_snapshot!(optimize(parse("(::foo)---").unwrap()), @r#"
5671        Ancestors {
5672            heads: CommitRef(Symbol("foo")),
5673            generation: 3..18446744073709551615,
5674            parents_range: 0..4294967295,
5675        }
5676        "#);
5677
5678        // 'foo-+' is not 'foo'.
5679        insta::assert_debug_snapshot!(optimize(parse("foo---+").unwrap()), @r#"
5680        Descendants {
5681            roots: Ancestors {
5682                heads: CommitRef(Symbol("foo")),
5683                generation: 3..4,
5684                parents_range: 0..4294967295,
5685            },
5686            generation: 1..2,
5687        }
5688        "#);
5689
5690        // For 'roots..heads', heads can be folded.
5691        insta::assert_debug_snapshot!(optimize(parse("foo..(bar--)").unwrap()), @r#"
5692        Range {
5693            roots: CommitRef(Symbol("foo")),
5694            heads: CommitRef(Symbol("bar")),
5695            generation: 2..18446744073709551615,
5696            parents_range: 0..4294967295,
5697        }
5698        "#);
5699        // roots can also be folded, and the range expression is reconstructed.
5700        insta::assert_debug_snapshot!(optimize(parse("(foo--)..(bar---)").unwrap()), @r#"
5701        Range {
5702            roots: Ancestors {
5703                heads: CommitRef(Symbol("foo")),
5704                generation: 2..3,
5705                parents_range: 0..4294967295,
5706            },
5707            heads: CommitRef(Symbol("bar")),
5708            generation: 3..18446744073709551615,
5709            parents_range: 0..4294967295,
5710        }
5711        "#);
5712        // Bounded ancestors shouldn't be substituted to range.
5713        insta::assert_debug_snapshot!(
5714            optimize(parse("~ancestors(foo, 2) & ::bar").unwrap()), @r#"
5715        Difference(
5716            Ancestors {
5717                heads: CommitRef(Symbol("bar")),
5718                generation: 0..18446744073709551615,
5719                parents_range: 0..4294967295,
5720            },
5721            Ancestors {
5722                heads: CommitRef(Symbol("foo")),
5723                generation: 0..2,
5724                parents_range: 0..4294967295,
5725            },
5726        )
5727        "#);
5728
5729        // If inner range is bounded by roots, it cannot be merged.
5730        // e.g. '..(foo..foo)' is equivalent to '..none()', not to '..foo'
5731        insta::assert_debug_snapshot!(optimize(parse("(foo..bar)--").unwrap()), @r#"
5732        Ancestors {
5733            heads: Range {
5734                roots: CommitRef(Symbol("foo")),
5735                heads: CommitRef(Symbol("bar")),
5736                generation: 0..18446744073709551615,
5737                parents_range: 0..4294967295,
5738            },
5739            generation: 2..3,
5740            parents_range: 0..4294967295,
5741        }
5742        "#);
5743        insta::assert_debug_snapshot!(optimize(parse("foo..(bar..baz)").unwrap()), @r#"
5744        Range {
5745            roots: CommitRef(Symbol("foo")),
5746            heads: HeadsRange {
5747                roots: CommitRef(Symbol("bar")),
5748                heads: CommitRef(Symbol("baz")),
5749                parents_range: 0..4294967295,
5750                filter: All,
5751            },
5752            generation: 0..18446744073709551615,
5753            parents_range: 0..4294967295,
5754        }
5755        "#);
5756
5757        // Ancestors of empty generation range should be empty.
5758        insta::assert_debug_snapshot!(
5759            optimize(parse("ancestors(ancestors(foo), 0)").unwrap()), @r#"
5760        Ancestors {
5761            heads: CommitRef(Symbol("foo")),
5762            generation: 0..0,
5763            parents_range: 0..4294967295,
5764        }
5765        "#
5766        );
5767        insta::assert_debug_snapshot!(
5768            optimize(parse("ancestors(ancestors(foo, 0))").unwrap()), @r#"
5769        Ancestors {
5770            heads: CommitRef(Symbol("foo")),
5771            generation: 0..0,
5772            parents_range: 0..4294967295,
5773        }
5774        "#
5775        );
5776
5777        // Ancestors can only be folded if parent ranges match.
5778        insta::assert_debug_snapshot!(
5779            optimize(parse("first_ancestors(first_ancestors(foo, 5), 5)").unwrap()), @r#"
5780        Ancestors {
5781            heads: CommitRef(Symbol("foo")),
5782            generation: 0..9,
5783            parents_range: 0..1,
5784        }
5785        "#
5786        );
5787        insta::assert_debug_snapshot!(
5788            optimize(parse("first_ancestors(first_parent(foo), 5)").unwrap()), @r#"
5789        Ancestors {
5790            heads: CommitRef(Symbol("foo")),
5791            generation: 1..6,
5792            parents_range: 0..1,
5793        }
5794        "#
5795        );
5796        insta::assert_debug_snapshot!(
5797            optimize(parse("first_ancestors(ancestors(foo, 5), 5)").unwrap()), @r#"
5798        Ancestors {
5799            heads: Ancestors {
5800                heads: CommitRef(Symbol("foo")),
5801                generation: 0..5,
5802                parents_range: 0..4294967295,
5803            },
5804            generation: 0..5,
5805            parents_range: 0..1,
5806        }
5807        "#
5808        );
5809        insta::assert_debug_snapshot!(
5810            optimize(parse("ancestors(first_ancestors(foo, 5), 5)").unwrap()), @r#"
5811        Ancestors {
5812            heads: Ancestors {
5813                heads: CommitRef(Symbol("foo")),
5814                generation: 0..5,
5815                parents_range: 0..1,
5816            },
5817            generation: 0..5,
5818            parents_range: 0..4294967295,
5819        }
5820        "#
5821        );
5822    }
5823
5824    #[test]
5825    fn test_optimize_descendants() {
5826        let settings = insta_settings();
5827        let _guard = settings.bind_to_scope();
5828
5829        // Typical scenario: fold nested children()
5830        insta::assert_debug_snapshot!(optimize(parse("foo++").unwrap()), @r#"
5831        Descendants {
5832            roots: CommitRef(Symbol("foo")),
5833            generation: 2..3,
5834        }
5835        "#);
5836        insta::assert_debug_snapshot!(optimize(parse("(foo+++)::").unwrap()), @r#"
5837        Descendants {
5838            roots: CommitRef(Symbol("foo")),
5839            generation: 3..18446744073709551615,
5840        }
5841        "#);
5842        insta::assert_debug_snapshot!(optimize(parse("(foo::)+++").unwrap()), @r#"
5843        Descendants {
5844            roots: CommitRef(Symbol("foo")),
5845            generation: 3..18446744073709551615,
5846        }
5847        "#);
5848
5849        // 'foo+-' is not 'foo'.
5850        insta::assert_debug_snapshot!(optimize(parse("foo+++-").unwrap()), @r#"
5851        Ancestors {
5852            heads: Descendants {
5853                roots: CommitRef(Symbol("foo")),
5854                generation: 3..4,
5855            },
5856            generation: 1..2,
5857            parents_range: 0..4294967295,
5858        }
5859        "#);
5860
5861        // TODO: Inner Descendants can be folded into DagRange. Perhaps, we can rewrite
5862        // 'x::y' to 'x:: & ::y' first, so the common substitution rule can handle both
5863        // 'x+::y' and 'x+ & ::y'.
5864        insta::assert_debug_snapshot!(optimize(parse("(foo++)::bar").unwrap()), @r#"
5865        DagRange {
5866            roots: Descendants {
5867                roots: CommitRef(Symbol("foo")),
5868                generation: 2..3,
5869            },
5870            heads: CommitRef(Symbol("bar")),
5871        }
5872        "#);
5873    }
5874
5875    #[test]
5876    fn test_optimize_flatten_intersection() {
5877        let settings = insta_settings();
5878        let _guard = settings.bind_to_scope();
5879
5880        // Nested intersections should be flattened.
5881        insta::assert_debug_snapshot!(optimize(parse("a & ((b & c) & (d & e))").unwrap()), @r#"
5882        Intersection(
5883            Intersection(
5884                Intersection(
5885                    Intersection(
5886                        CommitRef(Symbol("a")),
5887                        CommitRef(Symbol("b")),
5888                    ),
5889                    CommitRef(Symbol("c")),
5890                ),
5891                CommitRef(Symbol("d")),
5892            ),
5893            CommitRef(Symbol("e")),
5894        )
5895        "#);
5896    }
5897
5898    #[test]
5899    fn test_optimize_ancestors_union() {
5900        let settings = insta_settings();
5901        let _guard = settings.bind_to_scope();
5902
5903        // Ancestors should be folded in unions.
5904        insta::assert_debug_snapshot!(optimize(parse("::a | ::b | ::c | ::d").unwrap()), @r#"
5905        Ancestors {
5906            heads: Union(
5907                Union(
5908                    CommitRef(Symbol("a")),
5909                    CommitRef(Symbol("b")),
5910                ),
5911                Union(
5912                    CommitRef(Symbol("c")),
5913                    CommitRef(Symbol("d")),
5914                ),
5915            ),
5916            generation: 0..18446744073709551615,
5917            parents_range: 0..4294967295,
5918        }
5919        "#);
5920        insta::assert_debug_snapshot!(optimize(parse("ancestors(a-) | ancestors(b)").unwrap()), @r#"
5921        Ancestors {
5922            heads: Union(
5923                Ancestors {
5924                    heads: CommitRef(Symbol("a")),
5925                    generation: 1..2,
5926                    parents_range: 0..4294967295,
5927                },
5928                CommitRef(Symbol("b")),
5929            ),
5930            generation: 0..18446744073709551615,
5931            parents_range: 0..4294967295,
5932        }
5933        "#);
5934
5935        // Negated ancestors should be folded.
5936        insta::assert_debug_snapshot!(optimize(parse("~::a- & ~::b & ~::c & ::d").unwrap()), @r#"
5937        Range {
5938            roots: Union(
5939                Union(
5940                    Ancestors {
5941                        heads: CommitRef(Symbol("a")),
5942                        generation: 1..2,
5943                        parents_range: 0..4294967295,
5944                    },
5945                    CommitRef(Symbol("b")),
5946                ),
5947                CommitRef(Symbol("c")),
5948            ),
5949            heads: CommitRef(Symbol("d")),
5950            generation: 0..18446744073709551615,
5951            parents_range: 0..4294967295,
5952        }
5953        "#);
5954        insta::assert_debug_snapshot!(optimize(parse("a..b ~ ::c- ~ ::d").unwrap()), @r#"
5955        Range {
5956            roots: Union(
5957                Union(
5958                    CommitRef(Symbol("a")),
5959                    Ancestors {
5960                        heads: CommitRef(Symbol("c")),
5961                        generation: 1..2,
5962                        parents_range: 0..4294967295,
5963                    },
5964                ),
5965                CommitRef(Symbol("d")),
5966            ),
5967            heads: CommitRef(Symbol("b")),
5968            generation: 0..18446744073709551615,
5969            parents_range: 0..4294967295,
5970        }
5971        "#);
5972
5973        // Ancestors with a bounded generation range should not be merged.
5974        insta::assert_debug_snapshot!(optimize(parse("ancestors(a, 2) | ancestors(b)").unwrap()), @r#"
5975        Union(
5976            Ancestors {
5977                heads: CommitRef(Symbol("a")),
5978                generation: 0..2,
5979                parents_range: 0..4294967295,
5980            },
5981            Ancestors {
5982                heads: CommitRef(Symbol("b")),
5983                generation: 0..18446744073709551615,
5984                parents_range: 0..4294967295,
5985            },
5986        )
5987        "#);
5988    }
5989
5990    #[test]
5991    fn test_optimize_sort_negations_and_ancestors() {
5992        let settings = insta_settings();
5993        let _guard = settings.bind_to_scope();
5994
5995        // Negated ancestors and ancestors should be moved to the left, and other
5996        // negations should be moved to the right.
5997        insta::assert_debug_snapshot!(optimize(parse("~a & ::b & ~::c & d ~ e & f & ::g & ~::h").unwrap()), @r#"
5998        Difference(
5999            Difference(
6000                Intersection(
6001                    Intersection(
6002                        Intersection(
6003                            Range {
6004                                roots: Union(
6005                                    CommitRef(Symbol("c")),
6006                                    CommitRef(Symbol("h")),
6007                                ),
6008                                heads: CommitRef(Symbol("b")),
6009                                generation: 0..18446744073709551615,
6010                                parents_range: 0..4294967295,
6011                            },
6012                            Ancestors {
6013                                heads: CommitRef(Symbol("g")),
6014                                generation: 0..18446744073709551615,
6015                                parents_range: 0..4294967295,
6016                            },
6017                        ),
6018                        CommitRef(Symbol("d")),
6019                    ),
6020                    CommitRef(Symbol("f")),
6021                ),
6022                CommitRef(Symbol("a")),
6023            ),
6024            CommitRef(Symbol("e")),
6025        )
6026        "#);
6027    }
6028
6029    #[test]
6030    fn test_optimize_heads_range() {
6031        let settings = insta_settings();
6032        let _guard = settings.bind_to_scope();
6033
6034        // Heads of basic range operators can be folded.
6035        insta::assert_debug_snapshot!(optimize(parse("heads(::)").unwrap()), @r"
6036        HeadsRange {
6037            roots: None,
6038            heads: VisibleHeadsOrReferenced,
6039            parents_range: 0..4294967295,
6040            filter: All,
6041        }
6042        ");
6043        insta::assert_debug_snapshot!(optimize(parse("heads(::foo)").unwrap()), @r#"
6044        HeadsRange {
6045            roots: None,
6046            heads: CommitRef(Symbol("foo")),
6047            parents_range: 0..4294967295,
6048            filter: All,
6049        }
6050        "#);
6051        // It might be better to use `roots: Root`, but it would require adding a
6052        // special case for `~root()`, and this should be similar in performance.
6053        insta::assert_debug_snapshot!(optimize(parse("heads(..)").unwrap()), @r"
6054        HeadsRange {
6055            roots: None,
6056            heads: VisibleHeadsOrReferenced,
6057            parents_range: 0..4294967295,
6058            filter: NotIn(Root),
6059        }
6060        ");
6061        insta::assert_debug_snapshot!(optimize(parse("heads(foo..)").unwrap()), @r#"
6062        HeadsRange {
6063            roots: CommitRef(Symbol("foo")),
6064            heads: VisibleHeadsOrReferenced,
6065            parents_range: 0..4294967295,
6066            filter: All,
6067        }
6068        "#);
6069        insta::assert_debug_snapshot!(optimize(parse("heads(..bar)").unwrap()), @r#"
6070        HeadsRange {
6071            roots: Root,
6072            heads: CommitRef(Symbol("bar")),
6073            parents_range: 0..4294967295,
6074            filter: All,
6075        }
6076        "#);
6077        insta::assert_debug_snapshot!(optimize(parse("heads(foo..bar)").unwrap()), @r#"
6078        HeadsRange {
6079            roots: CommitRef(Symbol("foo")),
6080            heads: CommitRef(Symbol("bar")),
6081            parents_range: 0..4294967295,
6082            filter: All,
6083        }
6084        "#);
6085        insta::assert_debug_snapshot!(optimize(parse("heads(~::foo & ::bar)").unwrap()), @r#"
6086        HeadsRange {
6087            roots: CommitRef(Symbol("foo")),
6088            heads: CommitRef(Symbol("bar")),
6089            parents_range: 0..4294967295,
6090            filter: All,
6091        }
6092        "#);
6093        insta::assert_debug_snapshot!(optimize(parse("heads(~::foo)").unwrap()), @r#"
6094        HeadsRange {
6095            roots: CommitRef(Symbol("foo")),
6096            heads: VisibleHeadsOrReferenced,
6097            parents_range: 0..4294967295,
6098            filter: All,
6099        }
6100        "#);
6101        insta::assert_debug_snapshot!(optimize(parse("heads(a..b & c..d)").unwrap()), @r#"
6102        HeadsRange {
6103            roots: Union(
6104                CommitRef(Symbol("a")),
6105                CommitRef(Symbol("c")),
6106            ),
6107            heads: CommitRef(Symbol("b")),
6108            parents_range: 0..4294967295,
6109            filter: Ancestors {
6110                heads: CommitRef(Symbol("d")),
6111                generation: 0..18446744073709551615,
6112                parents_range: 0..4294967295,
6113            },
6114        }
6115        "#);
6116
6117        // Heads of first-parent ancestors can also be folded.
6118        insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(foo))").unwrap()), @r#"
6119        HeadsRange {
6120            roots: None,
6121            heads: CommitRef(Symbol("foo")),
6122            parents_range: 0..1,
6123            filter: All,
6124        }
6125        "#);
6126        insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(foo) & bar..)").unwrap()), @r#"
6127        HeadsRange {
6128            roots: CommitRef(Symbol("bar")),
6129            heads: CommitRef(Symbol("foo")),
6130            parents_range: 0..1,
6131            filter: All,
6132        }
6133        "#);
6134        insta::assert_debug_snapshot!(optimize(parse("heads(foo.. & first_ancestors(bar) & ::baz)").unwrap()), @r#"
6135        HeadsRange {
6136            roots: CommitRef(Symbol("foo")),
6137            heads: CommitRef(Symbol("bar")),
6138            parents_range: 0..1,
6139            filter: Ancestors {
6140                heads: CommitRef(Symbol("baz")),
6141                generation: 0..18446744073709551615,
6142                parents_range: 0..4294967295,
6143            },
6144        }
6145        "#);
6146
6147        // Ancestors with a limited depth should not be optimized.
6148        insta::assert_debug_snapshot!(optimize(parse("heads(ancestors(foo, 2))").unwrap()), @r#"
6149        Heads(
6150            Ancestors {
6151                heads: CommitRef(Symbol("foo")),
6152                generation: 0..2,
6153                parents_range: 0..4294967295,
6154            },
6155        )
6156        "#);
6157        insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(foo, 2))").unwrap()), @r#"
6158        Heads(
6159            Ancestors {
6160                heads: CommitRef(Symbol("foo")),
6161                generation: 0..2,
6162                parents_range: 0..1,
6163            },
6164        )
6165        "#);
6166
6167        // Generation folding should not prevent optimizing heads.
6168        insta::assert_debug_snapshot!(optimize(parse("heads(ancestors(foo--))").unwrap()), @r#"
6169        HeadsRange {
6170            roots: None,
6171            heads: Ancestors {
6172                heads: CommitRef(Symbol("foo")),
6173                generation: 2..3,
6174                parents_range: 0..4294967295,
6175            },
6176            parents_range: 0..4294967295,
6177            filter: All,
6178        }
6179        "#);
6180        insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(first_parent(foo, 2)))").unwrap()), @r#"
6181        HeadsRange {
6182            roots: None,
6183            heads: Ancestors {
6184                heads: CommitRef(Symbol("foo")),
6185                generation: 2..3,
6186                parents_range: 0..1,
6187            },
6188            parents_range: 0..1,
6189            filter: All,
6190        }
6191        "#);
6192
6193        // Heads of filters and negations can be folded.
6194        insta::assert_debug_snapshot!(optimize(parse("heads(author_name(A) | author_name(B))").unwrap()), @r#"
6195        HeadsRange {
6196            roots: None,
6197            heads: VisibleHeadsOrReferenced,
6198            parents_range: 0..4294967295,
6199            filter: AsFilter(
6200                Union(
6201                    Filter(AuthorName(Pattern(Exact("A")))),
6202                    Filter(AuthorName(Pattern(Exact("B")))),
6203                ),
6204            ),
6205        }
6206        "#);
6207        insta::assert_debug_snapshot!(optimize(parse("heads(~author_name(A))").unwrap()), @r#"
6208        HeadsRange {
6209            roots: None,
6210            heads: VisibleHeadsOrReferenced,
6211            parents_range: 0..4294967295,
6212            filter: AsFilter(
6213                NotIn(
6214                    Filter(AuthorName(Pattern(Exact("A")))),
6215                ),
6216            ),
6217        }
6218        "#);
6219        insta::assert_debug_snapshot!(optimize(parse("heads(~foo)").unwrap()), @r#"
6220        HeadsRange {
6221            roots: None,
6222            heads: VisibleHeadsOrReferenced,
6223            parents_range: 0..4294967295,
6224            filter: NotIn(CommitRef(Symbol("foo"))),
6225        }
6226        "#);
6227
6228        // Heads of intersections with filters can be folded.
6229        insta::assert_debug_snapshot!(optimize(parse("heads(author_name(A) & ::foo ~ author_name(B))").unwrap()), @r#"
6230        HeadsRange {
6231            roots: None,
6232            heads: CommitRef(Symbol("foo")),
6233            parents_range: 0..4294967295,
6234            filter: AsFilter(
6235                Difference(
6236                    Filter(AuthorName(Pattern(Exact("A")))),
6237                    Filter(AuthorName(Pattern(Exact("B")))),
6238                ),
6239            ),
6240        }
6241        "#);
6242
6243        // Heads of intersections with negations can be folded.
6244        insta::assert_debug_snapshot!(optimize(parse("heads(~foo & ~roots(bar) & ::baz)").unwrap()), @r#"
6245        HeadsRange {
6246            roots: None,
6247            heads: CommitRef(Symbol("baz")),
6248            parents_range: 0..4294967295,
6249            filter: Difference(
6250                NotIn(CommitRef(Symbol("foo"))),
6251                Roots(CommitRef(Symbol("bar"))),
6252            ),
6253        }
6254        "#);
6255    }
6256
6257    #[test]
6258    fn test_optimize_ancestors_heads_range() {
6259        // Can use heads range to optimize ancestors of filter.
6260        insta::assert_debug_snapshot!(optimize(parse("::description(bar)").unwrap()), @r#"
6261        Ancestors {
6262            heads: HeadsRange {
6263                roots: None,
6264                heads: VisibleHeadsOrReferenced,
6265                parents_range: 0..4294967295,
6266                filter: Filter(
6267                    Description(
6268                        Pattern(
6269                            Exact(
6270                                "bar",
6271                            ),
6272                        ),
6273                    ),
6274                ),
6275            },
6276            generation: 0..18446744073709551615,
6277            parents_range: 0..4294967295,
6278        }
6279        "#);
6280        insta::assert_debug_snapshot!(optimize(parse("author_name(foo)..").unwrap()), @r#"
6281        Range {
6282            roots: HeadsRange {
6283                roots: None,
6284                heads: VisibleHeadsOrReferenced,
6285                parents_range: 0..4294967295,
6286                filter: Filter(
6287                    AuthorName(
6288                        Pattern(
6289                            Exact(
6290                                "foo",
6291                            ),
6292                        ),
6293                    ),
6294                ),
6295            },
6296            heads: VisibleHeadsOrReferenced,
6297            generation: 0..18446744073709551615,
6298            parents_range: 0..4294967295,
6299        }
6300        "#);
6301
6302        // Can use heads range to optimize ancestors of range.
6303        insta::assert_debug_snapshot!(optimize(parse("::(foo..bar)").unwrap()), @r#"
6304        Ancestors {
6305            heads: HeadsRange {
6306                roots: CommitRef(
6307                    Symbol(
6308                        "foo",
6309                    ),
6310                ),
6311                heads: CommitRef(
6312                    Symbol(
6313                        "bar",
6314                    ),
6315                ),
6316                parents_range: 0..4294967295,
6317                filter: All,
6318            },
6319            generation: 0..18446744073709551615,
6320            parents_range: 0..4294967295,
6321        }
6322        "#);
6323
6324        // Can't optimize if not using full generation and parents ranges.
6325        insta::assert_debug_snapshot!(optimize(parse("ancestors(author_name(foo), 5)").unwrap()), @r#"
6326        Ancestors {
6327            heads: Filter(
6328                AuthorName(
6329                    Pattern(
6330                        Exact(
6331                            "foo",
6332                        ),
6333                    ),
6334                ),
6335            ),
6336            generation: 0..5,
6337            parents_range: 0..4294967295,
6338        }
6339        "#);
6340        insta::assert_debug_snapshot!(optimize(parse("first_ancestors(author_name(foo))").unwrap()), @r#"
6341        Ancestors {
6342            heads: Filter(
6343                AuthorName(
6344                    Pattern(
6345                        Exact(
6346                            "foo",
6347                        ),
6348                    ),
6349                ),
6350            ),
6351            generation: 0..18446744073709551615,
6352            parents_range: 0..1,
6353        }
6354        "#);
6355    }
6356
6357    #[test]
6358    fn test_escape_string_literal() {
6359        // Valid identifiers don't need quoting
6360        assert_eq!(format_symbol("foo"), "foo");
6361        assert_eq!(format_symbol("foo.bar"), "foo.bar");
6362
6363        // Invalid identifiers need quoting
6364        assert_eq!(format_symbol("foo@bar"), r#""foo@bar""#);
6365        assert_eq!(format_symbol("foo bar"), r#""foo bar""#);
6366        assert_eq!(format_symbol(" foo "), r#"" foo ""#);
6367        assert_eq!(format_symbol("(foo)"), r#""(foo)""#);
6368        assert_eq!(format_symbol("all:foo"), r#""all:foo""#);
6369
6370        // Some characters also need escaping
6371        assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
6372        assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
6373        assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
6374        assert_eq!(format_symbol("foo\nbar"), r#""foo\nbar""#);
6375
6376        // Some characters don't technically need escaping, but we escape them for
6377        // clarity
6378        assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
6379        assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
6380        assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
6381        assert_eq!(format_symbol("foo \x01 bar"), r#""foo \x01 bar""#);
6382    }
6383
6384    #[test]
6385    fn test_escape_remote_symbol() {
6386        assert_eq!(format_remote_symbol("foo", "bar"), "foo@bar");
6387        assert_eq!(
6388            format_remote_symbol(" foo ", "bar:baz"),
6389            r#"" foo "@"bar:baz""#
6390        );
6391    }
6392}