Skip to main content

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