jj_lib/
revset.rs

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