Skip to main content

jj_lib/
revset.rs

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