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#![allow(missing_docs)]
16
17use std::any::Any;
18use std::collections::hash_map;
19use std::collections::HashMap;
20use std::convert::Infallible;
21use std::fmt;
22use std::ops::Range;
23use std::rc::Rc;
24use std::sync::Arc;
25
26use itertools::Itertools as _;
27use once_cell::sync::Lazy;
28use thiserror::Error;
29
30use crate::backend::BackendError;
31use crate::backend::ChangeId;
32use crate::backend::CommitId;
33use crate::commit::Commit;
34use crate::dsl_util;
35use crate::dsl_util::collect_similar;
36use crate::dsl_util::AliasExpandError as _;
37use crate::fileset;
38use crate::fileset::FilesetDiagnostics;
39use crate::fileset::FilesetExpression;
40use crate::graph::GraphNode;
41use crate::hex_util::to_forward_hex;
42use crate::id_prefix::IdPrefixContext;
43use crate::id_prefix::IdPrefixIndex;
44use crate::object_id::HexPrefix;
45use crate::object_id::PrefixResolution;
46use crate::op_store::RemoteRefState;
47use crate::op_walk;
48use crate::ref_name::RemoteRefSymbol;
49use crate::ref_name::RemoteRefSymbolBuf;
50use crate::ref_name::WorkspaceName;
51use crate::ref_name::WorkspaceNameBuf;
52use crate::repo::ReadonlyRepo;
53use crate::repo::Repo;
54use crate::repo::RepoLoaderError;
55use crate::repo_path::RepoPathUiConverter;
56use crate::revset_parser;
57pub use crate::revset_parser::expect_literal;
58pub use crate::revset_parser::parse_program;
59pub use crate::revset_parser::parse_symbol;
60pub use crate::revset_parser::BinaryOp;
61pub use crate::revset_parser::ExpressionKind;
62pub use crate::revset_parser::ExpressionNode;
63pub use crate::revset_parser::FunctionCallNode;
64pub use crate::revset_parser::RevsetAliasesMap;
65pub use crate::revset_parser::RevsetDiagnostics;
66pub use crate::revset_parser::RevsetParseError;
67pub use crate::revset_parser::RevsetParseErrorKind;
68pub use crate::revset_parser::UnaryOp;
69use crate::store::Store;
70use crate::str_util::StringPattern;
71use crate::time_util::DatePattern;
72use crate::time_util::DatePatternContext;
73
74/// Error occurred during symbol resolution.
75#[derive(Debug, Error)]
76pub enum RevsetResolutionError {
77    #[error("Revision `{name}` doesn't exist")]
78    NoSuchRevision {
79        name: String,
80        candidates: Vec<String>,
81    },
82    #[error("Workspace `{}` doesn't have a working-copy commit", name.as_symbol())]
83    WorkspaceMissingWorkingCopy { name: WorkspaceNameBuf },
84    #[error("An empty string is not a valid revision")]
85    EmptyString,
86    #[error("Commit ID prefix `{0}` is ambiguous")]
87    AmbiguousCommitIdPrefix(String),
88    #[error("Change ID prefix `{0}` is ambiguous")]
89    AmbiguousChangeIdPrefix(String),
90    #[error("Unexpected error from commit backend")]
91    Backend(#[source] BackendError),
92    #[error(transparent)]
93    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
94}
95
96/// Error occurred during revset evaluation.
97#[derive(Debug, Error)]
98pub enum RevsetEvaluationError {
99    #[error("Unexpected error from commit backend")]
100    Backend(#[from] BackendError),
101    #[error(transparent)]
102    Other(Box<dyn std::error::Error + Send + Sync>),
103}
104
105impl RevsetEvaluationError {
106    // TODO: Create a higher-level error instead of putting non-BackendErrors in a
107    // BackendError
108    pub fn into_backend_error(self) -> BackendError {
109        match self {
110            Self::Backend(err) => err,
111            Self::Other(err) => BackendError::Other(err),
112        }
113    }
114}
115
116// assumes index has less than u64::MAX entries.
117pub const GENERATION_RANGE_FULL: Range<u64> = 0..u64::MAX;
118pub const GENERATION_RANGE_EMPTY: Range<u64> = 0..0;
119
120/// Global flag applied to the entire expression.
121///
122/// The core revset engine doesn't use this value. It's up to caller to
123/// interpret it to change the evaluation behavior.
124#[derive(Clone, Copy, Debug, Eq, PartialEq)]
125pub enum RevsetModifier {
126    /// Expression can be evaluated to multiple revisions even if a single
127    /// revision is expected by default.
128    All,
129}
130
131/// Symbol or function to be resolved to `CommitId`s.
132#[derive(Clone, Debug)]
133pub enum RevsetCommitRef {
134    WorkingCopy(WorkspaceNameBuf),
135    WorkingCopies,
136    Symbol(String),
137    RemoteSymbol(RemoteRefSymbolBuf),
138    Bookmarks(StringPattern),
139    RemoteBookmarks {
140        bookmark_pattern: StringPattern,
141        remote_pattern: StringPattern,
142        remote_ref_state: Option<RemoteRefState>,
143    },
144    Tags(StringPattern),
145    GitRefs,
146    GitHead,
147}
148
149/// A custom revset filter expression, defined by an extension.
150pub trait RevsetFilterExtension: std::fmt::Debug + Any {
151    fn as_any(&self) -> &dyn Any;
152
153    /// Returns true iff this filter matches the specified commit.
154    fn matches_commit(&self, commit: &Commit) -> bool;
155}
156
157#[derive(Clone, Debug)]
158pub enum RevsetFilterPredicate {
159    /// Commits with number of parents in the range.
160    ParentCount(Range<u32>),
161    /// Commits with description matching the pattern.
162    Description(StringPattern),
163    /// Commits with first line of the description matching the pattern.
164    Subject(StringPattern),
165    /// Commits with author name matching the pattern.
166    AuthorName(StringPattern),
167    /// Commits with author email matching the pattern.
168    AuthorEmail(StringPattern),
169    /// Commits with author dates matching the given date pattern.
170    AuthorDate(DatePattern),
171    /// Commits with committer name matching the pattern.
172    CommitterName(StringPattern),
173    /// Commits with committer email matching the pattern.
174    CommitterEmail(StringPattern),
175    /// Commits with committer dates matching the given date pattern.
176    CommitterDate(DatePattern),
177    /// Commits modifying the paths specified by the fileset.
178    File(FilesetExpression),
179    /// Commits containing diffs matching the `text` pattern within the `files`.
180    DiffContains {
181        text: StringPattern,
182        files: FilesetExpression,
183    },
184    /// Commits with conflicts
185    HasConflict,
186    /// Commits that are cryptographically signed.
187    Signed,
188    /// Custom predicates provided by extensions
189    Extension(Rc<dyn RevsetFilterExtension>),
190}
191
192mod private {
193    /// Defines [`RevsetExpression`] variants depending on resolution state.
194    pub trait ExpressionState {
195        type CommitRef: Clone;
196        type Operation: Clone;
197    }
198
199    // Not constructible because these state types just define associated types.
200    #[derive(Debug)]
201    pub enum UserExpressionState {}
202    #[derive(Debug)]
203    pub enum ResolvedExpressionState {}
204}
205
206use private::ExpressionState;
207use private::ResolvedExpressionState;
208use private::UserExpressionState;
209
210impl ExpressionState for UserExpressionState {
211    type CommitRef = RevsetCommitRef;
212    type Operation = String;
213}
214
215impl ExpressionState for ResolvedExpressionState {
216    type CommitRef = Infallible;
217    type Operation = Infallible;
218}
219
220/// [`RevsetExpression`] that may contain unresolved commit refs.
221pub type UserRevsetExpression = RevsetExpression<UserExpressionState>;
222/// [`RevsetExpression`] that never contains unresolved commit refs.
223pub type ResolvedRevsetExpression = RevsetExpression<ResolvedExpressionState>;
224
225/// Tree of revset expressions describing DAG operations.
226///
227/// Use [`UserRevsetExpression`] or [`ResolvedRevsetExpression`] to construct
228/// expression of that state.
229#[derive(Clone, Debug)]
230pub enum RevsetExpression<St: ExpressionState> {
231    None,
232    All,
233    VisibleHeads,
234    Root,
235    Commits(Vec<CommitId>),
236    CommitRef(St::CommitRef),
237    Ancestors {
238        heads: Rc<Self>,
239        generation: Range<u64>,
240    },
241    Descendants {
242        roots: Rc<Self>,
243        generation: Range<u64>,
244    },
245    // Commits that are ancestors of "heads" but not ancestors of "roots"
246    Range {
247        roots: Rc<Self>,
248        heads: Rc<Self>,
249        generation: Range<u64>,
250    },
251    // Commits that are descendants of "roots" and ancestors of "heads"
252    DagRange {
253        roots: Rc<Self>,
254        heads: Rc<Self>,
255        // TODO: maybe add generation_from_roots/heads?
256    },
257    // Commits reachable from "sources" within "domain"
258    Reachable {
259        sources: Rc<Self>,
260        domain: Rc<Self>,
261    },
262    Heads(Rc<Self>),
263    Roots(Rc<Self>),
264    ForkPoint(Rc<Self>),
265    Latest {
266        candidates: Rc<Self>,
267        count: usize,
268    },
269    Filter(RevsetFilterPredicate),
270    /// Marker for subtree that should be intersected as filter.
271    AsFilter(Rc<Self>),
272    /// Resolves symbols and visibility at the specified operation.
273    AtOperation {
274        operation: St::Operation,
275        candidates: Rc<Self>,
276    },
277    /// Resolves visibility within the specified repo state.
278    WithinVisibility {
279        candidates: Rc<Self>,
280        /// Copy of `repo.view().heads()` at the operation.
281        visible_heads: Vec<CommitId>,
282    },
283    Coalesce(Rc<Self>, Rc<Self>),
284    Present(Rc<Self>),
285    NotIn(Rc<Self>),
286    Union(Rc<Self>, Rc<Self>),
287    Intersection(Rc<Self>, Rc<Self>),
288    Difference(Rc<Self>, Rc<Self>),
289}
290
291// Leaf expression that never contains unresolved commit refs, which can be
292// either user or resolved expression
293impl<St: ExpressionState> RevsetExpression<St> {
294    pub fn none() -> Rc<Self> {
295        Rc::new(Self::None)
296    }
297
298    pub fn all() -> Rc<Self> {
299        Rc::new(Self::All)
300    }
301
302    pub fn visible_heads() -> Rc<Self> {
303        Rc::new(Self::VisibleHeads)
304    }
305
306    pub fn root() -> Rc<Self> {
307        Rc::new(Self::Root)
308    }
309
310    pub fn commit(commit_id: CommitId) -> Rc<Self> {
311        Self::commits(vec![commit_id])
312    }
313
314    pub fn commits(commit_ids: Vec<CommitId>) -> Rc<Self> {
315        Rc::new(Self::Commits(commit_ids))
316    }
317
318    pub fn filter(predicate: RevsetFilterPredicate) -> Rc<Self> {
319        Rc::new(Self::Filter(predicate))
320    }
321
322    /// Find any empty commits.
323    pub fn is_empty() -> Rc<Self> {
324        Self::filter(RevsetFilterPredicate::File(FilesetExpression::all())).negated()
325    }
326}
327
328// Leaf expression that represents unresolved commit refs
329impl<St: ExpressionState<CommitRef = RevsetCommitRef>> RevsetExpression<St> {
330    pub fn working_copy(name: WorkspaceNameBuf) -> Rc<Self> {
331        Rc::new(Self::CommitRef(RevsetCommitRef::WorkingCopy(name)))
332    }
333
334    pub fn working_copies() -> Rc<Self> {
335        Rc::new(Self::CommitRef(RevsetCommitRef::WorkingCopies))
336    }
337
338    pub fn symbol(value: String) -> Rc<Self> {
339        Rc::new(Self::CommitRef(RevsetCommitRef::Symbol(value)))
340    }
341
342    pub fn remote_symbol(value: RemoteRefSymbolBuf) -> Rc<Self> {
343        let commit_ref = RevsetCommitRef::RemoteSymbol(value);
344        Rc::new(Self::CommitRef(commit_ref))
345    }
346
347    pub fn bookmarks(pattern: StringPattern) -> Rc<Self> {
348        Rc::new(Self::CommitRef(RevsetCommitRef::Bookmarks(pattern)))
349    }
350
351    pub fn remote_bookmarks(
352        bookmark_pattern: StringPattern,
353        remote_pattern: StringPattern,
354        remote_ref_state: Option<RemoteRefState>,
355    ) -> Rc<Self> {
356        Rc::new(Self::CommitRef(RevsetCommitRef::RemoteBookmarks {
357            bookmark_pattern,
358            remote_pattern,
359            remote_ref_state,
360        }))
361    }
362
363    pub fn tags(pattern: StringPattern) -> Rc<Self> {
364        Rc::new(Self::CommitRef(RevsetCommitRef::Tags(pattern)))
365    }
366
367    pub fn git_refs() -> Rc<Self> {
368        Rc::new(Self::CommitRef(RevsetCommitRef::GitRefs))
369    }
370
371    pub fn git_head() -> Rc<Self> {
372        Rc::new(Self::CommitRef(RevsetCommitRef::GitHead))
373    }
374}
375
376// Compound expression
377impl<St: ExpressionState> RevsetExpression<St> {
378    pub fn latest(self: &Rc<Self>, count: usize) -> Rc<Self> {
379        Rc::new(Self::Latest {
380            candidates: self.clone(),
381            count,
382        })
383    }
384
385    /// Commits in `self` that don't have descendants in `self`.
386    pub fn heads(self: &Rc<Self>) -> Rc<Self> {
387        Rc::new(Self::Heads(self.clone()))
388    }
389
390    /// Commits in `self` that don't have ancestors in `self`.
391    pub fn roots(self: &Rc<Self>) -> Rc<Self> {
392        Rc::new(Self::Roots(self.clone()))
393    }
394
395    /// Parents of `self`.
396    pub fn parents(self: &Rc<Self>) -> Rc<Self> {
397        self.ancestors_at(1)
398    }
399
400    /// Ancestors of `self`, including `self`.
401    pub fn ancestors(self: &Rc<Self>) -> Rc<Self> {
402        self.ancestors_range(GENERATION_RANGE_FULL)
403    }
404
405    /// Ancestors of `self` at an offset of `generation` behind `self`.
406    /// The `generation` offset is zero-based starting from `self`.
407    pub fn ancestors_at(self: &Rc<Self>, generation: u64) -> Rc<Self> {
408        self.ancestors_range(generation..(generation + 1))
409    }
410
411    /// Ancestors of `self` in the given range.
412    pub fn ancestors_range(self: &Rc<Self>, generation_range: Range<u64>) -> Rc<Self> {
413        Rc::new(Self::Ancestors {
414            heads: self.clone(),
415            generation: generation_range,
416        })
417    }
418
419    /// Children of `self`.
420    pub fn children(self: &Rc<Self>) -> Rc<Self> {
421        self.descendants_at(1)
422    }
423
424    /// Descendants of `self`, including `self`.
425    pub fn descendants(self: &Rc<Self>) -> Rc<Self> {
426        self.descendants_range(GENERATION_RANGE_FULL)
427    }
428
429    /// Descendants of `self` at an offset of `generation` ahead of `self`.
430    /// The `generation` offset is zero-based starting from `self`.
431    pub fn descendants_at(self: &Rc<Self>, generation: u64) -> Rc<Self> {
432        self.descendants_range(generation..(generation + 1))
433    }
434
435    /// Descendants of `self` in the given range.
436    pub fn descendants_range(self: &Rc<Self>, generation_range: Range<u64>) -> Rc<Self> {
437        Rc::new(Self::Descendants {
438            roots: self.clone(),
439            generation: generation_range,
440        })
441    }
442
443    /// Fork point (best common ancestors) of `self`.
444    pub fn fork_point(self: &Rc<Self>) -> Rc<Self> {
445        Rc::new(Self::ForkPoint(self.clone()))
446    }
447
448    /// Filter all commits by `predicate` in `self`.
449    pub fn filtered(self: &Rc<Self>, predicate: RevsetFilterPredicate) -> Rc<Self> {
450        self.intersection(&Self::filter(predicate))
451    }
452
453    /// Commits that are descendants of `self` and ancestors of `heads`, both
454    /// inclusive.
455    pub fn dag_range_to(self: &Rc<Self>, heads: &Rc<Self>) -> Rc<Self> {
456        Rc::new(Self::DagRange {
457            roots: self.clone(),
458            heads: heads.clone(),
459        })
460    }
461
462    /// Connects any ancestors and descendants in the set by adding the commits
463    /// between them.
464    pub fn connected(self: &Rc<Self>) -> Rc<Self> {
465        self.dag_range_to(self)
466    }
467
468    /// All commits within `domain` reachable from this set of commits, by
469    /// traversing either parent or child edges.
470    pub fn reachable(self: &Rc<Self>, domain: &Rc<Self>) -> Rc<Self> {
471        Rc::new(Self::Reachable {
472            sources: self.clone(),
473            domain: domain.clone(),
474        })
475    }
476
477    /// Commits reachable from `heads` but not from `self`.
478    pub fn range(self: &Rc<Self>, heads: &Rc<Self>) -> Rc<Self> {
479        Rc::new(Self::Range {
480            roots: self.clone(),
481            heads: heads.clone(),
482            generation: GENERATION_RANGE_FULL,
483        })
484    }
485
486    /// Suppresses name resolution error within `self`.
487    pub fn present(self: &Rc<Self>) -> Rc<Self> {
488        Rc::new(Self::Present(self.clone()))
489    }
490
491    /// Commits that are not in `self`, i.e. the complement of `self`.
492    pub fn negated(self: &Rc<Self>) -> Rc<Self> {
493        Rc::new(Self::NotIn(self.clone()))
494    }
495
496    /// Commits that are in `self` or in `other` (or both).
497    pub fn union(self: &Rc<Self>, other: &Rc<Self>) -> Rc<Self> {
498        Rc::new(Self::Union(self.clone(), other.clone()))
499    }
500
501    /// Commits that are in any of the `expressions`.
502    pub fn union_all(expressions: &[Rc<Self>]) -> Rc<Self> {
503        match expressions {
504            [] => Self::none(),
505            [expression] => expression.clone(),
506            _ => {
507                // Build balanced tree to minimize the recursion depth.
508                let (left, right) = expressions.split_at(expressions.len() / 2);
509                Self::union(&Self::union_all(left), &Self::union_all(right))
510            }
511        }
512    }
513
514    /// Commits that are in `self` and in `other`.
515    pub fn intersection(self: &Rc<Self>, other: &Rc<Self>) -> Rc<Self> {
516        Rc::new(Self::Intersection(self.clone(), other.clone()))
517    }
518
519    /// Commits that are in `self` but not in `other`.
520    pub fn minus(self: &Rc<Self>, other: &Rc<Self>) -> Rc<Self> {
521        Rc::new(Self::Difference(self.clone(), other.clone()))
522    }
523
524    /// Commits that are in the first expression in `expressions` that is not
525    /// `none()`.
526    pub fn coalesce(expressions: &[Rc<Self>]) -> Rc<Self> {
527        match expressions {
528            [] => Self::none(),
529            [expression] => expression.clone(),
530            _ => {
531                // Build balanced tree to minimize the recursion depth.
532                let (left, right) = expressions.split_at(expressions.len() / 2);
533                Rc::new(Self::Coalesce(Self::coalesce(left), Self::coalesce(right)))
534            }
535        }
536    }
537}
538
539impl<St: ExpressionState<CommitRef = RevsetCommitRef>> RevsetExpression<St> {
540    /// Returns symbol string if this expression is of that type.
541    pub fn as_symbol(&self) -> Option<&str> {
542        match self {
543            RevsetExpression::CommitRef(RevsetCommitRef::Symbol(name)) => Some(name),
544            _ => None,
545        }
546    }
547}
548
549impl UserRevsetExpression {
550    /// Resolve a user-provided expression. Symbols will be resolved using the
551    /// provided `SymbolResolver`.
552    pub fn resolve_user_expression(
553        &self,
554        repo: &dyn Repo,
555        symbol_resolver: &dyn SymbolResolver,
556    ) -> Result<Rc<ResolvedRevsetExpression>, RevsetResolutionError> {
557        resolve_symbols(repo, self, symbol_resolver)
558    }
559}
560
561impl ResolvedRevsetExpression {
562    /// Optimizes and evaluates this expression.
563    pub fn evaluate<'index>(
564        self: Rc<Self>,
565        repo: &'index dyn Repo,
566    ) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
567        optimize(self).evaluate_unoptimized(repo)
568    }
569
570    /// Evaluates this expression without optimizing it.
571    ///
572    /// Use this function if `self` is already optimized, or to debug
573    /// optimization pass.
574    pub fn evaluate_unoptimized<'index>(
575        &self,
576        repo: &'index dyn Repo,
577    ) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
578        let expr = self.to_backend_expression(repo);
579        repo.index().evaluate_revset(&expr, repo.store())
580    }
581
582    /// Transforms this expression to the form which the `Index` backend will
583    /// process.
584    pub fn to_backend_expression(&self, repo: &dyn Repo) -> ResolvedExpression {
585        resolve_visibility(repo, self)
586    }
587}
588
589#[derive(Clone, Debug)]
590pub enum ResolvedPredicateExpression {
591    /// Pure filter predicate.
592    Filter(RevsetFilterPredicate),
593    /// Set expression to be evaluated as filter. This is typically a subtree
594    /// node of `Union` with a pure filter predicate.
595    Set(Box<ResolvedExpression>),
596    NotIn(Box<ResolvedPredicateExpression>),
597    Union(
598        Box<ResolvedPredicateExpression>,
599        Box<ResolvedPredicateExpression>,
600    ),
601}
602
603/// Describes evaluation plan of revset expression.
604///
605/// Unlike `RevsetExpression`, this doesn't contain unresolved symbols or `View`
606/// properties.
607///
608/// Use `RevsetExpression` API to build a query programmatically.
609// TODO: rename to BackendExpression?
610#[derive(Clone, Debug)]
611pub enum ResolvedExpression {
612    Commits(Vec<CommitId>),
613    Ancestors {
614        heads: Box<Self>,
615        generation: Range<u64>,
616    },
617    /// Commits that are ancestors of `heads` but not ancestors of `roots`.
618    Range {
619        roots: Box<Self>,
620        heads: Box<Self>,
621        generation: Range<u64>,
622    },
623    /// Commits that are descendants of `roots` and ancestors of `heads`.
624    DagRange {
625        roots: Box<Self>,
626        heads: Box<Self>,
627        generation_from_roots: Range<u64>,
628    },
629    /// Commits reachable from `sources` within `domain`.
630    Reachable {
631        sources: Box<Self>,
632        domain: Box<Self>,
633    },
634    Heads(Box<Self>),
635    Roots(Box<Self>),
636    ForkPoint(Box<Self>),
637    Latest {
638        candidates: Box<Self>,
639        count: usize,
640    },
641    Coalesce(Box<Self>, Box<Self>),
642    Union(Box<Self>, Box<Self>),
643    /// Intersects `candidates` with `predicate` by filtering.
644    FilterWithin {
645        candidates: Box<Self>,
646        predicate: ResolvedPredicateExpression,
647    },
648    /// Intersects expressions by merging.
649    Intersection(Box<Self>, Box<Self>),
650    Difference(Box<Self>, Box<Self>),
651}
652
653pub type RevsetFunction = fn(
654    &mut RevsetDiagnostics,
655    &FunctionCallNode,
656    &LoweringContext,
657) -> Result<Rc<UserRevsetExpression>, RevsetParseError>;
658
659static BUILTIN_FUNCTION_MAP: Lazy<HashMap<&'static str, RevsetFunction>> = Lazy::new(|| {
660    // Not using maplit::hashmap!{} or custom declarative macro here because
661    // code completion inside macro is quite restricted.
662    let mut map: HashMap<&'static str, RevsetFunction> = HashMap::new();
663    map.insert("parents", |diagnostics, function, context| {
664        let [arg] = function.expect_exact_arguments()?;
665        let expression = lower_expression(diagnostics, arg, context)?;
666        Ok(expression.parents())
667    });
668    map.insert("children", |diagnostics, function, context| {
669        let [arg] = function.expect_exact_arguments()?;
670        let expression = lower_expression(diagnostics, arg, context)?;
671        Ok(expression.children())
672    });
673    map.insert("ancestors", |diagnostics, function, context| {
674        let ([heads_arg], [depth_opt_arg]) = function.expect_arguments()?;
675        let heads = lower_expression(diagnostics, heads_arg, context)?;
676        let generation = if let Some(depth_arg) = depth_opt_arg {
677            let depth = expect_literal(diagnostics, "integer", depth_arg)?;
678            0..depth
679        } else {
680            GENERATION_RANGE_FULL
681        };
682        Ok(heads.ancestors_range(generation))
683    });
684    map.insert("descendants", |diagnostics, function, context| {
685        let ([roots_arg], [depth_opt_arg]) = function.expect_arguments()?;
686        let roots = lower_expression(diagnostics, roots_arg, context)?;
687        let generation = if let Some(depth_arg) = depth_opt_arg {
688            let depth = expect_literal(diagnostics, "integer", depth_arg)?;
689            0..depth
690        } else {
691            GENERATION_RANGE_FULL
692        };
693        Ok(roots.descendants_range(generation))
694    });
695    map.insert("connected", |diagnostics, function, context| {
696        let [arg] = function.expect_exact_arguments()?;
697        let candidates = lower_expression(diagnostics, arg, context)?;
698        Ok(candidates.connected())
699    });
700    map.insert("reachable", |diagnostics, function, context| {
701        let [source_arg, domain_arg] = function.expect_exact_arguments()?;
702        let sources = lower_expression(diagnostics, source_arg, context)?;
703        let domain = lower_expression(diagnostics, domain_arg, context)?;
704        Ok(sources.reachable(&domain))
705    });
706    map.insert("none", |_diagnostics, function, _context| {
707        function.expect_no_arguments()?;
708        Ok(RevsetExpression::none())
709    });
710    map.insert("all", |_diagnostics, function, _context| {
711        function.expect_no_arguments()?;
712        Ok(RevsetExpression::all())
713    });
714    map.insert("working_copies", |_diagnostics, function, _context| {
715        function.expect_no_arguments()?;
716        Ok(RevsetExpression::working_copies())
717    });
718    map.insert("heads", |diagnostics, function, context| {
719        let [arg] = function.expect_exact_arguments()?;
720        let candidates = lower_expression(diagnostics, arg, context)?;
721        Ok(candidates.heads())
722    });
723    map.insert("roots", |diagnostics, function, context| {
724        let [arg] = function.expect_exact_arguments()?;
725        let candidates = lower_expression(diagnostics, arg, context)?;
726        Ok(candidates.roots())
727    });
728    map.insert("visible_heads", |_diagnostics, function, _context| {
729        function.expect_no_arguments()?;
730        Ok(RevsetExpression::visible_heads())
731    });
732    map.insert("root", |_diagnostics, function, _context| {
733        function.expect_no_arguments()?;
734        Ok(RevsetExpression::root())
735    });
736    map.insert("bookmarks", |diagnostics, function, _context| {
737        let ([], [opt_arg]) = function.expect_arguments()?;
738        let pattern = if let Some(arg) = opt_arg {
739            expect_string_pattern(diagnostics, arg)?
740        } else {
741            StringPattern::everything()
742        };
743        Ok(RevsetExpression::bookmarks(pattern))
744    });
745    map.insert("remote_bookmarks", |diagnostics, function, _context| {
746        parse_remote_bookmarks_arguments(diagnostics, function, None)
747    });
748    map.insert(
749        "tracked_remote_bookmarks",
750        |diagnostics, function, _context| {
751            parse_remote_bookmarks_arguments(diagnostics, function, Some(RemoteRefState::Tracked))
752        },
753    );
754    map.insert(
755        "untracked_remote_bookmarks",
756        |diagnostics, function, _context| {
757            parse_remote_bookmarks_arguments(diagnostics, function, Some(RemoteRefState::New))
758        },
759    );
760    map.insert("tags", |diagnostics, function, _context| {
761        let ([], [opt_arg]) = function.expect_arguments()?;
762        let pattern = if let Some(arg) = opt_arg {
763            expect_string_pattern(diagnostics, arg)?
764        } else {
765            StringPattern::everything()
766        };
767        Ok(RevsetExpression::tags(pattern))
768    });
769    map.insert("git_refs", |_diagnostics, function, _context| {
770        function.expect_no_arguments()?;
771        Ok(RevsetExpression::git_refs())
772    });
773    map.insert("git_head", |_diagnostics, function, _context| {
774        function.expect_no_arguments()?;
775        Ok(RevsetExpression::git_head())
776    });
777    map.insert("latest", |diagnostics, function, context| {
778        let ([candidates_arg], [count_opt_arg]) = function.expect_arguments()?;
779        let candidates = lower_expression(diagnostics, candidates_arg, context)?;
780        let count = if let Some(count_arg) = count_opt_arg {
781            expect_literal(diagnostics, "integer", count_arg)?
782        } else {
783            1
784        };
785        Ok(candidates.latest(count))
786    });
787    map.insert("fork_point", |diagnostics, function, context| {
788        let [expression_arg] = function.expect_exact_arguments()?;
789        let expression = lower_expression(diagnostics, expression_arg, context)?;
790        Ok(RevsetExpression::fork_point(&expression))
791    });
792    map.insert("merges", |_diagnostics, function, _context| {
793        function.expect_no_arguments()?;
794        Ok(RevsetExpression::filter(
795            RevsetFilterPredicate::ParentCount(2..u32::MAX),
796        ))
797    });
798    map.insert("description", |diagnostics, function, _context| {
799        let [arg] = function.expect_exact_arguments()?;
800        let pattern = expect_string_pattern(diagnostics, arg)?;
801        Ok(RevsetExpression::filter(
802            RevsetFilterPredicate::Description(pattern),
803        ))
804    });
805    map.insert("subject", |diagnostics, function, _context| {
806        let [arg] = function.expect_exact_arguments()?;
807        let pattern = expect_string_pattern(diagnostics, arg)?;
808        let predicate = RevsetFilterPredicate::Subject(pattern);
809        Ok(RevsetExpression::filter(predicate))
810    });
811    map.insert("author", |diagnostics, function, _context| {
812        let [arg] = function.expect_exact_arguments()?;
813        let pattern = expect_string_pattern(diagnostics, arg)?;
814        let name_predicate = RevsetFilterPredicate::AuthorName(pattern.clone());
815        let email_predicate = RevsetFilterPredicate::AuthorEmail(pattern);
816        Ok(RevsetExpression::filter(name_predicate)
817            .union(&RevsetExpression::filter(email_predicate)))
818    });
819    map.insert("author_name", |diagnostics, function, _context| {
820        let [arg] = function.expect_exact_arguments()?;
821        let pattern = expect_string_pattern(diagnostics, arg)?;
822        let predicate = RevsetFilterPredicate::AuthorName(pattern);
823        Ok(RevsetExpression::filter(predicate))
824    });
825    map.insert("author_email", |diagnostics, function, _context| {
826        let [arg] = function.expect_exact_arguments()?;
827        let pattern = expect_string_pattern(diagnostics, arg)?;
828        let predicate = RevsetFilterPredicate::AuthorEmail(pattern);
829        Ok(RevsetExpression::filter(predicate))
830    });
831    map.insert("author_date", |diagnostics, function, context| {
832        let [arg] = function.expect_exact_arguments()?;
833        let pattern = expect_date_pattern(diagnostics, arg, context.date_pattern_context())?;
834        Ok(RevsetExpression::filter(RevsetFilterPredicate::AuthorDate(
835            pattern,
836        )))
837    });
838    map.insert("signed", |_diagnostics, function, _context| {
839        function.expect_no_arguments()?;
840        let predicate = RevsetFilterPredicate::Signed;
841        Ok(RevsetExpression::filter(predicate))
842    });
843    map.insert("mine", |_diagnostics, function, context| {
844        function.expect_no_arguments()?;
845        // Email address domains are inherently case‐insensitive, and the local‐parts
846        // are generally (although not universally) treated as case‐insensitive too, so
847        // we use a case‐insensitive match here.
848        let predicate =
849            RevsetFilterPredicate::AuthorEmail(StringPattern::exact_i(context.user_email));
850        Ok(RevsetExpression::filter(predicate))
851    });
852    map.insert("committer", |diagnostics, function, _context| {
853        let [arg] = function.expect_exact_arguments()?;
854        let pattern = expect_string_pattern(diagnostics, arg)?;
855        let name_predicate = RevsetFilterPredicate::CommitterName(pattern.clone());
856        let email_predicate = RevsetFilterPredicate::CommitterEmail(pattern);
857        Ok(RevsetExpression::filter(name_predicate)
858            .union(&RevsetExpression::filter(email_predicate)))
859    });
860    map.insert("committer_name", |diagnostics, function, _context| {
861        let [arg] = function.expect_exact_arguments()?;
862        let pattern = expect_string_pattern(diagnostics, arg)?;
863        let predicate = RevsetFilterPredicate::CommitterName(pattern);
864        Ok(RevsetExpression::filter(predicate))
865    });
866    map.insert("committer_email", |diagnostics, function, _context| {
867        let [arg] = function.expect_exact_arguments()?;
868        let pattern = expect_string_pattern(diagnostics, arg)?;
869        let predicate = RevsetFilterPredicate::CommitterEmail(pattern);
870        Ok(RevsetExpression::filter(predicate))
871    });
872    map.insert("committer_date", |diagnostics, function, context| {
873        let [arg] = function.expect_exact_arguments()?;
874        let pattern = expect_date_pattern(diagnostics, arg, context.date_pattern_context())?;
875        Ok(RevsetExpression::filter(
876            RevsetFilterPredicate::CommitterDate(pattern),
877        ))
878    });
879    map.insert("empty", |_diagnostics, function, _context| {
880        function.expect_no_arguments()?;
881        Ok(RevsetExpression::is_empty())
882    });
883    map.insert("files", |diagnostics, function, context| {
884        let ctx = context.workspace.as_ref().ok_or_else(|| {
885            RevsetParseError::with_span(
886                RevsetParseErrorKind::FsPathWithoutWorkspace,
887                function.args_span, // TODO: better to use name_span?
888            )
889        })?;
890        let [arg] = function.expect_exact_arguments()?;
891        let expr = expect_fileset_expression(diagnostics, arg, ctx.path_converter)?;
892        Ok(RevsetExpression::filter(RevsetFilterPredicate::File(expr)))
893    });
894    map.insert("diff_contains", |diagnostics, function, context| {
895        let ([text_arg], [files_opt_arg]) = function.expect_arguments()?;
896        let text = expect_string_pattern(diagnostics, text_arg)?;
897        let files = if let Some(files_arg) = files_opt_arg {
898            let ctx = context.workspace.as_ref().ok_or_else(|| {
899                RevsetParseError::with_span(
900                    RevsetParseErrorKind::FsPathWithoutWorkspace,
901                    files_arg.span,
902                )
903            })?;
904            expect_fileset_expression(diagnostics, files_arg, ctx.path_converter)?
905        } else {
906            // TODO: defaults to CLI path arguments?
907            // https://github.com/jj-vcs/jj/issues/2933#issuecomment-1925870731
908            FilesetExpression::all()
909        };
910        Ok(RevsetExpression::filter(
911            RevsetFilterPredicate::DiffContains { text, files },
912        ))
913    });
914    map.insert("conflicts", |_diagnostics, function, _context| {
915        function.expect_no_arguments()?;
916        Ok(RevsetExpression::filter(RevsetFilterPredicate::HasConflict))
917    });
918    map.insert("present", |diagnostics, function, context| {
919        let [arg] = function.expect_exact_arguments()?;
920        let expression = lower_expression(diagnostics, arg, context)?;
921        Ok(expression.present())
922    });
923    map.insert("at_operation", |diagnostics, function, context| {
924        let [op_arg, cand_arg] = function.expect_exact_arguments()?;
925        // TODO: Parse "opset" here if we add proper language support.
926        let operation =
927            revset_parser::expect_expression_with(diagnostics, op_arg, |_diagnostics, node| {
928                Ok(node.span.as_str().to_owned())
929            })?;
930        let candidates = lower_expression(diagnostics, cand_arg, context)?;
931        Ok(Rc::new(RevsetExpression::AtOperation {
932            operation,
933            candidates,
934        }))
935    });
936    map.insert("coalesce", |diagnostics, function, context| {
937        let ([], args) = function.expect_some_arguments()?;
938        let expressions: Vec<_> = args
939            .iter()
940            .map(|arg| lower_expression(diagnostics, arg, context))
941            .try_collect()?;
942        Ok(RevsetExpression::coalesce(&expressions))
943    });
944    map
945});
946
947/// Parses the given `node` as a fileset expression.
948pub fn expect_fileset_expression(
949    diagnostics: &mut RevsetDiagnostics,
950    node: &ExpressionNode,
951    path_converter: &RepoPathUiConverter,
952) -> Result<FilesetExpression, RevsetParseError> {
953    // Alias handling is a bit tricky. The outermost expression `alias` is
954    // substituted, but inner expressions `x & alias` aren't. If this seemed
955    // weird, we can either transform AST or turn off revset aliases completely.
956    revset_parser::expect_expression_with(diagnostics, node, |diagnostics, node| {
957        let mut inner_diagnostics = FilesetDiagnostics::new();
958        let expression = fileset::parse(&mut inner_diagnostics, node.span.as_str(), path_converter)
959            .map_err(|err| {
960                RevsetParseError::expression("In fileset expression", node.span).with_source(err)
961            })?;
962        diagnostics.extend_with(inner_diagnostics, |diag| {
963            RevsetParseError::expression("In fileset expression", node.span).with_source(diag)
964        });
965        Ok(expression)
966    })
967}
968
969pub fn expect_string_pattern(
970    diagnostics: &mut RevsetDiagnostics,
971    node: &ExpressionNode,
972) -> Result<StringPattern, RevsetParseError> {
973    revset_parser::expect_pattern_with(
974        diagnostics,
975        "string pattern",
976        node,
977        |_diagnostics, value, kind| match kind {
978            Some(kind) => StringPattern::from_str_kind(value, kind),
979            None => Ok(StringPattern::Substring(value.to_owned())),
980        },
981    )
982}
983
984pub fn expect_date_pattern(
985    diagnostics: &mut RevsetDiagnostics,
986    node: &ExpressionNode,
987    context: &DatePatternContext,
988) -> Result<DatePattern, RevsetParseError> {
989    revset_parser::expect_pattern_with(
990        diagnostics,
991        "date pattern",
992        node,
993        |_diagnostics, value, kind| -> Result<_, Box<dyn std::error::Error + Send + Sync>> {
994            match kind {
995                None => Err("Date pattern must specify 'after' or 'before'".into()),
996                Some(kind) => Ok(context.parse_relative(value, kind)?),
997            }
998        },
999    )
1000}
1001
1002fn parse_remote_bookmarks_arguments(
1003    diagnostics: &mut RevsetDiagnostics,
1004    function: &FunctionCallNode,
1005    remote_ref_state: Option<RemoteRefState>,
1006) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
1007    let ([], [bookmark_opt_arg, remote_opt_arg]) =
1008        function.expect_named_arguments(&["", "remote"])?;
1009    let bookmark_pattern = if let Some(bookmark_arg) = bookmark_opt_arg {
1010        expect_string_pattern(diagnostics, bookmark_arg)?
1011    } else {
1012        StringPattern::everything()
1013    };
1014    let remote_pattern = if let Some(remote_arg) = remote_opt_arg {
1015        expect_string_pattern(diagnostics, remote_arg)?
1016    } else {
1017        StringPattern::everything()
1018    };
1019    Ok(RevsetExpression::remote_bookmarks(
1020        bookmark_pattern,
1021        remote_pattern,
1022        remote_ref_state,
1023    ))
1024}
1025
1026/// Resolves function call by using the given function map.
1027fn lower_function_call(
1028    diagnostics: &mut RevsetDiagnostics,
1029    function: &FunctionCallNode,
1030    context: &LoweringContext,
1031) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
1032    let function_map = &context.extensions.function_map;
1033    if let Some(func) = function_map.get(function.name) {
1034        func(diagnostics, function, context)
1035    } else {
1036        Err(RevsetParseError::with_span(
1037            RevsetParseErrorKind::NoSuchFunction {
1038                name: function.name.to_owned(),
1039                candidates: collect_similar(function.name, function_map.keys()),
1040            },
1041            function.name_span,
1042        ))
1043    }
1044}
1045
1046/// Transforms the given AST `node` into expression that describes DAG
1047/// operation. Function calls will be resolved at this stage.
1048pub fn lower_expression(
1049    diagnostics: &mut RevsetDiagnostics,
1050    node: &ExpressionNode,
1051    context: &LoweringContext,
1052) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
1053    match &node.kind {
1054        ExpressionKind::Identifier(name) => Ok(RevsetExpression::symbol((*name).to_owned())),
1055        ExpressionKind::String(name) => Ok(RevsetExpression::symbol(name.to_owned())),
1056        ExpressionKind::StringPattern { .. } => Err(RevsetParseError::with_span(
1057            RevsetParseErrorKind::NotInfixOperator {
1058                op: ":".to_owned(),
1059                similar_op: "::".to_owned(),
1060                description: "DAG range".to_owned(),
1061            },
1062            node.span,
1063        )),
1064        ExpressionKind::RemoteSymbol(symbol) => Ok(RevsetExpression::remote_symbol(symbol.clone())),
1065        ExpressionKind::AtWorkspace(name) => Ok(RevsetExpression::working_copy(name.into())),
1066        ExpressionKind::AtCurrentWorkspace => {
1067            let ctx = context.workspace.as_ref().ok_or_else(|| {
1068                RevsetParseError::with_span(
1069                    RevsetParseErrorKind::WorkingCopyWithoutWorkspace,
1070                    node.span,
1071                )
1072            })?;
1073            Ok(RevsetExpression::working_copy(
1074                ctx.workspace_name.to_owned(),
1075            ))
1076        }
1077        ExpressionKind::DagRangeAll => Ok(RevsetExpression::all()),
1078        ExpressionKind::RangeAll => {
1079            Ok(RevsetExpression::root().range(&RevsetExpression::visible_heads()))
1080        }
1081        ExpressionKind::Unary(op, arg_node) => {
1082            let arg = lower_expression(diagnostics, arg_node, context)?;
1083            match op {
1084                UnaryOp::Negate => Ok(arg.negated()),
1085                UnaryOp::DagRangePre => Ok(arg.ancestors()),
1086                UnaryOp::DagRangePost => Ok(arg.descendants()),
1087                UnaryOp::RangePre => Ok(RevsetExpression::root().range(&arg)),
1088                UnaryOp::RangePost => Ok(arg.range(&RevsetExpression::visible_heads())),
1089                UnaryOp::Parents => Ok(arg.parents()),
1090                UnaryOp::Children => Ok(arg.children()),
1091            }
1092        }
1093        ExpressionKind::Binary(op, lhs_node, rhs_node) => {
1094            let lhs = lower_expression(diagnostics, lhs_node, context)?;
1095            let rhs = lower_expression(diagnostics, rhs_node, context)?;
1096            match op {
1097                BinaryOp::Intersection => Ok(lhs.intersection(&rhs)),
1098                BinaryOp::Difference => Ok(lhs.minus(&rhs)),
1099                BinaryOp::DagRange => Ok(lhs.dag_range_to(&rhs)),
1100                BinaryOp::Range => Ok(lhs.range(&rhs)),
1101            }
1102        }
1103        ExpressionKind::UnionAll(nodes) => {
1104            let expressions: Vec<_> = nodes
1105                .iter()
1106                .map(|node| lower_expression(diagnostics, node, context))
1107                .try_collect()?;
1108            Ok(RevsetExpression::union_all(&expressions))
1109        }
1110        ExpressionKind::FunctionCall(function) => {
1111            lower_function_call(diagnostics, function, context)
1112        }
1113        ExpressionKind::Modifier(modifier) => {
1114            let name = modifier.name;
1115            Err(RevsetParseError::expression(
1116                format!("Modifier `{name}:` is not allowed in sub expression"),
1117                modifier.name_span,
1118            ))
1119        }
1120        ExpressionKind::AliasExpanded(id, subst) => {
1121            let mut inner_diagnostics = RevsetDiagnostics::new();
1122            let expression = lower_expression(&mut inner_diagnostics, subst, context)
1123                .map_err(|e| e.within_alias_expansion(*id, node.span))?;
1124            diagnostics.extend_with(inner_diagnostics, |diag| {
1125                diag.within_alias_expansion(*id, node.span)
1126            });
1127            Ok(expression)
1128        }
1129    }
1130}
1131
1132pub fn parse(
1133    diagnostics: &mut RevsetDiagnostics,
1134    revset_str: &str,
1135    context: &RevsetParseContext,
1136) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
1137    let node = parse_program(revset_str)?;
1138    let node =
1139        dsl_util::expand_aliases_with_locals(node, context.aliases_map, &context.local_variables)?;
1140    lower_expression(diagnostics, &node, &context.to_lowering_context())
1141        .map_err(|err| err.extend_function_candidates(context.aliases_map.function_names()))
1142}
1143
1144pub fn parse_with_modifier(
1145    diagnostics: &mut RevsetDiagnostics,
1146    revset_str: &str,
1147    context: &RevsetParseContext,
1148) -> Result<(Rc<UserRevsetExpression>, Option<RevsetModifier>), RevsetParseError> {
1149    let node = parse_program(revset_str)?;
1150    let node =
1151        dsl_util::expand_aliases_with_locals(node, context.aliases_map, &context.local_variables)?;
1152    revset_parser::expect_program_with(
1153        diagnostics,
1154        &node,
1155        |diagnostics, node| lower_expression(diagnostics, node, &context.to_lowering_context()),
1156        |_diagnostics, name, span| match name {
1157            "all" => Ok(RevsetModifier::All),
1158            _ => Err(RevsetParseError::with_span(
1159                RevsetParseErrorKind::NoSuchModifier(name.to_owned()),
1160                span,
1161            )),
1162        },
1163    )
1164    .map_err(|err| err.extend_function_candidates(context.aliases_map.function_names()))
1165}
1166
1167/// `Some` for rewritten expression, or `None` to reuse the original expression.
1168type TransformedExpression<St> = Option<Rc<RevsetExpression<St>>>;
1169
1170/// Walks `expression` tree and applies `f` recursively from leaf nodes.
1171fn transform_expression_bottom_up<St: ExpressionState>(
1172    expression: &Rc<RevsetExpression<St>>,
1173    mut f: impl FnMut(&Rc<RevsetExpression<St>>) -> TransformedExpression<St>,
1174) -> TransformedExpression<St> {
1175    try_transform_expression::<St, Infallible>(
1176        expression,
1177        |_| Ok(None),
1178        |expression| Ok(f(expression)),
1179    )
1180    .unwrap()
1181}
1182
1183/// Walks `expression` tree and applies transformation recursively.
1184///
1185/// `pre` is the callback to rewrite subtree including children. It is
1186/// invoked before visiting the child nodes. If returned `Some`, children
1187/// won't be visited.
1188///
1189/// `post` is the callback to rewrite from leaf nodes. If returned `None`,
1190/// the original expression node will be reused.
1191///
1192/// If no nodes rewritten, this function returns `None`.
1193/// `std::iter::successors()` could be used if the transformation needs to be
1194/// applied repeatedly until converged.
1195fn try_transform_expression<St: ExpressionState, E>(
1196    expression: &Rc<RevsetExpression<St>>,
1197    mut pre: impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1198    mut post: impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1199) -> Result<TransformedExpression<St>, E> {
1200    fn transform_child_rec<St: ExpressionState, E>(
1201        expression: &Rc<RevsetExpression<St>>,
1202        pre: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1203        post: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1204    ) -> Result<TransformedExpression<St>, E> {
1205        Ok(match expression.as_ref() {
1206            RevsetExpression::None => None,
1207            RevsetExpression::All => None,
1208            RevsetExpression::VisibleHeads => None,
1209            RevsetExpression::Root => None,
1210            RevsetExpression::Commits(_) => None,
1211            RevsetExpression::CommitRef(_) => None,
1212            RevsetExpression::Ancestors { heads, generation } => transform_rec(heads, pre, post)?
1213                .map(|heads| RevsetExpression::Ancestors {
1214                    heads,
1215                    generation: generation.clone(),
1216                }),
1217            RevsetExpression::Descendants { roots, generation } => transform_rec(roots, pre, post)?
1218                .map(|roots| RevsetExpression::Descendants {
1219                    roots,
1220                    generation: generation.clone(),
1221                }),
1222            RevsetExpression::Range {
1223                roots,
1224                heads,
1225                generation,
1226            } => transform_rec_pair((roots, heads), pre, post)?.map(|(roots, heads)| {
1227                RevsetExpression::Range {
1228                    roots,
1229                    heads,
1230                    generation: generation.clone(),
1231                }
1232            }),
1233            RevsetExpression::DagRange { roots, heads } => {
1234                transform_rec_pair((roots, heads), pre, post)?
1235                    .map(|(roots, heads)| RevsetExpression::DagRange { roots, heads })
1236            }
1237            RevsetExpression::Reachable { sources, domain } => {
1238                transform_rec_pair((sources, domain), pre, post)?
1239                    .map(|(sources, domain)| RevsetExpression::Reachable { sources, domain })
1240            }
1241            RevsetExpression::Heads(candidates) => {
1242                transform_rec(candidates, pre, post)?.map(RevsetExpression::Heads)
1243            }
1244            RevsetExpression::Roots(candidates) => {
1245                transform_rec(candidates, pre, post)?.map(RevsetExpression::Roots)
1246            }
1247            RevsetExpression::ForkPoint(expression) => {
1248                transform_rec(expression, pre, post)?.map(RevsetExpression::ForkPoint)
1249            }
1250            RevsetExpression::Latest { candidates, count } => transform_rec(candidates, pre, post)?
1251                .map(|candidates| RevsetExpression::Latest {
1252                    candidates,
1253                    count: *count,
1254                }),
1255            RevsetExpression::Filter(_) => None,
1256            RevsetExpression::AsFilter(candidates) => {
1257                transform_rec(candidates, pre, post)?.map(RevsetExpression::AsFilter)
1258            }
1259            RevsetExpression::AtOperation {
1260                operation,
1261                candidates,
1262            } => transform_rec(candidates, pre, post)?.map(|candidates| {
1263                RevsetExpression::AtOperation {
1264                    operation: operation.clone(),
1265                    candidates,
1266                }
1267            }),
1268            RevsetExpression::WithinVisibility {
1269                candidates,
1270                visible_heads,
1271            } => transform_rec(candidates, pre, post)?.map(|candidates| {
1272                RevsetExpression::WithinVisibility {
1273                    candidates,
1274                    visible_heads: visible_heads.clone(),
1275                }
1276            }),
1277            RevsetExpression::Coalesce(expression1, expression2) => transform_rec_pair(
1278                (expression1, expression2),
1279                pre,
1280                post,
1281            )?
1282            .map(|(expression1, expression2)| RevsetExpression::Coalesce(expression1, expression2)),
1283            RevsetExpression::Present(candidates) => {
1284                transform_rec(candidates, pre, post)?.map(RevsetExpression::Present)
1285            }
1286            RevsetExpression::NotIn(complement) => {
1287                transform_rec(complement, pre, post)?.map(RevsetExpression::NotIn)
1288            }
1289            RevsetExpression::Union(expression1, expression2) => {
1290                transform_rec_pair((expression1, expression2), pre, post)?.map(
1291                    |(expression1, expression2)| RevsetExpression::Union(expression1, expression2),
1292                )
1293            }
1294            RevsetExpression::Intersection(expression1, expression2) => {
1295                transform_rec_pair((expression1, expression2), pre, post)?.map(
1296                    |(expression1, expression2)| {
1297                        RevsetExpression::Intersection(expression1, expression2)
1298                    },
1299                )
1300            }
1301            RevsetExpression::Difference(expression1, expression2) => {
1302                transform_rec_pair((expression1, expression2), pre, post)?.map(
1303                    |(expression1, expression2)| {
1304                        RevsetExpression::Difference(expression1, expression2)
1305                    },
1306                )
1307            }
1308        }
1309        .map(Rc::new))
1310    }
1311
1312    #[expect(clippy::type_complexity)]
1313    fn transform_rec_pair<St: ExpressionState, E>(
1314        (expression1, expression2): (&Rc<RevsetExpression<St>>, &Rc<RevsetExpression<St>>),
1315        pre: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1316        post: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1317    ) -> Result<Option<(Rc<RevsetExpression<St>>, Rc<RevsetExpression<St>>)>, E> {
1318        match (
1319            transform_rec(expression1, pre, post)?,
1320            transform_rec(expression2, pre, post)?,
1321        ) {
1322            (Some(new_expression1), Some(new_expression2)) => {
1323                Ok(Some((new_expression1, new_expression2)))
1324            }
1325            (Some(new_expression1), None) => Ok(Some((new_expression1, expression2.clone()))),
1326            (None, Some(new_expression2)) => Ok(Some((expression1.clone(), new_expression2))),
1327            (None, None) => Ok(None),
1328        }
1329    }
1330
1331    fn transform_rec<St: ExpressionState, E>(
1332        expression: &Rc<RevsetExpression<St>>,
1333        pre: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1334        post: &mut impl FnMut(&Rc<RevsetExpression<St>>) -> Result<TransformedExpression<St>, E>,
1335    ) -> Result<TransformedExpression<St>, E> {
1336        if let Some(new_expression) = pre(expression)? {
1337            return Ok(Some(new_expression));
1338        }
1339        if let Some(new_expression) = transform_child_rec(expression, pre, post)? {
1340            // must propagate new expression tree
1341            Ok(Some(post(&new_expression)?.unwrap_or(new_expression)))
1342        } else {
1343            post(expression)
1344        }
1345    }
1346
1347    transform_rec(expression, &mut pre, &mut post)
1348}
1349
1350/// Visitor-like interface to transform [`RevsetExpression`] state recursively.
1351///
1352/// This is similar to [`try_transform_expression()`], but is supposed to
1353/// transform the resolution state from `InSt` to `OutSt`.
1354trait ExpressionStateFolder<InSt: ExpressionState, OutSt: ExpressionState> {
1355    type Error;
1356
1357    /// Transforms the `expression`. By default, inner items are transformed
1358    /// recursively.
1359    fn fold_expression(
1360        &mut self,
1361        expression: &RevsetExpression<InSt>,
1362    ) -> Result<Rc<RevsetExpression<OutSt>>, Self::Error> {
1363        fold_child_expression_state(self, expression)
1364    }
1365
1366    /// Transforms commit ref such as symbol.
1367    fn fold_commit_ref(
1368        &mut self,
1369        commit_ref: &InSt::CommitRef,
1370    ) -> Result<Rc<RevsetExpression<OutSt>>, Self::Error>;
1371
1372    /// Transforms `at_operation(operation, candidates)` expression.
1373    fn fold_at_operation(
1374        &mut self,
1375        operation: &InSt::Operation,
1376        candidates: &RevsetExpression<InSt>,
1377    ) -> Result<Rc<RevsetExpression<OutSt>>, Self::Error>;
1378}
1379
1380/// Transforms inner items of the `expression` by using the `folder`.
1381fn fold_child_expression_state<InSt, OutSt, F>(
1382    folder: &mut F,
1383    expression: &RevsetExpression<InSt>,
1384) -> Result<Rc<RevsetExpression<OutSt>>, F::Error>
1385where
1386    InSt: ExpressionState,
1387    OutSt: ExpressionState,
1388    F: ExpressionStateFolder<InSt, OutSt> + ?Sized,
1389{
1390    let expression: Rc<_> = match expression {
1391        RevsetExpression::None => RevsetExpression::None.into(),
1392        RevsetExpression::All => RevsetExpression::All.into(),
1393        RevsetExpression::VisibleHeads => RevsetExpression::VisibleHeads.into(),
1394        RevsetExpression::Root => RevsetExpression::Root.into(),
1395        RevsetExpression::Commits(ids) => RevsetExpression::Commits(ids.clone()).into(),
1396        RevsetExpression::CommitRef(commit_ref) => folder.fold_commit_ref(commit_ref)?,
1397        RevsetExpression::Ancestors { heads, generation } => {
1398            let heads = folder.fold_expression(heads)?;
1399            let generation = generation.clone();
1400            RevsetExpression::Ancestors { heads, generation }.into()
1401        }
1402        RevsetExpression::Descendants { roots, generation } => {
1403            let roots = folder.fold_expression(roots)?;
1404            let generation = generation.clone();
1405            RevsetExpression::Descendants { roots, generation }.into()
1406        }
1407        RevsetExpression::Range {
1408            roots,
1409            heads,
1410            generation,
1411        } => {
1412            let roots = folder.fold_expression(roots)?;
1413            let heads = folder.fold_expression(heads)?;
1414            let generation = generation.clone();
1415            RevsetExpression::Range {
1416                roots,
1417                heads,
1418                generation,
1419            }
1420            .into()
1421        }
1422        RevsetExpression::DagRange { roots, heads } => {
1423            let roots = folder.fold_expression(roots)?;
1424            let heads = folder.fold_expression(heads)?;
1425            RevsetExpression::DagRange { roots, heads }.into()
1426        }
1427        RevsetExpression::Reachable { sources, domain } => {
1428            let sources = folder.fold_expression(sources)?;
1429            let domain = folder.fold_expression(domain)?;
1430            RevsetExpression::Reachable { sources, domain }.into()
1431        }
1432        RevsetExpression::Heads(heads) => {
1433            let heads = folder.fold_expression(heads)?;
1434            RevsetExpression::Heads(heads).into()
1435        }
1436        RevsetExpression::Roots(roots) => {
1437            let roots = folder.fold_expression(roots)?;
1438            RevsetExpression::Roots(roots).into()
1439        }
1440        RevsetExpression::ForkPoint(expression) => {
1441            let expression = folder.fold_expression(expression)?;
1442            RevsetExpression::ForkPoint(expression).into()
1443        }
1444        RevsetExpression::Latest { candidates, count } => {
1445            let candidates = folder.fold_expression(candidates)?;
1446            let count = *count;
1447            RevsetExpression::Latest { candidates, count }.into()
1448        }
1449        RevsetExpression::Filter(predicate) => RevsetExpression::Filter(predicate.clone()).into(),
1450        RevsetExpression::AsFilter(candidates) => {
1451            let candidates = folder.fold_expression(candidates)?;
1452            RevsetExpression::AsFilter(candidates).into()
1453        }
1454        RevsetExpression::AtOperation {
1455            operation,
1456            candidates,
1457        } => folder.fold_at_operation(operation, candidates)?,
1458        RevsetExpression::WithinVisibility {
1459            candidates,
1460            visible_heads,
1461        } => {
1462            let candidates = folder.fold_expression(candidates)?;
1463            let visible_heads = visible_heads.clone();
1464            RevsetExpression::WithinVisibility {
1465                candidates,
1466                visible_heads,
1467            }
1468            .into()
1469        }
1470        RevsetExpression::Coalesce(expression1, expression2) => {
1471            let expression1 = folder.fold_expression(expression1)?;
1472            let expression2 = folder.fold_expression(expression2)?;
1473            RevsetExpression::Coalesce(expression1, expression2).into()
1474        }
1475        RevsetExpression::Present(candidates) => {
1476            let candidates = folder.fold_expression(candidates)?;
1477            RevsetExpression::Present(candidates).into()
1478        }
1479        RevsetExpression::NotIn(complement) => {
1480            let complement = folder.fold_expression(complement)?;
1481            RevsetExpression::NotIn(complement).into()
1482        }
1483        RevsetExpression::Union(expression1, expression2) => {
1484            let expression1 = folder.fold_expression(expression1)?;
1485            let expression2 = folder.fold_expression(expression2)?;
1486            RevsetExpression::Union(expression1, expression2).into()
1487        }
1488        RevsetExpression::Intersection(expression1, expression2) => {
1489            let expression1 = folder.fold_expression(expression1)?;
1490            let expression2 = folder.fold_expression(expression2)?;
1491            RevsetExpression::Intersection(expression1, expression2).into()
1492        }
1493        RevsetExpression::Difference(expression1, expression2) => {
1494            let expression1 = folder.fold_expression(expression1)?;
1495            let expression2 = folder.fold_expression(expression2)?;
1496            RevsetExpression::Difference(expression1, expression2).into()
1497        }
1498    };
1499    Ok(expression)
1500}
1501
1502/// Transforms filter expressions, by applying the following rules.
1503///
1504/// a. Moves as many sets to left of filter intersection as possible, to
1505///    minimize the filter inputs.
1506/// b. TODO: Rewrites set operations to and/or/not of predicates, to
1507///    help further optimization (e.g. combine `file(_)` matchers.)
1508/// c. Wraps union of filter and set (e.g. `author(_) | heads()`), to
1509///    ensure inner filter wouldn't need to evaluate all the input sets.
1510fn internalize_filter<St: ExpressionState>(
1511    expression: &Rc<RevsetExpression<St>>,
1512) -> TransformedExpression<St> {
1513    fn is_filter<St: ExpressionState>(expression: &RevsetExpression<St>) -> bool {
1514        matches!(
1515            expression,
1516            RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_)
1517        )
1518    }
1519
1520    fn is_filter_tree<St: ExpressionState>(expression: &RevsetExpression<St>) -> bool {
1521        is_filter(expression) || as_filter_intersection(expression).is_some()
1522    }
1523
1524    // Extracts 'c & f' from intersect_down()-ed node.
1525    #[expect(clippy::type_complexity)]
1526    fn as_filter_intersection<St: ExpressionState>(
1527        expression: &RevsetExpression<St>,
1528    ) -> Option<(&Rc<RevsetExpression<St>>, &Rc<RevsetExpression<St>>)> {
1529        if let RevsetExpression::Intersection(expression1, expression2) = expression {
1530            is_filter(expression2).then_some((expression1, expression2))
1531        } else {
1532            None
1533        }
1534    }
1535
1536    // Since both sides must have already been intersect_down()-ed, we don't need to
1537    // apply the whole bottom-up pass to new intersection node. Instead, just push
1538    // new 'c & (d & g)' down-left to '(c & d) & g' while either side is
1539    // an intersection of filter node.
1540    fn intersect_down<St: ExpressionState>(
1541        expression1: &Rc<RevsetExpression<St>>,
1542        expression2: &Rc<RevsetExpression<St>>,
1543    ) -> TransformedExpression<St> {
1544        let recurse = |e1, e2| intersect_down(e1, e2).unwrap_or_else(|| e1.intersection(e2));
1545        match (expression1.as_ref(), expression2.as_ref()) {
1546            // Don't reorder 'f1 & f2'
1547            (_, e2) if is_filter(e2) => None,
1548            // f1 & e2 -> e2 & f1
1549            (e1, _) if is_filter(e1) => Some(expression2.intersection(expression1)),
1550            (e1, e2) => match (as_filter_intersection(e1), as_filter_intersection(e2)) {
1551                // e1 & (c2 & f2) -> (e1 & c2) & f2
1552                // (c1 & f1) & (c2 & f2) -> ((c1 & f1) & c2) & f2 -> ((c1 & c2) & f1) & f2
1553                (_, Some((c2, f2))) => Some(recurse(expression1, c2).intersection(f2)),
1554                // (c1 & f1) & e2 -> (c1 & e2) & f1
1555                // ((c1 & f1) & g1) & e2 -> ((c1 & f1) & e2) & g1 -> ((c1 & e2) & f1) & g1
1556                (Some((c1, f1)), _) => Some(recurse(c1, expression2).intersection(f1)),
1557                (None, None) => None,
1558            },
1559        }
1560    }
1561
1562    // Bottom-up pass pulls up-right filter node from leaf '(c & f) & e' ->
1563    // '(c & e) & f', so that an intersection of filter node can be found as
1564    // a direct child of another intersection node. However, the rewritten
1565    // intersection node 'c & e' can also be a rewrite target if 'e' contains
1566    // a filter node. That's why intersect_down() is also recursive.
1567    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1568        RevsetExpression::Present(e) => {
1569            is_filter_tree(e).then(|| Rc::new(RevsetExpression::AsFilter(expression.clone())))
1570        }
1571        RevsetExpression::NotIn(e) => {
1572            is_filter_tree(e).then(|| Rc::new(RevsetExpression::AsFilter(expression.clone())))
1573        }
1574        RevsetExpression::Union(e1, e2) => (is_filter_tree(e1) || is_filter_tree(e2))
1575            .then(|| Rc::new(RevsetExpression::AsFilter(expression.clone()))),
1576        RevsetExpression::Intersection(expression1, expression2) => {
1577            intersect_down(expression1, expression2)
1578        }
1579        // Difference(e1, e2) should have been unfolded to Intersection(e1, NotIn(e2)).
1580        _ => None,
1581    })
1582}
1583
1584/// Eliminates redundant nodes like `x & all()`, `~~x`.
1585///
1586/// This does not rewrite 'x & none()' to 'none()' because 'x' may be an invalid
1587/// symbol.
1588fn fold_redundant_expression<St: ExpressionState>(
1589    expression: &Rc<RevsetExpression<St>>,
1590) -> TransformedExpression<St> {
1591    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1592        RevsetExpression::NotIn(outer) => match outer.as_ref() {
1593            RevsetExpression::NotIn(inner) => Some(inner.clone()),
1594            _ => None,
1595        },
1596        RevsetExpression::Intersection(expression1, expression2) => {
1597            match (expression1.as_ref(), expression2.as_ref()) {
1598                (_, RevsetExpression::All) => Some(expression1.clone()),
1599                (RevsetExpression::All, _) => Some(expression2.clone()),
1600                _ => None,
1601            }
1602        }
1603        _ => None,
1604    })
1605}
1606
1607fn to_difference_range<St: ExpressionState>(
1608    expression: &Rc<RevsetExpression<St>>,
1609    complement: &Rc<RevsetExpression<St>>,
1610) -> TransformedExpression<St> {
1611    match (expression.as_ref(), complement.as_ref()) {
1612        // ::heads & ~(::roots) -> roots..heads
1613        (
1614            RevsetExpression::Ancestors { heads, generation },
1615            RevsetExpression::Ancestors {
1616                heads: roots,
1617                generation: GENERATION_RANGE_FULL,
1618            },
1619        ) => Some(Rc::new(RevsetExpression::Range {
1620            roots: roots.clone(),
1621            heads: heads.clone(),
1622            generation: generation.clone(),
1623        })),
1624        // ::heads & ~(::roots-) -> ::heads & ~ancestors(roots, 1..) -> roots-..heads
1625        (
1626            RevsetExpression::Ancestors { heads, generation },
1627            RevsetExpression::Ancestors {
1628                heads: roots,
1629                generation:
1630                    Range {
1631                        start: roots_start,
1632                        end: u64::MAX,
1633                    },
1634            },
1635        ) => Some(Rc::new(RevsetExpression::Range {
1636            roots: roots.ancestors_at(*roots_start),
1637            heads: heads.clone(),
1638            generation: generation.clone(),
1639        })),
1640        _ => None,
1641    }
1642}
1643
1644/// Transforms negative intersection to difference. Redundant intersections like
1645/// `all() & e` should have been removed.
1646fn fold_difference<St: ExpressionState>(
1647    expression: &Rc<RevsetExpression<St>>,
1648) -> TransformedExpression<St> {
1649    fn to_difference<St: ExpressionState>(
1650        expression: &Rc<RevsetExpression<St>>,
1651        complement: &Rc<RevsetExpression<St>>,
1652    ) -> Rc<RevsetExpression<St>> {
1653        to_difference_range(expression, complement).unwrap_or_else(|| expression.minus(complement))
1654    }
1655
1656    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1657        RevsetExpression::Intersection(expression1, expression2) => {
1658            match (expression1.as_ref(), expression2.as_ref()) {
1659                // For '~x & f', don't move filter node 'f' left
1660                (_, RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_)) => None,
1661                (_, RevsetExpression::NotIn(complement)) => {
1662                    Some(to_difference(expression1, complement))
1663                }
1664                (RevsetExpression::NotIn(complement), _) => {
1665                    Some(to_difference(expression2, complement))
1666                }
1667                _ => None,
1668            }
1669        }
1670        _ => None,
1671    })
1672}
1673
1674/// Transforms remaining negated ancestors `~(::h)` to range `h..`.
1675///
1676/// Since this rule inserts redundant `visible_heads()`, negative intersections
1677/// should have been transformed.
1678fn fold_not_in_ancestors<St: ExpressionState>(
1679    expression: &Rc<RevsetExpression<St>>,
1680) -> TransformedExpression<St> {
1681    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1682        RevsetExpression::NotIn(complement)
1683            if matches!(complement.as_ref(), RevsetExpression::Ancestors { .. }) =>
1684        {
1685            // ~(::heads) -> heads..
1686            // ~(::heads-) -> ~ancestors(heads, 1..) -> heads-..
1687            to_difference_range(&RevsetExpression::visible_heads().ancestors(), complement)
1688        }
1689        _ => None,
1690    })
1691}
1692
1693/// Transforms binary difference to more primitive negative intersection.
1694///
1695/// For example, `all() ~ e` will become `all() & ~e`, which can be simplified
1696/// further by `fold_redundant_expression()`.
1697fn unfold_difference<St: ExpressionState>(
1698    expression: &Rc<RevsetExpression<St>>,
1699) -> TransformedExpression<St> {
1700    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1701        // roots..heads -> ::heads & ~(::roots)
1702        RevsetExpression::Range {
1703            roots,
1704            heads,
1705            generation,
1706        } => {
1707            let heads_ancestors = Rc::new(RevsetExpression::Ancestors {
1708                heads: heads.clone(),
1709                generation: generation.clone(),
1710            });
1711            Some(heads_ancestors.intersection(&roots.ancestors().negated()))
1712        }
1713        RevsetExpression::Difference(expression1, expression2) => {
1714            Some(expression1.intersection(&expression2.negated()))
1715        }
1716        _ => None,
1717    })
1718}
1719
1720/// Transforms nested `ancestors()`/`parents()`/`descendants()`/`children()`
1721/// like `h---`/`r+++`.
1722fn fold_generation<St: ExpressionState>(
1723    expression: &Rc<RevsetExpression<St>>,
1724) -> TransformedExpression<St> {
1725    fn add_generation(generation1: &Range<u64>, generation2: &Range<u64>) -> Range<u64> {
1726        // For any (g1, g2) in (generation1, generation2), g1 + g2.
1727        if generation1.is_empty() || generation2.is_empty() {
1728            GENERATION_RANGE_EMPTY
1729        } else {
1730            let start = u64::saturating_add(generation1.start, generation2.start);
1731            let end = u64::saturating_add(generation1.end, generation2.end - 1);
1732            start..end
1733        }
1734    }
1735
1736    transform_expression_bottom_up(expression, |expression| match expression.as_ref() {
1737        RevsetExpression::Ancestors {
1738            heads,
1739            generation: generation1,
1740        } => {
1741            match heads.as_ref() {
1742                // (h-)- -> ancestors(ancestors(h, 1), 1) -> ancestors(h, 2)
1743                // ::(h-) -> ancestors(ancestors(h, 1), ..) -> ancestors(h, 1..)
1744                // (::h)- -> ancestors(ancestors(h, ..), 1) -> ancestors(h, 1..)
1745                RevsetExpression::Ancestors {
1746                    heads,
1747                    generation: generation2,
1748                } => Some(Rc::new(RevsetExpression::Ancestors {
1749                    heads: heads.clone(),
1750                    generation: add_generation(generation1, generation2),
1751                })),
1752                _ => None,
1753            }
1754        }
1755        RevsetExpression::Descendants {
1756            roots,
1757            generation: generation1,
1758        } => {
1759            match roots.as_ref() {
1760                // (r+)+ -> descendants(descendants(r, 1), 1) -> descendants(r, 2)
1761                // (r+):: -> descendants(descendants(r, 1), ..) -> descendants(r, 1..)
1762                // (r::)+ -> descendants(descendants(r, ..), 1) -> descendants(r, 1..)
1763                RevsetExpression::Descendants {
1764                    roots,
1765                    generation: generation2,
1766                } => Some(Rc::new(RevsetExpression::Descendants {
1767                    roots: roots.clone(),
1768                    generation: add_generation(generation1, generation2),
1769                })),
1770                _ => None,
1771            }
1772        }
1773        // Range should have been unfolded to intersection of Ancestors.
1774        _ => None,
1775    })
1776}
1777
1778/// Rewrites the given `expression` tree to reduce evaluation cost. Returns new
1779/// tree.
1780pub fn optimize<St: ExpressionState>(
1781    expression: Rc<RevsetExpression<St>>,
1782) -> Rc<RevsetExpression<St>> {
1783    let expression = unfold_difference(&expression).unwrap_or(expression);
1784    let expression = fold_redundant_expression(&expression).unwrap_or(expression);
1785    let expression = fold_generation(&expression).unwrap_or(expression);
1786    let expression = internalize_filter(&expression).unwrap_or(expression);
1787    let expression = fold_difference(&expression).unwrap_or(expression);
1788    fold_not_in_ancestors(&expression).unwrap_or(expression)
1789}
1790
1791// TODO: find better place to host this function (or add compile-time revset
1792// parsing and resolution like
1793// `revset!("{unwanted}..{wanted}").evaluate(repo)`?)
1794pub fn walk_revs<'index>(
1795    repo: &'index dyn Repo,
1796    wanted: &[CommitId],
1797    unwanted: &[CommitId],
1798) -> Result<Box<dyn Revset + 'index>, RevsetEvaluationError> {
1799    RevsetExpression::commits(unwanted.to_vec())
1800        .range(&RevsetExpression::commits(wanted.to_vec()))
1801        .evaluate(repo)
1802}
1803
1804fn reload_repo_at_operation(
1805    repo: &dyn Repo,
1806    op_str: &str,
1807) -> Result<Arc<ReadonlyRepo>, RevsetResolutionError> {
1808    // TODO: Maybe we should ensure that the resolved operation is an ancestor
1809    // of the current operation. If it weren't, there might be commits unknown
1810    // to the outer repo.
1811    let base_repo = repo.base_repo();
1812    let operation = op_walk::resolve_op_with_repo(base_repo, op_str)
1813        .map_err(|err| RevsetResolutionError::Other(err.into()))?;
1814    base_repo.reload_at(&operation).map_err(|err| match err {
1815        RepoLoaderError::Backend(err) => RevsetResolutionError::Backend(err),
1816        RepoLoaderError::IndexRead(_)
1817        | RepoLoaderError::OpHeadResolution(_)
1818        | RepoLoaderError::OpHeadsStoreError(_)
1819        | RepoLoaderError::OpStore(_)
1820        | RepoLoaderError::TransactionCommit(_) => RevsetResolutionError::Other(err.into()),
1821    })
1822}
1823
1824fn resolve_remote_bookmark(repo: &dyn Repo, symbol: RemoteRefSymbol<'_>) -> Option<Vec<CommitId>> {
1825    let target = &repo.view().get_remote_bookmark(symbol).target;
1826    target
1827        .is_present()
1828        .then(|| target.added_ids().cloned().collect())
1829}
1830
1831fn all_formatted_bookmark_symbols(
1832    repo: &dyn Repo,
1833    include_synced_remotes: bool,
1834) -> impl Iterator<Item = String> + use<'_> {
1835    let view = repo.view();
1836    view.bookmarks().flat_map(move |(name, bookmark_target)| {
1837        let local_target = bookmark_target.local_target;
1838        let local_symbol = local_target
1839            .is_present()
1840            .then(|| format_symbol(name.as_str()));
1841        let remote_symbols = bookmark_target
1842            .remote_refs
1843            .into_iter()
1844            .filter(move |&(_, remote_ref)| {
1845                include_synced_remotes
1846                    || !remote_ref.is_tracked()
1847                    || remote_ref.target != *local_target
1848            })
1849            .map(move |(remote, _)| format_remote_symbol(name.as_str(), remote.as_str()));
1850        local_symbol.into_iter().chain(remote_symbols)
1851    })
1852}
1853
1854fn make_no_such_symbol_error(repo: &dyn Repo, name: String) -> RevsetResolutionError {
1855    // TODO: include tags?
1856    let bookmark_names = all_formatted_bookmark_symbols(repo, name.contains('@'));
1857    let candidates = collect_similar(&name, bookmark_names);
1858    RevsetResolutionError::NoSuchRevision { name, candidates }
1859}
1860
1861pub trait SymbolResolver {
1862    /// Looks up `symbol` in the given `repo`.
1863    fn resolve_symbol(
1864        &self,
1865        repo: &dyn Repo,
1866        symbol: &str,
1867    ) -> Result<Vec<CommitId>, RevsetResolutionError>;
1868}
1869
1870/// Fails on any attempt to resolve a symbol.
1871pub struct FailingSymbolResolver;
1872
1873impl SymbolResolver for FailingSymbolResolver {
1874    fn resolve_symbol(
1875        &self,
1876        _repo: &dyn Repo,
1877        symbol: &str,
1878    ) -> Result<Vec<CommitId>, RevsetResolutionError> {
1879        Err(RevsetResolutionError::NoSuchRevision {
1880            name: format!(
1881                "Won't resolve symbol {symbol:?}. When creating revsets programmatically, avoid \
1882                 using RevsetExpression::symbol(); use RevsetExpression::commits() instead."
1883            ),
1884            candidates: Default::default(),
1885        })
1886    }
1887}
1888
1889/// A symbol resolver for a specific namespace of labels.
1890///
1891/// Returns None if it cannot handle the symbol.
1892pub trait PartialSymbolResolver {
1893    fn resolve_symbol(
1894        &self,
1895        repo: &dyn Repo,
1896        symbol: &str,
1897    ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError>;
1898}
1899
1900struct TagResolver;
1901
1902impl PartialSymbolResolver for TagResolver {
1903    fn resolve_symbol(
1904        &self,
1905        repo: &dyn Repo,
1906        symbol: &str,
1907    ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
1908        let target = repo.view().get_tag(symbol.as_ref());
1909        Ok(target
1910            .is_present()
1911            .then(|| target.added_ids().cloned().collect()))
1912    }
1913}
1914
1915struct BookmarkResolver;
1916
1917impl PartialSymbolResolver for BookmarkResolver {
1918    fn resolve_symbol(
1919        &self,
1920        repo: &dyn Repo,
1921        symbol: &str,
1922    ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
1923        let target = repo.view().get_local_bookmark(symbol.as_ref());
1924        Ok(target
1925            .is_present()
1926            .then(|| target.added_ids().cloned().collect()))
1927    }
1928}
1929
1930struct GitRefResolver;
1931
1932impl PartialSymbolResolver for GitRefResolver {
1933    fn resolve_symbol(
1934        &self,
1935        repo: &dyn Repo,
1936        symbol: &str,
1937    ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
1938        let view = repo.view();
1939        for git_ref_prefix in &["", "refs/"] {
1940            let target = view.get_git_ref([git_ref_prefix, symbol].concat().as_ref());
1941            if target.is_present() {
1942                return Ok(Some(target.added_ids().cloned().collect()));
1943            }
1944        }
1945
1946        Ok(None)
1947    }
1948}
1949
1950const DEFAULT_RESOLVERS: &[&'static dyn PartialSymbolResolver] =
1951    &[&TagResolver, &BookmarkResolver, &GitRefResolver];
1952
1953struct CommitPrefixResolver<'a> {
1954    context_repo: &'a dyn Repo,
1955    context: Option<&'a IdPrefixContext>,
1956}
1957
1958impl PartialSymbolResolver for CommitPrefixResolver<'_> {
1959    fn resolve_symbol(
1960        &self,
1961        repo: &dyn Repo,
1962        symbol: &str,
1963    ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
1964        if let Some(prefix) = HexPrefix::new(symbol) {
1965            let index = self
1966                .context
1967                .map(|ctx| ctx.populate(self.context_repo))
1968                .transpose()
1969                .map_err(|err| RevsetResolutionError::Other(err.into()))?
1970                .unwrap_or(IdPrefixIndex::empty());
1971            match index.resolve_commit_prefix(repo, &prefix) {
1972                PrefixResolution::AmbiguousMatch => Err(
1973                    RevsetResolutionError::AmbiguousCommitIdPrefix(symbol.to_owned()),
1974                ),
1975                PrefixResolution::SingleMatch(id) => Ok(Some(vec![id])),
1976                PrefixResolution::NoMatch => Ok(None),
1977            }
1978        } else {
1979            Ok(None)
1980        }
1981    }
1982}
1983
1984struct ChangePrefixResolver<'a> {
1985    context_repo: &'a dyn Repo,
1986    context: Option<&'a IdPrefixContext>,
1987}
1988
1989impl PartialSymbolResolver for ChangePrefixResolver<'_> {
1990    fn resolve_symbol(
1991        &self,
1992        repo: &dyn Repo,
1993        symbol: &str,
1994    ) -> Result<Option<Vec<CommitId>>, RevsetResolutionError> {
1995        if let Some(prefix) = to_forward_hex(symbol).as_deref().and_then(HexPrefix::new) {
1996            let index = self
1997                .context
1998                .map(|ctx| ctx.populate(self.context_repo))
1999                .transpose()
2000                .map_err(|err| RevsetResolutionError::Other(err.into()))?
2001                .unwrap_or(IdPrefixIndex::empty());
2002            match index.resolve_change_prefix(repo, &prefix) {
2003                PrefixResolution::AmbiguousMatch => Err(
2004                    RevsetResolutionError::AmbiguousChangeIdPrefix(symbol.to_owned()),
2005                ),
2006                PrefixResolution::SingleMatch(ids) => Ok(Some(ids)),
2007                PrefixResolution::NoMatch => Ok(None),
2008            }
2009        } else {
2010            Ok(None)
2011        }
2012    }
2013}
2014
2015/// An extension of the DefaultSymbolResolver.
2016///
2017/// Each PartialSymbolResolver will be invoked in order, its result used if one
2018/// is provided. Native resolvers are always invoked first. In the future, we
2019/// may provide a way for extensions to override native resolvers like tags and
2020/// bookmarks.
2021pub trait SymbolResolverExtension {
2022    /// PartialSymbolResolvers can initialize some global data by using the
2023    /// `context_repo`, but the `context_repo` may point to a different
2024    /// operation from the `repo` passed into `resolve_symbol()`. For
2025    /// resolution, the latter `repo` should be used.
2026    fn new_resolvers<'a>(
2027        &self,
2028        context_repo: &'a dyn Repo,
2029    ) -> Vec<Box<dyn PartialSymbolResolver + 'a>>;
2030}
2031
2032/// Resolves bookmarks, remote bookmarks, tags, git refs, and full and
2033/// abbreviated commit and change ids.
2034pub struct DefaultSymbolResolver<'a> {
2035    commit_id_resolver: CommitPrefixResolver<'a>,
2036    change_id_resolver: ChangePrefixResolver<'a>,
2037    extensions: Vec<Box<dyn PartialSymbolResolver + 'a>>,
2038}
2039
2040impl<'a> DefaultSymbolResolver<'a> {
2041    /// Creates new symbol resolver that will first disambiguate short ID
2042    /// prefixes within the given `context_repo` if configured.
2043    pub fn new(
2044        context_repo: &'a dyn Repo,
2045        extensions: &[impl AsRef<dyn SymbolResolverExtension>],
2046    ) -> Self {
2047        DefaultSymbolResolver {
2048            commit_id_resolver: CommitPrefixResolver {
2049                context_repo,
2050                context: None,
2051            },
2052            change_id_resolver: ChangePrefixResolver {
2053                context_repo,
2054                context: None,
2055            },
2056            extensions: extensions
2057                .iter()
2058                .flat_map(|ext| ext.as_ref().new_resolvers(context_repo))
2059                .collect(),
2060        }
2061    }
2062
2063    pub fn with_id_prefix_context(mut self, id_prefix_context: &'a IdPrefixContext) -> Self {
2064        self.commit_id_resolver.context = Some(id_prefix_context);
2065        self.change_id_resolver.context = Some(id_prefix_context);
2066        self
2067    }
2068
2069    fn partial_resolvers(&self) -> impl Iterator<Item = &(dyn PartialSymbolResolver + 'a)> {
2070        let prefix_resolvers: [&dyn PartialSymbolResolver; 2] =
2071            [&self.commit_id_resolver, &self.change_id_resolver];
2072        itertools::chain!(
2073            DEFAULT_RESOLVERS.iter().copied(),
2074            prefix_resolvers,
2075            self.extensions.iter().map(|e| e.as_ref())
2076        )
2077    }
2078}
2079
2080impl SymbolResolver for DefaultSymbolResolver<'_> {
2081    fn resolve_symbol(
2082        &self,
2083        repo: &dyn Repo,
2084        symbol: &str,
2085    ) -> Result<Vec<CommitId>, RevsetResolutionError> {
2086        if symbol.is_empty() {
2087            return Err(RevsetResolutionError::EmptyString);
2088        }
2089
2090        for partial_resolver in self.partial_resolvers() {
2091            if let Some(ids) = partial_resolver.resolve_symbol(repo, symbol)? {
2092                return Ok(ids);
2093            }
2094        }
2095
2096        Err(make_no_such_symbol_error(repo, format_symbol(symbol)))
2097    }
2098}
2099
2100fn resolve_commit_ref(
2101    repo: &dyn Repo,
2102    commit_ref: &RevsetCommitRef,
2103    symbol_resolver: &dyn SymbolResolver,
2104) -> Result<Vec<CommitId>, RevsetResolutionError> {
2105    match commit_ref {
2106        RevsetCommitRef::Symbol(symbol) => symbol_resolver.resolve_symbol(repo, symbol),
2107        RevsetCommitRef::RemoteSymbol(symbol) => resolve_remote_bookmark(repo, symbol.as_ref())
2108            .ok_or_else(|| make_no_such_symbol_error(repo, symbol.to_string())),
2109        RevsetCommitRef::WorkingCopy(name) => {
2110            if let Some(commit_id) = repo.view().get_wc_commit_id(name) {
2111                Ok(vec![commit_id.clone()])
2112            } else {
2113                Err(RevsetResolutionError::WorkspaceMissingWorkingCopy { name: name.clone() })
2114            }
2115        }
2116        RevsetCommitRef::WorkingCopies => {
2117            let wc_commits = repo.view().wc_commit_ids().values().cloned().collect_vec();
2118            Ok(wc_commits)
2119        }
2120        RevsetCommitRef::Bookmarks(pattern) => {
2121            let commit_ids = repo
2122                .view()
2123                .local_bookmarks_matching(pattern)
2124                .flat_map(|(_, target)| target.added_ids())
2125                .cloned()
2126                .collect();
2127            Ok(commit_ids)
2128        }
2129        RevsetCommitRef::RemoteBookmarks {
2130            bookmark_pattern,
2131            remote_pattern,
2132            remote_ref_state,
2133        } => {
2134            // TODO: should we allow to select @git bookmarks explicitly?
2135            let commit_ids = repo
2136                .view()
2137                .remote_bookmarks_matching(bookmark_pattern, remote_pattern)
2138                .filter(|(_, remote_ref)| {
2139                    remote_ref_state.is_none_or(|state| remote_ref.state == state)
2140                })
2141                .filter(|&(symbol, _)| !crate::git::is_special_git_remote(symbol.remote))
2142                .flat_map(|(_, remote_ref)| remote_ref.target.added_ids())
2143                .cloned()
2144                .collect();
2145            Ok(commit_ids)
2146        }
2147        RevsetCommitRef::Tags(pattern) => {
2148            let commit_ids = repo
2149                .view()
2150                .tags_matching(pattern)
2151                .flat_map(|(_, target)| target.added_ids())
2152                .cloned()
2153                .collect();
2154            Ok(commit_ids)
2155        }
2156        RevsetCommitRef::GitRefs => {
2157            let mut commit_ids = vec![];
2158            for ref_target in repo.view().git_refs().values() {
2159                commit_ids.extend(ref_target.added_ids().cloned());
2160            }
2161            Ok(commit_ids)
2162        }
2163        RevsetCommitRef::GitHead => Ok(repo.view().git_head().added_ids().cloned().collect()),
2164    }
2165}
2166
2167/// Resolves symbols and commit refs recursively.
2168struct ExpressionSymbolResolver<'a> {
2169    base_repo: &'a dyn Repo,
2170    repo_stack: Vec<Arc<ReadonlyRepo>>,
2171    symbol_resolver: &'a dyn SymbolResolver,
2172}
2173
2174impl<'a> ExpressionSymbolResolver<'a> {
2175    fn new(base_repo: &'a dyn Repo, symbol_resolver: &'a dyn SymbolResolver) -> Self {
2176        ExpressionSymbolResolver {
2177            base_repo,
2178            repo_stack: vec![],
2179            symbol_resolver,
2180        }
2181    }
2182
2183    fn repo(&self) -> &dyn Repo {
2184        self.repo_stack
2185            .last()
2186            .map_or(self.base_repo, |repo| repo.as_ref())
2187    }
2188}
2189
2190impl ExpressionStateFolder<UserExpressionState, ResolvedExpressionState>
2191    for ExpressionSymbolResolver<'_>
2192{
2193    type Error = RevsetResolutionError;
2194
2195    fn fold_expression(
2196        &mut self,
2197        expression: &UserRevsetExpression,
2198    ) -> Result<Rc<ResolvedRevsetExpression>, Self::Error> {
2199        match expression {
2200            // 'present(x)' opens new symbol resolution scope to map error to 'none()'
2201            RevsetExpression::Present(candidates) => {
2202                self.fold_expression(candidates).or_else(|err| match err {
2203                    RevsetResolutionError::NoSuchRevision { .. }
2204                    | RevsetResolutionError::WorkspaceMissingWorkingCopy { .. } => {
2205                        Ok(RevsetExpression::none())
2206                    }
2207                    RevsetResolutionError::EmptyString
2208                    | RevsetResolutionError::AmbiguousCommitIdPrefix(_)
2209                    | RevsetResolutionError::AmbiguousChangeIdPrefix(_)
2210                    | RevsetResolutionError::Backend(_)
2211                    | RevsetResolutionError::Other(_) => Err(err),
2212                })
2213            }
2214            _ => fold_child_expression_state(self, expression),
2215        }
2216    }
2217
2218    fn fold_commit_ref(
2219        &mut self,
2220        commit_ref: &RevsetCommitRef,
2221    ) -> Result<Rc<ResolvedRevsetExpression>, Self::Error> {
2222        let commit_ids = resolve_commit_ref(self.repo(), commit_ref, self.symbol_resolver)?;
2223        Ok(RevsetExpression::commits(commit_ids))
2224    }
2225
2226    fn fold_at_operation(
2227        &mut self,
2228        operation: &String,
2229        candidates: &UserRevsetExpression,
2230    ) -> Result<Rc<ResolvedRevsetExpression>, Self::Error> {
2231        let repo = reload_repo_at_operation(self.repo(), operation)?;
2232        self.repo_stack.push(repo);
2233        let candidates = self.fold_expression(candidates)?;
2234        let visible_heads = self.repo().view().heads().iter().cloned().collect();
2235        self.repo_stack.pop();
2236        Ok(Rc::new(RevsetExpression::WithinVisibility {
2237            candidates,
2238            visible_heads,
2239        }))
2240    }
2241}
2242
2243fn resolve_symbols(
2244    repo: &dyn Repo,
2245    expression: &UserRevsetExpression,
2246    symbol_resolver: &dyn SymbolResolver,
2247) -> Result<Rc<ResolvedRevsetExpression>, RevsetResolutionError> {
2248    let mut resolver = ExpressionSymbolResolver::new(repo, symbol_resolver);
2249    resolver.fold_expression(expression)
2250}
2251
2252/// Inserts implicit `all()` and `visible_heads()` nodes to the `expression`.
2253///
2254/// Symbols and commit refs in the `expression` should have been resolved.
2255///
2256/// This is a separate step because a symbol-resolved `expression` could be
2257/// transformed further to e.g. combine OR-ed `Commits(_)`, or to collect
2258/// commit ids to make `all()` include hidden-but-specified commits. The
2259/// return type `ResolvedExpression` is stricter than `RevsetExpression`,
2260/// and isn't designed for such transformation.
2261fn resolve_visibility(
2262    repo: &dyn Repo,
2263    expression: &ResolvedRevsetExpression,
2264) -> ResolvedExpression {
2265    let context = VisibilityResolutionContext {
2266        visible_heads: &repo.view().heads().iter().cloned().collect_vec(),
2267        root: repo.store().root_commit_id(),
2268    };
2269    context.resolve(expression)
2270}
2271
2272#[derive(Clone, Debug)]
2273struct VisibilityResolutionContext<'a> {
2274    visible_heads: &'a [CommitId],
2275    root: &'a CommitId,
2276}
2277
2278impl VisibilityResolutionContext<'_> {
2279    /// Resolves expression tree as set.
2280    fn resolve(&self, expression: &ResolvedRevsetExpression) -> ResolvedExpression {
2281        match expression {
2282            RevsetExpression::None => ResolvedExpression::Commits(vec![]),
2283            RevsetExpression::All => self.resolve_all(),
2284            RevsetExpression::VisibleHeads => self.resolve_visible_heads(),
2285            RevsetExpression::Root => self.resolve_root(),
2286            RevsetExpression::Commits(commit_ids) => {
2287                ResolvedExpression::Commits(commit_ids.clone())
2288            }
2289            RevsetExpression::CommitRef(commit_ref) => match *commit_ref {},
2290            RevsetExpression::Ancestors { heads, generation } => ResolvedExpression::Ancestors {
2291                heads: self.resolve(heads).into(),
2292                generation: generation.clone(),
2293            },
2294            RevsetExpression::Descendants { roots, generation } => ResolvedExpression::DagRange {
2295                roots: self.resolve(roots).into(),
2296                heads: self.resolve_visible_heads().into(),
2297                generation_from_roots: generation.clone(),
2298            },
2299            RevsetExpression::Range {
2300                roots,
2301                heads,
2302                generation,
2303            } => ResolvedExpression::Range {
2304                roots: self.resolve(roots).into(),
2305                heads: self.resolve(heads).into(),
2306                generation: generation.clone(),
2307            },
2308            RevsetExpression::DagRange { roots, heads } => ResolvedExpression::DagRange {
2309                roots: self.resolve(roots).into(),
2310                heads: self.resolve(heads).into(),
2311                generation_from_roots: GENERATION_RANGE_FULL,
2312            },
2313            RevsetExpression::Reachable { sources, domain } => ResolvedExpression::Reachable {
2314                sources: self.resolve(sources).into(),
2315                domain: self.resolve(domain).into(),
2316            },
2317            RevsetExpression::Heads(candidates) => {
2318                ResolvedExpression::Heads(self.resolve(candidates).into())
2319            }
2320            RevsetExpression::Roots(candidates) => {
2321                ResolvedExpression::Roots(self.resolve(candidates).into())
2322            }
2323            RevsetExpression::ForkPoint(expression) => {
2324                ResolvedExpression::ForkPoint(self.resolve(expression).into())
2325            }
2326            RevsetExpression::Latest { candidates, count } => ResolvedExpression::Latest {
2327                candidates: self.resolve(candidates).into(),
2328                count: *count,
2329            },
2330            RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
2331                // Top-level filter without intersection: e.g. "~author(_)" is represented as
2332                // `AsFilter(NotIn(Filter(Author(_))))`.
2333                ResolvedExpression::FilterWithin {
2334                    candidates: self.resolve_all().into(),
2335                    predicate: self.resolve_predicate(expression),
2336                }
2337            }
2338            RevsetExpression::AtOperation { operation, .. } => match *operation {},
2339            RevsetExpression::WithinVisibility {
2340                candidates,
2341                visible_heads,
2342            } => {
2343                let context = VisibilityResolutionContext {
2344                    visible_heads,
2345                    root: self.root,
2346                };
2347                context.resolve(candidates)
2348            }
2349            RevsetExpression::Coalesce(expression1, expression2) => ResolvedExpression::Coalesce(
2350                self.resolve(expression1).into(),
2351                self.resolve(expression2).into(),
2352            ),
2353            // present(x) is noop if x doesn't contain any commit refs.
2354            RevsetExpression::Present(candidates) => self.resolve(candidates),
2355            RevsetExpression::NotIn(complement) => ResolvedExpression::Difference(
2356                self.resolve_all().into(),
2357                self.resolve(complement).into(),
2358            ),
2359            RevsetExpression::Union(expression1, expression2) => ResolvedExpression::Union(
2360                self.resolve(expression1).into(),
2361                self.resolve(expression2).into(),
2362            ),
2363            RevsetExpression::Intersection(expression1, expression2) => {
2364                match expression2.as_ref() {
2365                    RevsetExpression::Filter(_) | RevsetExpression::AsFilter(_) => {
2366                        ResolvedExpression::FilterWithin {
2367                            candidates: self.resolve(expression1).into(),
2368                            predicate: self.resolve_predicate(expression2),
2369                        }
2370                    }
2371                    _ => ResolvedExpression::Intersection(
2372                        self.resolve(expression1).into(),
2373                        self.resolve(expression2).into(),
2374                    ),
2375                }
2376            }
2377            RevsetExpression::Difference(expression1, expression2) => {
2378                ResolvedExpression::Difference(
2379                    self.resolve(expression1).into(),
2380                    self.resolve(expression2).into(),
2381                )
2382            }
2383        }
2384    }
2385
2386    fn resolve_all(&self) -> ResolvedExpression {
2387        // Since `all()` does not include hidden commits, some of the logical
2388        // transformation rules may subtly change the evaluated set. For example,
2389        // `all() & x` is not `x` if `x` is hidden. This wouldn't matter in practice,
2390        // but if it does, the heads set could be extended to include the commits
2391        // (and `remote_bookmarks()`) specified in the revset expression. Alternatively,
2392        // some optimization rules could be removed, but that means `author(_) & x`
2393        // would have to test `::visible_heads() & x`.
2394        ResolvedExpression::Ancestors {
2395            heads: self.resolve_visible_heads().into(),
2396            generation: GENERATION_RANGE_FULL,
2397        }
2398    }
2399
2400    fn resolve_visible_heads(&self) -> ResolvedExpression {
2401        ResolvedExpression::Commits(self.visible_heads.to_owned())
2402    }
2403
2404    fn resolve_root(&self) -> ResolvedExpression {
2405        ResolvedExpression::Commits(vec![self.root.to_owned()])
2406    }
2407
2408    /// Resolves expression tree as filter predicate.
2409    ///
2410    /// For filter expression, this never inserts a hidden `all()` since a
2411    /// filter predicate doesn't need to produce revisions to walk.
2412    fn resolve_predicate(
2413        &self,
2414        expression: &ResolvedRevsetExpression,
2415    ) -> ResolvedPredicateExpression {
2416        match expression {
2417            RevsetExpression::None
2418            | RevsetExpression::All
2419            | RevsetExpression::VisibleHeads
2420            | RevsetExpression::Root
2421            | RevsetExpression::Commits(_)
2422            | RevsetExpression::CommitRef(_)
2423            | RevsetExpression::Ancestors { .. }
2424            | RevsetExpression::Descendants { .. }
2425            | RevsetExpression::Range { .. }
2426            | RevsetExpression::DagRange { .. }
2427            | RevsetExpression::Reachable { .. }
2428            | RevsetExpression::Heads(_)
2429            | RevsetExpression::Roots(_)
2430            | RevsetExpression::ForkPoint(_)
2431            | RevsetExpression::Latest { .. } => {
2432                ResolvedPredicateExpression::Set(self.resolve(expression).into())
2433            }
2434            RevsetExpression::Filter(predicate) => {
2435                ResolvedPredicateExpression::Filter(predicate.clone())
2436            }
2437            RevsetExpression::AsFilter(candidates) => self.resolve_predicate(candidates),
2438            RevsetExpression::AtOperation { operation, .. } => match *operation {},
2439            // Filters should be intersected with all() within the at-op repo.
2440            RevsetExpression::WithinVisibility { .. } => {
2441                ResolvedPredicateExpression::Set(self.resolve(expression).into())
2442            }
2443            RevsetExpression::Coalesce(_, _) => {
2444                ResolvedPredicateExpression::Set(self.resolve(expression).into())
2445            }
2446            // present(x) is noop if x doesn't contain any commit refs.
2447            RevsetExpression::Present(candidates) => self.resolve_predicate(candidates),
2448            RevsetExpression::NotIn(complement) => {
2449                ResolvedPredicateExpression::NotIn(self.resolve_predicate(complement).into())
2450            }
2451            RevsetExpression::Union(expression1, expression2) => {
2452                let predicate1 = self.resolve_predicate(expression1);
2453                let predicate2 = self.resolve_predicate(expression2);
2454                ResolvedPredicateExpression::Union(predicate1.into(), predicate2.into())
2455            }
2456            // Intersection of filters should have been substituted by optimize().
2457            // If it weren't, just fall back to the set evaluation path.
2458            RevsetExpression::Intersection(..) | RevsetExpression::Difference(..) => {
2459                ResolvedPredicateExpression::Set(self.resolve(expression).into())
2460            }
2461        }
2462    }
2463}
2464
2465pub trait Revset: fmt::Debug {
2466    /// Iterate in topological order with children before parents.
2467    fn iter<'a>(&self) -> Box<dyn Iterator<Item = Result<CommitId, RevsetEvaluationError>> + 'a>
2468    where
2469        Self: 'a;
2470
2471    /// Iterates commit/change id pairs in topological order.
2472    fn commit_change_ids<'a>(
2473        &self,
2474    ) -> Box<dyn Iterator<Item = Result<(CommitId, ChangeId), RevsetEvaluationError>> + 'a>
2475    where
2476        Self: 'a;
2477
2478    fn iter_graph<'a>(
2479        &self,
2480    ) -> Box<dyn Iterator<Item = Result<GraphNode<CommitId>, RevsetEvaluationError>> + 'a>
2481    where
2482        Self: 'a;
2483
2484    /// Returns true if iterator will emit no commit nor error.
2485    fn is_empty(&self) -> bool;
2486
2487    /// Inclusive lower bound and, optionally, inclusive upper bound of how many
2488    /// commits are in the revset. The implementation can use its discretion as
2489    /// to how much effort should be put into the estimation, and how accurate
2490    /// the resulting estimate should be.
2491    fn count_estimate(&self) -> Result<(usize, Option<usize>), RevsetEvaluationError>;
2492
2493    /// Returns a closure that checks if a commit is contained within the
2494    /// revset.
2495    ///
2496    /// The implementation may construct and maintain any necessary internal
2497    /// context to optimize the performance of the check.
2498    fn containing_fn<'a>(&self) -> Box<RevsetContainingFn<'a>>
2499    where
2500        Self: 'a;
2501}
2502
2503/// Function that checks if a commit is contained within the revset.
2504pub type RevsetContainingFn<'a> = dyn Fn(&CommitId) -> Result<bool, RevsetEvaluationError> + 'a;
2505
2506pub trait RevsetIteratorExt<I> {
2507    fn commits(self, store: &Arc<Store>) -> RevsetCommitIterator<I>;
2508}
2509
2510impl<I: Iterator<Item = Result<CommitId, RevsetEvaluationError>>> RevsetIteratorExt<I> for I {
2511    fn commits(self, store: &Arc<Store>) -> RevsetCommitIterator<I> {
2512        RevsetCommitIterator {
2513            iter: self,
2514            store: store.clone(),
2515        }
2516    }
2517}
2518
2519pub struct RevsetCommitIterator<I> {
2520    store: Arc<Store>,
2521    iter: I,
2522}
2523
2524impl<I: Iterator<Item = Result<CommitId, RevsetEvaluationError>>> Iterator
2525    for RevsetCommitIterator<I>
2526{
2527    type Item = Result<Commit, RevsetEvaluationError>;
2528
2529    fn next(&mut self) -> Option<Self::Item> {
2530        self.iter.next().map(|commit_id| {
2531            let commit_id = commit_id?;
2532            self.store
2533                .get_commit(&commit_id)
2534                .map_err(RevsetEvaluationError::Backend)
2535        })
2536    }
2537}
2538
2539/// A set of extensions for revset evaluation.
2540pub struct RevsetExtensions {
2541    symbol_resolvers: Vec<Box<dyn SymbolResolverExtension>>,
2542    function_map: HashMap<&'static str, RevsetFunction>,
2543}
2544
2545impl Default for RevsetExtensions {
2546    fn default() -> Self {
2547        Self::new()
2548    }
2549}
2550
2551impl RevsetExtensions {
2552    pub fn new() -> Self {
2553        Self {
2554            symbol_resolvers: vec![],
2555            function_map: BUILTIN_FUNCTION_MAP.clone(),
2556        }
2557    }
2558
2559    pub fn symbol_resolvers(&self) -> &[impl AsRef<dyn SymbolResolverExtension> + use<>] {
2560        &self.symbol_resolvers
2561    }
2562
2563    pub fn add_symbol_resolver(&mut self, symbol_resolver: Box<dyn SymbolResolverExtension>) {
2564        self.symbol_resolvers.push(symbol_resolver);
2565    }
2566
2567    pub fn add_custom_function(&mut self, name: &'static str, func: RevsetFunction) {
2568        match self.function_map.entry(name) {
2569            hash_map::Entry::Occupied(_) => {
2570                panic!("Conflict registering revset function '{name}'")
2571            }
2572            hash_map::Entry::Vacant(v) => v.insert(func),
2573        };
2574    }
2575}
2576
2577/// Information needed to parse revset expression.
2578#[derive(Clone)]
2579pub struct RevsetParseContext<'a> {
2580    pub aliases_map: &'a RevsetAliasesMap,
2581    pub local_variables: HashMap<&'a str, ExpressionNode<'a>>,
2582    pub user_email: &'a str,
2583    pub date_pattern_context: DatePatternContext,
2584    pub extensions: &'a RevsetExtensions,
2585    pub workspace: Option<RevsetWorkspaceContext<'a>>,
2586}
2587
2588impl<'a> RevsetParseContext<'a> {
2589    fn to_lowering_context(&self) -> LoweringContext<'a> {
2590        let RevsetParseContext {
2591            aliases_map: _,
2592            local_variables: _,
2593            user_email,
2594            date_pattern_context,
2595            extensions,
2596            workspace,
2597        } = *self;
2598        LoweringContext {
2599            user_email,
2600            date_pattern_context,
2601            extensions,
2602            workspace,
2603        }
2604    }
2605}
2606
2607/// Information needed to transform revset AST into `UserRevsetExpression`.
2608#[derive(Clone)]
2609pub struct LoweringContext<'a> {
2610    user_email: &'a str,
2611    date_pattern_context: DatePatternContext,
2612    extensions: &'a RevsetExtensions,
2613    workspace: Option<RevsetWorkspaceContext<'a>>,
2614}
2615
2616impl<'a> LoweringContext<'a> {
2617    pub fn user_email(&self) -> &'a str {
2618        self.user_email
2619    }
2620
2621    pub fn date_pattern_context(&self) -> &DatePatternContext {
2622        &self.date_pattern_context
2623    }
2624
2625    pub fn symbol_resolvers(&self) -> &'a [impl AsRef<dyn SymbolResolverExtension> + use<>] {
2626        self.extensions.symbol_resolvers()
2627    }
2628}
2629
2630/// Workspace information needed to parse revset expression.
2631#[derive(Clone, Copy, Debug)]
2632pub struct RevsetWorkspaceContext<'a> {
2633    pub path_converter: &'a RepoPathUiConverter,
2634    pub workspace_name: &'a WorkspaceName,
2635}
2636
2637/// Formats a string as symbol by quoting and escaping it if necessary.
2638///
2639/// Note that symbols may be substituted to user aliases. Use
2640/// [`format_string()`] to ensure that the provided string is resolved as a
2641/// tag/bookmark name, commit/change ID prefix, etc.
2642pub fn format_symbol(literal: &str) -> String {
2643    if revset_parser::is_identifier(literal) {
2644        literal.to_string()
2645    } else {
2646        format_string(literal)
2647    }
2648}
2649
2650/// Formats a string by quoting and escaping it.
2651pub fn format_string(literal: &str) -> String {
2652    format!(r#""{}""#, dsl_util::escape_string(literal))
2653}
2654
2655/// Formats a `name@remote` symbol, applies quoting and escaping if necessary.
2656pub fn format_remote_symbol(name: &str, remote: &str) -> String {
2657    let name = format_symbol(name);
2658    let remote = format_symbol(remote);
2659    format!("{name}@{remote}")
2660}
2661
2662#[cfg(test)]
2663mod tests {
2664    use std::path::PathBuf;
2665
2666    use assert_matches::assert_matches;
2667
2668    use super::*;
2669
2670    fn parse(revset_str: &str) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
2671        parse_with_aliases(revset_str, [] as [(&str, &str); 0])
2672    }
2673
2674    fn parse_with_workspace(
2675        revset_str: &str,
2676        workspace_name: &WorkspaceName,
2677    ) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
2678        parse_with_aliases_and_workspace(revset_str, [] as [(&str, &str); 0], workspace_name)
2679    }
2680
2681    fn parse_with_aliases(
2682        revset_str: &str,
2683        aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
2684    ) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
2685        let mut aliases_map = RevsetAliasesMap::new();
2686        for (decl, defn) in aliases {
2687            aliases_map.insert(decl, defn).unwrap();
2688        }
2689        let context = RevsetParseContext {
2690            aliases_map: &aliases_map,
2691            local_variables: HashMap::new(),
2692            user_email: "test.user@example.com",
2693            date_pattern_context: chrono::Utc::now().fixed_offset().into(),
2694            extensions: &RevsetExtensions::default(),
2695            workspace: None,
2696        };
2697        super::parse(&mut RevsetDiagnostics::new(), revset_str, &context)
2698    }
2699
2700    fn parse_with_aliases_and_workspace(
2701        revset_str: &str,
2702        aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
2703        workspace_name: &WorkspaceName,
2704    ) -> Result<Rc<UserRevsetExpression>, RevsetParseError> {
2705        // Set up pseudo context to resolve `workspace_name@` and `file(path)`
2706        let path_converter = RepoPathUiConverter::Fs {
2707            cwd: PathBuf::from("/"),
2708            base: PathBuf::from("/"),
2709        };
2710        let workspace_ctx = RevsetWorkspaceContext {
2711            path_converter: &path_converter,
2712            workspace_name,
2713        };
2714        let mut aliases_map = RevsetAliasesMap::new();
2715        for (decl, defn) in aliases {
2716            aliases_map.insert(decl, defn).unwrap();
2717        }
2718        let context = RevsetParseContext {
2719            aliases_map: &aliases_map,
2720            local_variables: HashMap::new(),
2721            user_email: "test.user@example.com",
2722            date_pattern_context: chrono::Utc::now().fixed_offset().into(),
2723            extensions: &RevsetExtensions::default(),
2724            workspace: Some(workspace_ctx),
2725        };
2726        super::parse(&mut RevsetDiagnostics::new(), revset_str, &context)
2727    }
2728
2729    fn parse_with_modifier(
2730        revset_str: &str,
2731    ) -> Result<(Rc<UserRevsetExpression>, Option<RevsetModifier>), RevsetParseError> {
2732        parse_with_aliases_and_modifier(revset_str, [] as [(&str, &str); 0])
2733    }
2734
2735    fn parse_with_aliases_and_modifier(
2736        revset_str: &str,
2737        aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
2738    ) -> Result<(Rc<UserRevsetExpression>, Option<RevsetModifier>), RevsetParseError> {
2739        let mut aliases_map = RevsetAliasesMap::new();
2740        for (decl, defn) in aliases {
2741            aliases_map.insert(decl, defn).unwrap();
2742        }
2743        let context = RevsetParseContext {
2744            aliases_map: &aliases_map,
2745            local_variables: HashMap::new(),
2746            user_email: "test.user@example.com",
2747            date_pattern_context: chrono::Utc::now().fixed_offset().into(),
2748            extensions: &RevsetExtensions::default(),
2749            workspace: None,
2750        };
2751        super::parse_with_modifier(&mut RevsetDiagnostics::new(), revset_str, &context)
2752    }
2753
2754    fn insta_settings() -> insta::Settings {
2755        let mut settings = insta::Settings::clone_current();
2756        // Collapse short "Thing(_,)" repeatedly to save vertical space and make
2757        // the output more readable.
2758        for _ in 0..4 {
2759            settings.add_filter(
2760                r"(?x)
2761                \b([A-Z]\w*)\(\n
2762                    \s*(.{1,60}),\n
2763                \s*\)",
2764                "$1($2)",
2765            );
2766        }
2767        settings
2768    }
2769
2770    #[test]
2771    #[expect(clippy::redundant_clone)] // allow symbol.clone()
2772    fn test_revset_expression_building() {
2773        let settings = insta_settings();
2774        let _guard = settings.bind_to_scope();
2775        let current_wc = UserRevsetExpression::working_copy(WorkspaceName::DEFAULT.to_owned());
2776        let foo_symbol = UserRevsetExpression::symbol("foo".to_string());
2777        let bar_symbol = UserRevsetExpression::symbol("bar".to_string());
2778        let baz_symbol = UserRevsetExpression::symbol("baz".to_string());
2779
2780        insta::assert_debug_snapshot!(
2781            current_wc,
2782            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
2783        insta::assert_debug_snapshot!(
2784            current_wc.heads(),
2785            @r#"Heads(CommitRef(WorkingCopy(WorkspaceNameBuf("default"))))"#);
2786        insta::assert_debug_snapshot!(
2787            current_wc.roots(),
2788            @r#"Roots(CommitRef(WorkingCopy(WorkspaceNameBuf("default"))))"#);
2789        insta::assert_debug_snapshot!(
2790            current_wc.parents(), @r#"
2791        Ancestors {
2792            heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2793            generation: 1..2,
2794        }
2795        "#);
2796        insta::assert_debug_snapshot!(
2797            current_wc.ancestors(), @r#"
2798        Ancestors {
2799            heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2800            generation: 0..18446744073709551615,
2801        }
2802        "#);
2803        insta::assert_debug_snapshot!(
2804            foo_symbol.children(), @r#"
2805        Descendants {
2806            roots: CommitRef(Symbol("foo")),
2807            generation: 1..2,
2808        }
2809        "#);
2810        insta::assert_debug_snapshot!(
2811            foo_symbol.descendants(), @r#"
2812        Descendants {
2813            roots: CommitRef(Symbol("foo")),
2814            generation: 0..18446744073709551615,
2815        }
2816        "#);
2817        insta::assert_debug_snapshot!(
2818            foo_symbol.dag_range_to(&current_wc), @r#"
2819        DagRange {
2820            roots: CommitRef(Symbol("foo")),
2821            heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2822        }
2823        "#);
2824        insta::assert_debug_snapshot!(
2825            foo_symbol.connected(), @r#"
2826        DagRange {
2827            roots: CommitRef(Symbol("foo")),
2828            heads: CommitRef(Symbol("foo")),
2829        }
2830        "#);
2831        insta::assert_debug_snapshot!(
2832            foo_symbol.range(&current_wc), @r#"
2833        Range {
2834            roots: CommitRef(Symbol("foo")),
2835            heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2836            generation: 0..18446744073709551615,
2837        }
2838        "#);
2839        insta::assert_debug_snapshot!(
2840            foo_symbol.negated(),
2841            @r#"NotIn(CommitRef(Symbol("foo")))"#);
2842        insta::assert_debug_snapshot!(
2843            foo_symbol.union(&current_wc), @r#"
2844        Union(
2845            CommitRef(Symbol("foo")),
2846            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2847        )
2848        "#);
2849        insta::assert_debug_snapshot!(
2850            UserRevsetExpression::union_all(&[]),
2851            @"None");
2852        insta::assert_debug_snapshot!(
2853            RevsetExpression::union_all(&[current_wc.clone()]),
2854            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
2855        insta::assert_debug_snapshot!(
2856            RevsetExpression::union_all(&[current_wc.clone(), foo_symbol.clone()]),
2857            @r#"
2858        Union(
2859            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2860            CommitRef(Symbol("foo")),
2861        )
2862        "#);
2863        insta::assert_debug_snapshot!(
2864            RevsetExpression::union_all(&[
2865                current_wc.clone(),
2866                foo_symbol.clone(),
2867                bar_symbol.clone(),
2868            ]),
2869            @r#"
2870        Union(
2871            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2872            Union(
2873                CommitRef(Symbol("foo")),
2874                CommitRef(Symbol("bar")),
2875            ),
2876        )
2877        "#);
2878        insta::assert_debug_snapshot!(
2879            RevsetExpression::union_all(&[
2880                current_wc.clone(),
2881                foo_symbol.clone(),
2882                bar_symbol.clone(),
2883                baz_symbol.clone(),
2884            ]),
2885            @r#"
2886        Union(
2887            Union(
2888                CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2889                CommitRef(Symbol("foo")),
2890            ),
2891            Union(
2892                CommitRef(Symbol("bar")),
2893                CommitRef(Symbol("baz")),
2894            ),
2895        )
2896        "#);
2897        insta::assert_debug_snapshot!(
2898            foo_symbol.intersection(&current_wc), @r#"
2899        Intersection(
2900            CommitRef(Symbol("foo")),
2901            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2902        )
2903        "#);
2904        insta::assert_debug_snapshot!(
2905            foo_symbol.minus(&current_wc), @r#"
2906        Difference(
2907            CommitRef(Symbol("foo")),
2908            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2909        )
2910        "#);
2911        insta::assert_debug_snapshot!(
2912            UserRevsetExpression::coalesce(&[]),
2913            @"None");
2914        insta::assert_debug_snapshot!(
2915            RevsetExpression::coalesce(&[current_wc.clone()]),
2916            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
2917        insta::assert_debug_snapshot!(
2918            RevsetExpression::coalesce(&[current_wc.clone(), foo_symbol.clone()]),
2919            @r#"
2920        Coalesce(
2921            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2922            CommitRef(Symbol("foo")),
2923        )
2924        "#);
2925        insta::assert_debug_snapshot!(
2926            RevsetExpression::coalesce(&[
2927                current_wc.clone(),
2928                foo_symbol.clone(),
2929                bar_symbol.clone(),
2930            ]),
2931            @r#"
2932        Coalesce(
2933            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
2934            Coalesce(
2935                CommitRef(Symbol("foo")),
2936                CommitRef(Symbol("bar")),
2937            ),
2938        )
2939        "#);
2940    }
2941
2942    #[test]
2943    fn test_parse_revset() {
2944        let settings = insta_settings();
2945        let _guard = settings.bind_to_scope();
2946        let main_workspace_name = WorkspaceNameBuf::from("main");
2947        let other_workspace_name = WorkspaceNameBuf::from("other");
2948
2949        // Parse "@" (the current working copy)
2950        insta::assert_debug_snapshot!(
2951            parse("@").unwrap_err().kind(),
2952            @"WorkingCopyWithoutWorkspace");
2953        insta::assert_debug_snapshot!(
2954            parse("main@").unwrap(),
2955            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
2956        insta::assert_debug_snapshot!(
2957            parse_with_workspace("@", &main_workspace_name).unwrap(),
2958            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
2959        insta::assert_debug_snapshot!(
2960            parse_with_workspace("main@", &other_workspace_name).unwrap(),
2961            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
2962        // "@" in function argument must be quoted
2963        insta::assert_debug_snapshot!(
2964            parse("author_name(foo@)").unwrap_err().kind(),
2965            @r#"Expression("Expected expression of string pattern")"#);
2966        insta::assert_debug_snapshot!(
2967            parse(r#"author_name("foo@")"#).unwrap(),
2968            @r#"Filter(AuthorName(Substring("foo@")))"#);
2969        // Parse a single symbol
2970        insta::assert_debug_snapshot!(
2971            parse("foo").unwrap(),
2972            @r#"CommitRef(Symbol("foo"))"#);
2973        // Default arguments for *bookmarks() are all ""
2974        insta::assert_debug_snapshot!(
2975            parse("bookmarks()").unwrap(),
2976            @r#"CommitRef(Bookmarks(Substring("")))"#);
2977        // Default argument for tags() is ""
2978        insta::assert_debug_snapshot!(
2979            parse("tags()").unwrap(),
2980            @r#"CommitRef(Tags(Substring("")))"#);
2981        insta::assert_debug_snapshot!(parse("remote_bookmarks()").unwrap(), @r#"
2982        CommitRef(
2983            RemoteBookmarks {
2984                bookmark_pattern: Substring(""),
2985                remote_pattern: Substring(""),
2986                remote_ref_state: None,
2987            },
2988        )
2989        "#);
2990        insta::assert_debug_snapshot!(parse("tracked_remote_bookmarks()").unwrap(), @r#"
2991        CommitRef(
2992            RemoteBookmarks {
2993                bookmark_pattern: Substring(""),
2994                remote_pattern: Substring(""),
2995                remote_ref_state: Some(Tracked),
2996            },
2997        )
2998        "#);
2999        insta::assert_debug_snapshot!(parse("untracked_remote_bookmarks()").unwrap(), @r#"
3000        CommitRef(
3001            RemoteBookmarks {
3002                bookmark_pattern: Substring(""),
3003                remote_pattern: Substring(""),
3004                remote_ref_state: Some(New),
3005            },
3006        )
3007        "#);
3008        // Parse a quoted symbol
3009        insta::assert_debug_snapshot!(
3010            parse("'foo'").unwrap(),
3011            @r#"CommitRef(Symbol("foo"))"#);
3012        // Parse the "parents" operator
3013        insta::assert_debug_snapshot!(parse("foo-").unwrap(), @r#"
3014        Ancestors {
3015            heads: CommitRef(Symbol("foo")),
3016            generation: 1..2,
3017        }
3018        "#);
3019        // Parse the "children" operator
3020        insta::assert_debug_snapshot!(parse("foo+").unwrap(), @r#"
3021        Descendants {
3022            roots: CommitRef(Symbol("foo")),
3023            generation: 1..2,
3024        }
3025        "#);
3026        // Parse the "ancestors" operator
3027        insta::assert_debug_snapshot!(parse("::foo").unwrap(), @r#"
3028        Ancestors {
3029            heads: CommitRef(Symbol("foo")),
3030            generation: 0..18446744073709551615,
3031        }
3032        "#);
3033        // Parse the "descendants" operator
3034        insta::assert_debug_snapshot!(parse("foo::").unwrap(), @r#"
3035        Descendants {
3036            roots: CommitRef(Symbol("foo")),
3037            generation: 0..18446744073709551615,
3038        }
3039        "#);
3040        // Parse the "dag range" operator
3041        insta::assert_debug_snapshot!(parse("foo::bar").unwrap(), @r#"
3042        DagRange {
3043            roots: CommitRef(Symbol("foo")),
3044            heads: CommitRef(Symbol("bar")),
3045        }
3046        "#);
3047        // Parse the nullary "dag range" operator
3048        insta::assert_debug_snapshot!(parse("::").unwrap(), @"All");
3049        // Parse the "range" prefix operator
3050        insta::assert_debug_snapshot!(parse("..foo").unwrap(), @r#"
3051        Range {
3052            roots: Root,
3053            heads: CommitRef(Symbol("foo")),
3054            generation: 0..18446744073709551615,
3055        }
3056        "#);
3057        insta::assert_debug_snapshot!(parse("foo..").unwrap(), @r#"
3058        Range {
3059            roots: CommitRef(Symbol("foo")),
3060            heads: VisibleHeads,
3061            generation: 0..18446744073709551615,
3062        }
3063        "#);
3064        insta::assert_debug_snapshot!(parse("foo..bar").unwrap(), @r#"
3065        Range {
3066            roots: CommitRef(Symbol("foo")),
3067            heads: CommitRef(Symbol("bar")),
3068            generation: 0..18446744073709551615,
3069        }
3070        "#);
3071        // Parse the nullary "range" operator
3072        insta::assert_debug_snapshot!(parse("..").unwrap(), @r"
3073        Range {
3074            roots: Root,
3075            heads: VisibleHeads,
3076            generation: 0..18446744073709551615,
3077        }
3078        ");
3079        // Parse the "negate" operator
3080        insta::assert_debug_snapshot!(
3081            parse("~ foo").unwrap(),
3082            @r#"NotIn(CommitRef(Symbol("foo")))"#);
3083        // Parse the "intersection" operator
3084        insta::assert_debug_snapshot!(parse("foo & bar").unwrap(), @r#"
3085        Intersection(
3086            CommitRef(Symbol("foo")),
3087            CommitRef(Symbol("bar")),
3088        )
3089        "#);
3090        // Parse the "union" operator
3091        insta::assert_debug_snapshot!(parse("foo | bar").unwrap(), @r#"
3092        Union(
3093            CommitRef(Symbol("foo")),
3094            CommitRef(Symbol("bar")),
3095        )
3096        "#);
3097        // Parse the "difference" operator
3098        insta::assert_debug_snapshot!(parse("foo ~ bar").unwrap(), @r#"
3099        Difference(
3100            CommitRef(Symbol("foo")),
3101            CommitRef(Symbol("bar")),
3102        )
3103        "#);
3104    }
3105
3106    #[test]
3107    fn test_parse_revset_with_modifier() {
3108        let settings = insta_settings();
3109        let _guard = settings.bind_to_scope();
3110
3111        insta::assert_debug_snapshot!(
3112            parse_with_modifier("all:foo").unwrap(), @r#"
3113        (
3114            CommitRef(Symbol("foo")),
3115            Some(All),
3116        )
3117        "#);
3118
3119        // Top-level string pattern can't be parsed, which is an error anyway
3120        insta::assert_debug_snapshot!(
3121            parse_with_modifier(r#"exact:"foo""#).unwrap_err().kind(),
3122            @r#"NoSuchModifier("exact")"#);
3123    }
3124
3125    #[test]
3126    fn test_parse_string_pattern() {
3127        let settings = insta_settings();
3128        let _guard = settings.bind_to_scope();
3129
3130        insta::assert_debug_snapshot!(
3131            parse(r#"bookmarks("foo")"#).unwrap(),
3132            @r#"CommitRef(Bookmarks(Substring("foo")))"#);
3133        insta::assert_debug_snapshot!(
3134            parse(r#"bookmarks(exact:"foo")"#).unwrap(),
3135            @r#"CommitRef(Bookmarks(Exact("foo")))"#);
3136        insta::assert_debug_snapshot!(
3137            parse(r#"bookmarks(substring:"foo")"#).unwrap(),
3138            @r#"CommitRef(Bookmarks(Substring("foo")))"#);
3139        insta::assert_debug_snapshot!(
3140            parse(r#"bookmarks(bad:"foo")"#).unwrap_err().kind(),
3141            @r#"Expression("Invalid string pattern")"#);
3142        insta::assert_debug_snapshot!(
3143            parse(r#"bookmarks(exact::"foo")"#).unwrap_err().kind(),
3144            @r#"Expression("Expected expression of string pattern")"#);
3145        insta::assert_debug_snapshot!(
3146            parse(r#"bookmarks(exact:"foo"+)"#).unwrap_err().kind(),
3147            @r#"Expression("Expected expression of string pattern")"#);
3148
3149        insta::assert_debug_snapshot!(
3150            parse(r#"tags("foo")"#).unwrap(),
3151            @r#"CommitRef(Tags(Substring("foo")))"#);
3152        insta::assert_debug_snapshot!(
3153            parse(r#"tags(exact:"foo")"#).unwrap(),
3154            @r#"CommitRef(Tags(Exact("foo")))"#);
3155        insta::assert_debug_snapshot!(
3156            parse(r#"tags(substring:"foo")"#).unwrap(),
3157            @r#"CommitRef(Tags(Substring("foo")))"#);
3158        insta::assert_debug_snapshot!(
3159            parse(r#"tags(bad:"foo")"#).unwrap_err().kind(),
3160            @r#"Expression("Invalid string pattern")"#);
3161        insta::assert_debug_snapshot!(
3162            parse(r#"tags(exact::"foo")"#).unwrap_err().kind(),
3163            @r#"Expression("Expected expression of string pattern")"#);
3164        insta::assert_debug_snapshot!(
3165            parse(r#"tags(exact:"foo"+)"#).unwrap_err().kind(),
3166            @r#"Expression("Expected expression of string pattern")"#);
3167
3168        // String pattern isn't allowed at top level.
3169        assert_matches!(
3170            parse(r#"(exact:"foo")"#).unwrap_err().kind(),
3171            RevsetParseErrorKind::NotInfixOperator { .. }
3172        );
3173    }
3174
3175    #[test]
3176    fn test_parse_revset_function() {
3177        let settings = insta_settings();
3178        let _guard = settings.bind_to_scope();
3179
3180        insta::assert_debug_snapshot!(
3181            parse("parents(foo)").unwrap(), @r#"
3182        Ancestors {
3183            heads: CommitRef(Symbol("foo")),
3184            generation: 1..2,
3185        }
3186        "#);
3187        insta::assert_debug_snapshot!(
3188            parse("parents(\"foo\")").unwrap(), @r#"
3189        Ancestors {
3190            heads: CommitRef(Symbol("foo")),
3191            generation: 1..2,
3192        }
3193        "#);
3194        insta::assert_debug_snapshot!(
3195            parse("ancestors(parents(foo))").unwrap(), @r#"
3196        Ancestors {
3197            heads: Ancestors {
3198                heads: CommitRef(Symbol("foo")),
3199                generation: 1..2,
3200            },
3201            generation: 0..18446744073709551615,
3202        }
3203        "#);
3204        insta::assert_debug_snapshot!(
3205            parse("parents(foo,foo)").unwrap_err().kind(), @r#"
3206        InvalidFunctionArguments {
3207            name: "parents",
3208            message: "Expected 1 arguments",
3209        }
3210        "#);
3211        insta::assert_debug_snapshot!(
3212            parse("root()").unwrap(),
3213            @"Root");
3214        assert!(parse("root(a)").is_err());
3215        insta::assert_debug_snapshot!(
3216            parse(r#"description("")"#).unwrap(),
3217            @r#"Filter(Description(Substring("")))"#);
3218        insta::assert_debug_snapshot!(
3219            parse("description(foo)").unwrap(),
3220            @r#"Filter(Description(Substring("foo")))"#);
3221        insta::assert_debug_snapshot!(
3222            parse("description(visible_heads())").unwrap_err().kind(),
3223            @r#"Expression("Expected expression of string pattern")"#);
3224        insta::assert_debug_snapshot!(
3225            parse("description(\"(foo)\")").unwrap(),
3226            @r#"Filter(Description(Substring("(foo)")))"#);
3227        assert!(parse("mine(foo)").is_err());
3228        insta::assert_debug_snapshot!(
3229            parse_with_workspace("empty()", WorkspaceName::DEFAULT).unwrap(),
3230            @"NotIn(Filter(File(All)))");
3231        assert!(parse_with_workspace("empty(foo)", WorkspaceName::DEFAULT).is_err());
3232        assert!(parse_with_workspace("file()", WorkspaceName::DEFAULT).is_err());
3233        insta::assert_debug_snapshot!(
3234            parse_with_workspace("files(foo)", WorkspaceName::DEFAULT).unwrap(),
3235            @r#"Filter(File(Pattern(PrefixPath("foo"))))"#);
3236        insta::assert_debug_snapshot!(
3237            parse_with_workspace("files(all())", WorkspaceName::DEFAULT).unwrap(),
3238            @"Filter(File(All))");
3239        insta::assert_debug_snapshot!(
3240            parse_with_workspace(r#"files(file:"foo")"#, WorkspaceName::DEFAULT).unwrap(),
3241            @r#"Filter(File(Pattern(FilePath("foo"))))"#);
3242        insta::assert_debug_snapshot!(
3243            parse_with_workspace("files(foo|bar&baz)", WorkspaceName::DEFAULT).unwrap(), @r#"
3244        Filter(
3245            File(
3246                UnionAll(
3247                    [
3248                        Pattern(PrefixPath("foo")),
3249                        Intersection(
3250                            Pattern(PrefixPath("bar")),
3251                            Pattern(PrefixPath("baz")),
3252                        ),
3253                    ],
3254                ),
3255            ),
3256        )
3257        "#);
3258        insta::assert_debug_snapshot!(parse("signed()").unwrap(), @"Filter(Signed)");
3259    }
3260
3261    #[test]
3262    fn test_parse_revset_author_committer_functions() {
3263        let settings = insta_settings();
3264        let _guard = settings.bind_to_scope();
3265
3266        insta::assert_debug_snapshot!(
3267            parse("author(foo)").unwrap(), @r#"
3268        Union(
3269            Filter(AuthorName(Substring("foo"))),
3270            Filter(AuthorEmail(Substring("foo"))),
3271        )
3272        "#);
3273        insta::assert_debug_snapshot!(
3274            parse("author_name(foo)").unwrap(),
3275            @r#"Filter(AuthorName(Substring("foo")))"#);
3276        insta::assert_debug_snapshot!(
3277            parse("author_email(foo)").unwrap(),
3278            @r#"Filter(AuthorEmail(Substring("foo")))"#);
3279
3280        insta::assert_debug_snapshot!(
3281            parse("committer(foo)").unwrap(), @r#"
3282        Union(
3283            Filter(CommitterName(Substring("foo"))),
3284            Filter(CommitterEmail(Substring("foo"))),
3285        )
3286        "#);
3287        insta::assert_debug_snapshot!(
3288            parse("committer_name(foo)").unwrap(),
3289            @r#"Filter(CommitterName(Substring("foo")))"#);
3290        insta::assert_debug_snapshot!(
3291            parse("committer_email(foo)").unwrap(),
3292            @r#"Filter(CommitterEmail(Substring("foo")))"#);
3293
3294        insta::assert_debug_snapshot!(
3295            parse("mine()").unwrap(),
3296            @r#"Filter(AuthorEmail(ExactI("test.user@example.com")))"#);
3297    }
3298
3299    #[test]
3300    fn test_parse_revset_keyword_arguments() {
3301        let settings = insta_settings();
3302        let _guard = settings.bind_to_scope();
3303
3304        insta::assert_debug_snapshot!(
3305            parse("remote_bookmarks(remote=foo)").unwrap(), @r#"
3306        CommitRef(
3307            RemoteBookmarks {
3308                bookmark_pattern: Substring(""),
3309                remote_pattern: Substring("foo"),
3310                remote_ref_state: None,
3311            },
3312        )
3313        "#);
3314        insta::assert_debug_snapshot!(
3315            parse("remote_bookmarks(foo, remote=bar)").unwrap(), @r#"
3316        CommitRef(
3317            RemoteBookmarks {
3318                bookmark_pattern: Substring("foo"),
3319                remote_pattern: Substring("bar"),
3320                remote_ref_state: None,
3321            },
3322        )
3323        "#);
3324        insta::assert_debug_snapshot!(
3325            parse("tracked_remote_bookmarks(foo, remote=bar)").unwrap(), @r#"
3326        CommitRef(
3327            RemoteBookmarks {
3328                bookmark_pattern: Substring("foo"),
3329                remote_pattern: Substring("bar"),
3330                remote_ref_state: Some(Tracked),
3331            },
3332        )
3333        "#);
3334        insta::assert_debug_snapshot!(
3335            parse("untracked_remote_bookmarks(foo, remote=bar)").unwrap(), @r#"
3336        CommitRef(
3337            RemoteBookmarks {
3338                bookmark_pattern: Substring("foo"),
3339                remote_pattern: Substring("bar"),
3340                remote_ref_state: Some(New),
3341            },
3342        )
3343        "#);
3344        insta::assert_debug_snapshot!(
3345            parse(r#"remote_bookmarks(remote=foo, bar)"#).unwrap_err().kind(),
3346            @r#"
3347        InvalidFunctionArguments {
3348            name: "remote_bookmarks",
3349            message: "Positional argument follows keyword argument",
3350        }
3351        "#);
3352        insta::assert_debug_snapshot!(
3353            parse(r#"remote_bookmarks("", foo, remote=bar)"#).unwrap_err().kind(),
3354            @r#"
3355        InvalidFunctionArguments {
3356            name: "remote_bookmarks",
3357            message: "Got multiple values for keyword \"remote\"",
3358        }
3359        "#);
3360        insta::assert_debug_snapshot!(
3361            parse(r#"remote_bookmarks(remote=bar, remote=bar)"#).unwrap_err().kind(),
3362            @r#"
3363        InvalidFunctionArguments {
3364            name: "remote_bookmarks",
3365            message: "Got multiple values for keyword \"remote\"",
3366        }
3367        "#);
3368        insta::assert_debug_snapshot!(
3369            parse(r#"remote_bookmarks(unknown=bar)"#).unwrap_err().kind(),
3370            @r#"
3371        InvalidFunctionArguments {
3372            name: "remote_bookmarks",
3373            message: "Unexpected keyword argument \"unknown\"",
3374        }
3375        "#);
3376    }
3377
3378    #[test]
3379    fn test_expand_symbol_alias() {
3380        let settings = insta_settings();
3381        let _guard = settings.bind_to_scope();
3382
3383        insta::assert_debug_snapshot!(
3384            parse_with_aliases("AB|c", [("AB", "a|b")]).unwrap(), @r#"
3385        Union(
3386            Union(
3387                CommitRef(Symbol("a")),
3388                CommitRef(Symbol("b")),
3389            ),
3390            CommitRef(Symbol("c")),
3391        )
3392        "#);
3393
3394        // Alias can be substituted to string literal.
3395        insta::assert_debug_snapshot!(
3396            parse_with_aliases_and_workspace("files(A)", [("A", "a")], WorkspaceName::DEFAULT)
3397                .unwrap(),
3398            @r#"Filter(File(Pattern(PrefixPath("a"))))"#);
3399
3400        // Alias can be substituted to string pattern.
3401        insta::assert_debug_snapshot!(
3402            parse_with_aliases("author_name(A)", [("A", "a")]).unwrap(),
3403            @r#"Filter(AuthorName(Substring("a")))"#);
3404        // However, parentheses are required because top-level x:y is parsed as
3405        // program modifier.
3406        insta::assert_debug_snapshot!(
3407            parse_with_aliases("author_name(A)", [("A", "(exact:a)")]).unwrap(),
3408            @r#"Filter(AuthorName(Exact("a")))"#);
3409
3410        // Sub-expression alias cannot be substituted to modifier expression.
3411        insta::assert_debug_snapshot!(
3412            parse_with_aliases_and_modifier("A-", [("A", "all:a")]).unwrap_err().kind(),
3413            @r#"InAliasExpansion("A")"#);
3414    }
3415
3416    #[test]
3417    fn test_expand_function_alias() {
3418        let settings = insta_settings();
3419        let _guard = settings.bind_to_scope();
3420
3421        // Pass string literal as parameter.
3422        insta::assert_debug_snapshot!(
3423            parse_with_aliases("F(a)", [("F(x)", "author_name(x)|committer_name(x)")]).unwrap(),
3424            @r#"
3425        Union(
3426            Filter(AuthorName(Substring("a"))),
3427            Filter(CommitterName(Substring("a"))),
3428        )
3429        "#);
3430    }
3431
3432    #[test]
3433    fn test_optimize_subtree() {
3434        let settings = insta_settings();
3435        let _guard = settings.bind_to_scope();
3436
3437        // Check that transform_expression_bottom_up() never rewrites enum variant
3438        // (e.g. Range -> DagRange) nor reorders arguments unintentionally.
3439
3440        insta::assert_debug_snapshot!(
3441            optimize(parse("parents(bookmarks() & all())").unwrap()), @r#"
3442        Ancestors {
3443            heads: CommitRef(Bookmarks(Substring(""))),
3444            generation: 1..2,
3445        }
3446        "#);
3447        insta::assert_debug_snapshot!(
3448            optimize(parse("children(bookmarks() & all())").unwrap()), @r#"
3449        Descendants {
3450            roots: CommitRef(Bookmarks(Substring(""))),
3451            generation: 1..2,
3452        }
3453        "#);
3454        insta::assert_debug_snapshot!(
3455            optimize(parse("ancestors(bookmarks() & all())").unwrap()), @r#"
3456        Ancestors {
3457            heads: CommitRef(Bookmarks(Substring(""))),
3458            generation: 0..18446744073709551615,
3459        }
3460        "#);
3461        insta::assert_debug_snapshot!(
3462            optimize(parse("descendants(bookmarks() & all())").unwrap()), @r#"
3463        Descendants {
3464            roots: CommitRef(Bookmarks(Substring(""))),
3465            generation: 0..18446744073709551615,
3466        }
3467        "#);
3468
3469        insta::assert_debug_snapshot!(
3470            optimize(parse("(bookmarks() & all())..(all() & tags())").unwrap()), @r#"
3471        Range {
3472            roots: CommitRef(Bookmarks(Substring(""))),
3473            heads: CommitRef(Tags(Substring(""))),
3474            generation: 0..18446744073709551615,
3475        }
3476        "#);
3477        insta::assert_debug_snapshot!(
3478            optimize(parse("(bookmarks() & all())::(all() & tags())").unwrap()), @r#"
3479        DagRange {
3480            roots: CommitRef(Bookmarks(Substring(""))),
3481            heads: CommitRef(Tags(Substring(""))),
3482        }
3483        "#);
3484
3485        insta::assert_debug_snapshot!(
3486            optimize(parse("heads(bookmarks() & all())").unwrap()),
3487            @r#"Heads(CommitRef(Bookmarks(Substring(""))))"#);
3488        insta::assert_debug_snapshot!(
3489            optimize(parse("roots(bookmarks() & all())").unwrap()),
3490            @r#"Roots(CommitRef(Bookmarks(Substring(""))))"#);
3491
3492        insta::assert_debug_snapshot!(
3493            optimize(parse("latest(bookmarks() & all(), 2)").unwrap()), @r#"
3494        Latest {
3495            candidates: CommitRef(Bookmarks(Substring(""))),
3496            count: 2,
3497        }
3498        "#);
3499
3500        insta::assert_debug_snapshot!(
3501            optimize(parse("present(foo ~ bar)").unwrap()), @r#"
3502        Present(
3503            Difference(
3504                CommitRef(Symbol("foo")),
3505                CommitRef(Symbol("bar")),
3506            ),
3507        )
3508        "#);
3509        insta::assert_debug_snapshot!(
3510            optimize(parse("present(bookmarks() & all())").unwrap()),
3511            @r#"Present(CommitRef(Bookmarks(Substring(""))))"#);
3512
3513        insta::assert_debug_snapshot!(
3514            optimize(parse("at_operation(@-, bookmarks() & all())").unwrap()), @r#"
3515        AtOperation {
3516            operation: "@-",
3517            candidates: CommitRef(Bookmarks(Substring(""))),
3518        }
3519        "#);
3520        insta::assert_debug_snapshot!(
3521            optimize(Rc::new(RevsetExpression::WithinVisibility {
3522                candidates: parse("bookmarks() & all()").unwrap(),
3523                visible_heads: vec![CommitId::from_hex("012345")],
3524            })), @r#"
3525        WithinVisibility {
3526            candidates: CommitRef(Bookmarks(Substring(""))),
3527            visible_heads: [
3528                CommitId("012345"),
3529            ],
3530        }
3531        "#);
3532
3533        insta::assert_debug_snapshot!(
3534            optimize(parse("~bookmarks() & all()").unwrap()),
3535            @r#"NotIn(CommitRef(Bookmarks(Substring(""))))"#);
3536        insta::assert_debug_snapshot!(
3537            optimize(parse("(bookmarks() & all()) | (all() & tags())").unwrap()), @r#"
3538        Union(
3539            CommitRef(Bookmarks(Substring(""))),
3540            CommitRef(Tags(Substring(""))),
3541        )
3542        "#);
3543        insta::assert_debug_snapshot!(
3544            optimize(parse("(bookmarks() & all()) & (all() & tags())").unwrap()), @r#"
3545        Intersection(
3546            CommitRef(Bookmarks(Substring(""))),
3547            CommitRef(Tags(Substring(""))),
3548        )
3549        "#);
3550        insta::assert_debug_snapshot!(
3551            optimize(parse("(bookmarks() & all()) ~ (all() & tags())").unwrap()), @r#"
3552        Difference(
3553            CommitRef(Bookmarks(Substring(""))),
3554            CommitRef(Tags(Substring(""))),
3555        )
3556        "#);
3557    }
3558
3559    #[test]
3560    fn test_optimize_unchanged_subtree() {
3561        fn unwrap_union(
3562            expression: &UserRevsetExpression,
3563        ) -> (&Rc<UserRevsetExpression>, &Rc<UserRevsetExpression>) {
3564            match expression {
3565                RevsetExpression::Union(left, right) => (left, right),
3566                _ => panic!("unexpected expression: {expression:?}"),
3567            }
3568        }
3569
3570        // transform_expression_bottom_up() should not recreate tree unnecessarily.
3571        let parsed = parse("foo-").unwrap();
3572        let optimized = optimize(parsed.clone());
3573        assert!(Rc::ptr_eq(&parsed, &optimized));
3574
3575        let parsed = parse("bookmarks() | tags()").unwrap();
3576        let optimized = optimize(parsed.clone());
3577        assert!(Rc::ptr_eq(&parsed, &optimized));
3578
3579        let parsed = parse("bookmarks() & tags()").unwrap();
3580        let optimized = optimize(parsed.clone());
3581        assert!(Rc::ptr_eq(&parsed, &optimized));
3582
3583        // Only left subtree should be rewritten.
3584        let parsed = parse("(bookmarks() & all()) | tags()").unwrap();
3585        let optimized = optimize(parsed.clone());
3586        assert_matches!(
3587            unwrap_union(&optimized).0.as_ref(),
3588            RevsetExpression::CommitRef(RevsetCommitRef::Bookmarks(_))
3589        );
3590        assert!(Rc::ptr_eq(
3591            unwrap_union(&parsed).1,
3592            unwrap_union(&optimized).1
3593        ));
3594
3595        // Only right subtree should be rewritten.
3596        let parsed = parse("bookmarks() | (all() & tags())").unwrap();
3597        let optimized = optimize(parsed.clone());
3598        assert!(Rc::ptr_eq(
3599            unwrap_union(&parsed).0,
3600            unwrap_union(&optimized).0
3601        ));
3602        assert_matches!(
3603            unwrap_union(&optimized).1.as_ref(),
3604            RevsetExpression::CommitRef(RevsetCommitRef::Tags(_))
3605        );
3606    }
3607
3608    #[test]
3609    fn test_optimize_difference() {
3610        let settings = insta_settings();
3611        let _guard = settings.bind_to_scope();
3612
3613        insta::assert_debug_snapshot!(optimize(parse("foo & ~bar").unwrap()), @r#"
3614        Difference(
3615            CommitRef(Symbol("foo")),
3616            CommitRef(Symbol("bar")),
3617        )
3618        "#);
3619        insta::assert_debug_snapshot!(optimize(parse("~foo & bar").unwrap()), @r#"
3620        Difference(
3621            CommitRef(Symbol("bar")),
3622            CommitRef(Symbol("foo")),
3623        )
3624        "#);
3625        insta::assert_debug_snapshot!(optimize(parse("~foo & bar & ~baz").unwrap()), @r#"
3626        Difference(
3627            Difference(
3628                CommitRef(Symbol("bar")),
3629                CommitRef(Symbol("foo")),
3630            ),
3631            CommitRef(Symbol("baz")),
3632        )
3633        "#);
3634        insta::assert_debug_snapshot!(optimize(parse("(all() & ~foo) & bar").unwrap()), @r#"
3635        Difference(
3636            CommitRef(Symbol("bar")),
3637            CommitRef(Symbol("foo")),
3638        )
3639        "#);
3640
3641        // Binary difference operation should go through the same optimization passes.
3642        insta::assert_debug_snapshot!(
3643            optimize(parse("all() ~ foo").unwrap()),
3644            @r#"NotIn(CommitRef(Symbol("foo")))"#);
3645        insta::assert_debug_snapshot!(optimize(parse("foo ~ bar").unwrap()), @r#"
3646        Difference(
3647            CommitRef(Symbol("foo")),
3648            CommitRef(Symbol("bar")),
3649        )
3650        "#);
3651        insta::assert_debug_snapshot!(optimize(parse("(all() ~ foo) & bar").unwrap()), @r#"
3652        Difference(
3653            CommitRef(Symbol("bar")),
3654            CommitRef(Symbol("foo")),
3655        )
3656        "#);
3657
3658        // Range expression.
3659        insta::assert_debug_snapshot!(optimize(parse("::foo & ~::bar").unwrap()), @r#"
3660        Range {
3661            roots: CommitRef(Symbol("bar")),
3662            heads: CommitRef(Symbol("foo")),
3663            generation: 0..18446744073709551615,
3664        }
3665        "#);
3666        insta::assert_debug_snapshot!(optimize(parse("~::foo & ::bar").unwrap()), @r#"
3667        Range {
3668            roots: CommitRef(Symbol("foo")),
3669            heads: CommitRef(Symbol("bar")),
3670            generation: 0..18446744073709551615,
3671        }
3672        "#);
3673        insta::assert_debug_snapshot!(optimize(parse("foo..").unwrap()), @r#"
3674        Range {
3675            roots: CommitRef(Symbol("foo")),
3676            heads: VisibleHeads,
3677            generation: 0..18446744073709551615,
3678        }
3679        "#);
3680        insta::assert_debug_snapshot!(optimize(parse("foo..bar").unwrap()), @r#"
3681        Range {
3682            roots: CommitRef(Symbol("foo")),
3683            heads: CommitRef(Symbol("bar")),
3684            generation: 0..18446744073709551615,
3685        }
3686        "#);
3687
3688        // Double/triple negates.
3689        insta::assert_debug_snapshot!(optimize(parse("foo & ~~bar").unwrap()), @r#"
3690        Intersection(
3691            CommitRef(Symbol("foo")),
3692            CommitRef(Symbol("bar")),
3693        )
3694        "#);
3695        insta::assert_debug_snapshot!(optimize(parse("foo & ~~~bar").unwrap()), @r#"
3696        Difference(
3697            CommitRef(Symbol("foo")),
3698            CommitRef(Symbol("bar")),
3699        )
3700        "#);
3701        insta::assert_debug_snapshot!(optimize(parse("~(all() & ~foo) & bar").unwrap()), @r#"
3702        Intersection(
3703            CommitRef(Symbol("foo")),
3704            CommitRef(Symbol("bar")),
3705        )
3706        "#);
3707
3708        // Should be better than '(all() & ~foo) & (all() & ~bar)'.
3709        insta::assert_debug_snapshot!(optimize(parse("~foo & ~bar").unwrap()), @r#"
3710        Difference(
3711            NotIn(CommitRef(Symbol("foo"))),
3712            CommitRef(Symbol("bar")),
3713        )
3714        "#);
3715    }
3716
3717    #[test]
3718    fn test_optimize_not_in_ancestors() {
3719        let settings = insta_settings();
3720        let _guard = settings.bind_to_scope();
3721
3722        // '~(::foo)' is equivalent to 'foo..'.
3723        insta::assert_debug_snapshot!(optimize(parse("~(::foo)").unwrap()), @r#"
3724        Range {
3725            roots: CommitRef(Symbol("foo")),
3726            heads: VisibleHeads,
3727            generation: 0..18446744073709551615,
3728        }
3729        "#);
3730
3731        // '~(::foo-)' is equivalent to 'foo-..'.
3732        insta::assert_debug_snapshot!(optimize(parse("~(::foo-)").unwrap()), @r#"
3733        Range {
3734            roots: Ancestors {
3735                heads: CommitRef(Symbol("foo")),
3736                generation: 1..2,
3737            },
3738            heads: VisibleHeads,
3739            generation: 0..18446744073709551615,
3740        }
3741        "#);
3742        insta::assert_debug_snapshot!(optimize(parse("~(::foo--)").unwrap()), @r#"
3743        Range {
3744            roots: Ancestors {
3745                heads: CommitRef(Symbol("foo")),
3746                generation: 2..3,
3747            },
3748            heads: VisibleHeads,
3749            generation: 0..18446744073709551615,
3750        }
3751        "#);
3752
3753        // Bounded ancestors shouldn't be substituted.
3754        insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo, 1)").unwrap()), @r#"
3755        NotIn(
3756            Ancestors {
3757                heads: CommitRef(Symbol("foo")),
3758                generation: 0..1,
3759            },
3760        )
3761        "#);
3762        insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo-, 1)").unwrap()), @r#"
3763        NotIn(
3764            Ancestors {
3765                heads: CommitRef(Symbol("foo")),
3766                generation: 1..2,
3767            },
3768        )
3769        "#);
3770    }
3771
3772    #[test]
3773    fn test_optimize_filter_difference() {
3774        let settings = insta_settings();
3775        let _guard = settings.bind_to_scope();
3776
3777        // '~empty()' -> '~~file(*)' -> 'file(*)'
3778        insta::assert_debug_snapshot!(optimize(parse("~empty()").unwrap()), @"Filter(File(All))");
3779
3780        // '& baz' can be moved into the filter node, and form a difference node.
3781        insta::assert_debug_snapshot!(
3782            optimize(parse("(author_name(foo) & ~bar) & baz").unwrap()), @r#"
3783        Intersection(
3784            Difference(
3785                CommitRef(Symbol("baz")),
3786                CommitRef(Symbol("bar")),
3787            ),
3788            Filter(AuthorName(Substring("foo"))),
3789        )
3790        "#);
3791
3792        // '~set & filter()' shouldn't be substituted.
3793        insta::assert_debug_snapshot!(
3794            optimize(parse("~foo & author_name(bar)").unwrap()), @r#"
3795        Intersection(
3796            NotIn(CommitRef(Symbol("foo"))),
3797            Filter(AuthorName(Substring("bar"))),
3798        )
3799        "#);
3800        insta::assert_debug_snapshot!(
3801            optimize(parse("~foo & (author_name(bar) | baz)").unwrap()), @r#"
3802        Intersection(
3803            NotIn(CommitRef(Symbol("foo"))),
3804            AsFilter(
3805                Union(
3806                    Filter(AuthorName(Substring("bar"))),
3807                    CommitRef(Symbol("baz")),
3808                ),
3809            ),
3810        )
3811        "#);
3812
3813        // Filter should be moved right of the intersection.
3814        insta::assert_debug_snapshot!(
3815            optimize(parse("author_name(foo) ~ bar").unwrap()), @r#"
3816        Intersection(
3817            NotIn(CommitRef(Symbol("bar"))),
3818            Filter(AuthorName(Substring("foo"))),
3819        )
3820        "#);
3821    }
3822
3823    #[test]
3824    fn test_optimize_filter_intersection() {
3825        let settings = insta_settings();
3826        let _guard = settings.bind_to_scope();
3827
3828        insta::assert_debug_snapshot!(
3829            optimize(parse("author_name(foo)").unwrap()),
3830            @r#"Filter(AuthorName(Substring("foo")))"#);
3831
3832        insta::assert_debug_snapshot!(optimize(parse("foo & description(bar)").unwrap()), @r#"
3833        Intersection(
3834            CommitRef(Symbol("foo")),
3835            Filter(Description(Substring("bar"))),
3836        )
3837        "#);
3838        insta::assert_debug_snapshot!(optimize(parse("author_name(foo) & bar").unwrap()), @r#"
3839        Intersection(
3840            CommitRef(Symbol("bar")),
3841            Filter(AuthorName(Substring("foo"))),
3842        )
3843        "#);
3844        insta::assert_debug_snapshot!(
3845            optimize(parse("author_name(foo) & committer_name(bar)").unwrap()), @r#"
3846        Intersection(
3847            Filter(AuthorName(Substring("foo"))),
3848            Filter(CommitterName(Substring("bar"))),
3849        )
3850        "#);
3851
3852        insta::assert_debug_snapshot!(
3853            optimize(parse("foo & description(bar) & author_name(baz)").unwrap()), @r#"
3854        Intersection(
3855            Intersection(
3856                CommitRef(Symbol("foo")),
3857                Filter(Description(Substring("bar"))),
3858            ),
3859            Filter(AuthorName(Substring("baz"))),
3860        )
3861        "#);
3862        insta::assert_debug_snapshot!(
3863            optimize(parse("committer_name(foo) & bar & author_name(baz)").unwrap()), @r#"
3864        Intersection(
3865            Intersection(
3866                CommitRef(Symbol("bar")),
3867                Filter(CommitterName(Substring("foo"))),
3868            ),
3869            Filter(AuthorName(Substring("baz"))),
3870        )
3871        "#);
3872        insta::assert_debug_snapshot!(
3873            optimize(parse_with_workspace(
3874                "committer_name(foo) & files(bar) & baz",
3875                WorkspaceName::DEFAULT).unwrap(),
3876            ), @r#"
3877        Intersection(
3878            Intersection(
3879                CommitRef(Symbol("baz")),
3880                Filter(CommitterName(Substring("foo"))),
3881            ),
3882            Filter(File(Pattern(PrefixPath("bar")))),
3883        )
3884        "#);
3885        insta::assert_debug_snapshot!(
3886            optimize(parse_with_workspace(
3887                "committer_name(foo) & files(bar) & author_name(baz)",
3888                WorkspaceName::DEFAULT).unwrap(),
3889            ), @r#"
3890        Intersection(
3891            Intersection(
3892                Filter(CommitterName(Substring("foo"))),
3893                Filter(File(Pattern(PrefixPath("bar")))),
3894            ),
3895            Filter(AuthorName(Substring("baz"))),
3896        )
3897        "#);
3898        insta::assert_debug_snapshot!(
3899            optimize(parse_with_workspace(
3900                "foo & files(bar) & baz",
3901                WorkspaceName::DEFAULT).unwrap(),
3902            ), @r#"
3903        Intersection(
3904            Intersection(
3905                CommitRef(Symbol("foo")),
3906                CommitRef(Symbol("baz")),
3907            ),
3908            Filter(File(Pattern(PrefixPath("bar")))),
3909        )
3910        "#);
3911
3912        insta::assert_debug_snapshot!(
3913            optimize(parse("foo & description(bar) & author_name(baz) & qux").unwrap()), @r#"
3914        Intersection(
3915            Intersection(
3916                Intersection(
3917                    CommitRef(Symbol("foo")),
3918                    CommitRef(Symbol("qux")),
3919                ),
3920                Filter(Description(Substring("bar"))),
3921            ),
3922            Filter(AuthorName(Substring("baz"))),
3923        )
3924        "#);
3925        insta::assert_debug_snapshot!(
3926            optimize(parse("foo & description(bar) & parents(author_name(baz)) & qux").unwrap()),
3927            @r#"
3928        Intersection(
3929            Intersection(
3930                Intersection(
3931                    CommitRef(Symbol("foo")),
3932                    Ancestors {
3933                        heads: Filter(AuthorName(Substring("baz"))),
3934                        generation: 1..2,
3935                    },
3936                ),
3937                CommitRef(Symbol("qux")),
3938            ),
3939            Filter(Description(Substring("bar"))),
3940        )
3941        "#);
3942        insta::assert_debug_snapshot!(
3943            optimize(parse("foo & description(bar) & parents(author_name(baz) & qux)").unwrap()),
3944            @r#"
3945        Intersection(
3946            Intersection(
3947                CommitRef(Symbol("foo")),
3948                Ancestors {
3949                    heads: Intersection(
3950                        CommitRef(Symbol("qux")),
3951                        Filter(AuthorName(Substring("baz"))),
3952                    ),
3953                    generation: 1..2,
3954                },
3955            ),
3956            Filter(Description(Substring("bar"))),
3957        )
3958        "#);
3959
3960        // Symbols have to be pushed down to the innermost filter node.
3961        insta::assert_debug_snapshot!(
3962            optimize(parse("(a & author_name(A)) & (b & author_name(B)) & (c & author_name(C))").unwrap()),
3963            @r#"
3964        Intersection(
3965            Intersection(
3966                Intersection(
3967                    Intersection(
3968                        Intersection(
3969                            CommitRef(Symbol("a")),
3970                            CommitRef(Symbol("b")),
3971                        ),
3972                        CommitRef(Symbol("c")),
3973                    ),
3974                    Filter(AuthorName(Substring("A"))),
3975                ),
3976                Filter(AuthorName(Substring("B"))),
3977            ),
3978            Filter(AuthorName(Substring("C"))),
3979        )
3980        "#);
3981        insta::assert_debug_snapshot!(
3982            optimize(parse("(a & author_name(A)) & ((b & author_name(B)) & (c & author_name(C))) & d").unwrap()),
3983            @r#"
3984        Intersection(
3985            Intersection(
3986                Intersection(
3987                    Intersection(
3988                        Intersection(
3989                            CommitRef(Symbol("a")),
3990                            Intersection(
3991                                CommitRef(Symbol("b")),
3992                                CommitRef(Symbol("c")),
3993                            ),
3994                        ),
3995                        CommitRef(Symbol("d")),
3996                    ),
3997                    Filter(AuthorName(Substring("A"))),
3998                ),
3999                Filter(AuthorName(Substring("B"))),
4000            ),
4001            Filter(AuthorName(Substring("C"))),
4002        )
4003        "#);
4004
4005        // 'all()' moves in to 'filter()' first, so 'A & filter()' can be found.
4006        insta::assert_debug_snapshot!(
4007            optimize(parse("foo & (all() & description(bar)) & (author_name(baz) & all())").unwrap()),
4008            @r#"
4009        Intersection(
4010            Intersection(
4011                CommitRef(Symbol("foo")),
4012                Filter(Description(Substring("bar"))),
4013            ),
4014            Filter(AuthorName(Substring("baz"))),
4015        )
4016        "#);
4017
4018        // Filter node shouldn't move across at_operation() boundary.
4019        insta::assert_debug_snapshot!(
4020            optimize(parse("author_name(foo) & bar & at_operation(@-, committer_name(baz))").unwrap()),
4021            @r#"
4022        Intersection(
4023            Intersection(
4024                CommitRef(Symbol("bar")),
4025                AtOperation {
4026                    operation: "@-",
4027                    candidates: Filter(CommitterName(Substring("baz"))),
4028                },
4029            ),
4030            Filter(AuthorName(Substring("foo"))),
4031        )
4032        "#);
4033    }
4034
4035    #[test]
4036    fn test_optimize_filter_subtree() {
4037        let settings = insta_settings();
4038        let _guard = settings.bind_to_scope();
4039
4040        insta::assert_debug_snapshot!(
4041            optimize(parse("(author_name(foo) | bar) & baz").unwrap()), @r#"
4042        Intersection(
4043            CommitRef(Symbol("baz")),
4044            AsFilter(
4045                Union(
4046                    Filter(AuthorName(Substring("foo"))),
4047                    CommitRef(Symbol("bar")),
4048                ),
4049            ),
4050        )
4051        "#);
4052
4053        insta::assert_debug_snapshot!(
4054            optimize(parse("(foo | committer_name(bar)) & description(baz) & qux").unwrap()), @r#"
4055        Intersection(
4056            Intersection(
4057                CommitRef(Symbol("qux")),
4058                AsFilter(
4059                    Union(
4060                        CommitRef(Symbol("foo")),
4061                        Filter(CommitterName(Substring("bar"))),
4062                    ),
4063                ),
4064            ),
4065            Filter(Description(Substring("baz"))),
4066        )
4067        "#);
4068
4069        insta::assert_debug_snapshot!(
4070            optimize(parse("(~present(author_name(foo) & bar) | baz) & qux").unwrap()), @r#"
4071        Intersection(
4072            CommitRef(Symbol("qux")),
4073            AsFilter(
4074                Union(
4075                    AsFilter(
4076                        NotIn(
4077                            AsFilter(
4078                                Present(
4079                                    Intersection(
4080                                        CommitRef(Symbol("bar")),
4081                                        Filter(AuthorName(Substring("foo"))),
4082                                    ),
4083                                ),
4084                            ),
4085                        ),
4086                    ),
4087                    CommitRef(Symbol("baz")),
4088                ),
4089            ),
4090        )
4091        "#);
4092
4093        // Symbols have to be pushed down to the innermost filter node.
4094        insta::assert_debug_snapshot!(
4095            optimize(parse(
4096                "(a & (author_name(A) | 0)) & (b & (author_name(B) | 1)) & (c & (author_name(C) | 2))").unwrap()),
4097            @r#"
4098        Intersection(
4099            Intersection(
4100                Intersection(
4101                    Intersection(
4102                        Intersection(
4103                            CommitRef(Symbol("a")),
4104                            CommitRef(Symbol("b")),
4105                        ),
4106                        CommitRef(Symbol("c")),
4107                    ),
4108                    AsFilter(
4109                        Union(
4110                            Filter(AuthorName(Substring("A"))),
4111                            CommitRef(Symbol("0")),
4112                        ),
4113                    ),
4114                ),
4115                AsFilter(
4116                    Union(
4117                        Filter(AuthorName(Substring("B"))),
4118                        CommitRef(Symbol("1")),
4119                    ),
4120                ),
4121            ),
4122            AsFilter(
4123                Union(
4124                    Filter(AuthorName(Substring("C"))),
4125                    CommitRef(Symbol("2")),
4126                ),
4127            ),
4128        )
4129        "#);
4130    }
4131
4132    #[test]
4133    fn test_optimize_ancestors() {
4134        let settings = insta_settings();
4135        let _guard = settings.bind_to_scope();
4136
4137        // Typical scenario: fold nested parents()
4138        insta::assert_debug_snapshot!(optimize(parse("foo--").unwrap()), @r#"
4139        Ancestors {
4140            heads: CommitRef(Symbol("foo")),
4141            generation: 2..3,
4142        }
4143        "#);
4144        insta::assert_debug_snapshot!(optimize(parse("::(foo---)").unwrap()), @r#"
4145        Ancestors {
4146            heads: CommitRef(Symbol("foo")),
4147            generation: 3..18446744073709551615,
4148        }
4149        "#);
4150        insta::assert_debug_snapshot!(optimize(parse("(::foo)---").unwrap()), @r#"
4151        Ancestors {
4152            heads: CommitRef(Symbol("foo")),
4153            generation: 3..18446744073709551615,
4154        }
4155        "#);
4156
4157        // 'foo-+' is not 'foo'.
4158        insta::assert_debug_snapshot!(optimize(parse("foo---+").unwrap()), @r#"
4159        Descendants {
4160            roots: Ancestors {
4161                heads: CommitRef(Symbol("foo")),
4162                generation: 3..4,
4163            },
4164            generation: 1..2,
4165        }
4166        "#);
4167
4168        // For 'roots..heads', heads can be folded.
4169        insta::assert_debug_snapshot!(optimize(parse("foo..(bar--)").unwrap()), @r#"
4170        Range {
4171            roots: CommitRef(Symbol("foo")),
4172            heads: CommitRef(Symbol("bar")),
4173            generation: 2..18446744073709551615,
4174        }
4175        "#);
4176        // roots can also be folded, and the range expression is reconstructed.
4177        insta::assert_debug_snapshot!(optimize(parse("(foo--)..(bar---)").unwrap()), @r#"
4178        Range {
4179            roots: Ancestors {
4180                heads: CommitRef(Symbol("foo")),
4181                generation: 2..3,
4182            },
4183            heads: CommitRef(Symbol("bar")),
4184            generation: 3..18446744073709551615,
4185        }
4186        "#);
4187        // Bounded ancestors shouldn't be substituted to range.
4188        insta::assert_debug_snapshot!(
4189            optimize(parse("~ancestors(foo, 2) & ::bar").unwrap()), @r#"
4190        Difference(
4191            Ancestors {
4192                heads: CommitRef(Symbol("bar")),
4193                generation: 0..18446744073709551615,
4194            },
4195            Ancestors {
4196                heads: CommitRef(Symbol("foo")),
4197                generation: 0..2,
4198            },
4199        )
4200        "#);
4201
4202        // If inner range is bounded by roots, it cannot be merged.
4203        // e.g. '..(foo..foo)' is equivalent to '..none()', not to '..foo'
4204        insta::assert_debug_snapshot!(optimize(parse("(foo..bar)--").unwrap()), @r#"
4205        Ancestors {
4206            heads: Range {
4207                roots: CommitRef(Symbol("foo")),
4208                heads: CommitRef(Symbol("bar")),
4209                generation: 0..18446744073709551615,
4210            },
4211            generation: 2..3,
4212        }
4213        "#);
4214        insta::assert_debug_snapshot!(optimize(parse("foo..(bar..baz)").unwrap()), @r#"
4215        Range {
4216            roots: CommitRef(Symbol("foo")),
4217            heads: Range {
4218                roots: CommitRef(Symbol("bar")),
4219                heads: CommitRef(Symbol("baz")),
4220                generation: 0..18446744073709551615,
4221            },
4222            generation: 0..18446744073709551615,
4223        }
4224        "#);
4225
4226        // Ancestors of empty generation range should be empty.
4227        insta::assert_debug_snapshot!(
4228            optimize(parse("ancestors(ancestors(foo), 0)").unwrap()), @r#"
4229        Ancestors {
4230            heads: CommitRef(Symbol("foo")),
4231            generation: 0..0,
4232        }
4233        "#
4234        );
4235        insta::assert_debug_snapshot!(
4236            optimize(parse("ancestors(ancestors(foo, 0))").unwrap()), @r#"
4237        Ancestors {
4238            heads: CommitRef(Symbol("foo")),
4239            generation: 0..0,
4240        }
4241        "#
4242        );
4243    }
4244
4245    #[test]
4246    fn test_optimize_descendants() {
4247        let settings = insta_settings();
4248        let _guard = settings.bind_to_scope();
4249
4250        // Typical scenario: fold nested children()
4251        insta::assert_debug_snapshot!(optimize(parse("foo++").unwrap()), @r#"
4252        Descendants {
4253            roots: CommitRef(Symbol("foo")),
4254            generation: 2..3,
4255        }
4256        "#);
4257        insta::assert_debug_snapshot!(optimize(parse("(foo+++)::").unwrap()), @r#"
4258        Descendants {
4259            roots: CommitRef(Symbol("foo")),
4260            generation: 3..18446744073709551615,
4261        }
4262        "#);
4263        insta::assert_debug_snapshot!(optimize(parse("(foo::)+++").unwrap()), @r#"
4264        Descendants {
4265            roots: CommitRef(Symbol("foo")),
4266            generation: 3..18446744073709551615,
4267        }
4268        "#);
4269
4270        // 'foo+-' is not 'foo'.
4271        insta::assert_debug_snapshot!(optimize(parse("foo+++-").unwrap()), @r#"
4272        Ancestors {
4273            heads: Descendants {
4274                roots: CommitRef(Symbol("foo")),
4275                generation: 3..4,
4276            },
4277            generation: 1..2,
4278        }
4279        "#);
4280
4281        // TODO: Inner Descendants can be folded into DagRange. Perhaps, we can rewrite
4282        // 'x::y' to 'x:: & ::y' first, so the common substitution rule can handle both
4283        // 'x+::y' and 'x+ & ::y'.
4284        insta::assert_debug_snapshot!(optimize(parse("(foo++)::bar").unwrap()), @r#"
4285        DagRange {
4286            roots: Descendants {
4287                roots: CommitRef(Symbol("foo")),
4288                generation: 2..3,
4289            },
4290            heads: CommitRef(Symbol("bar")),
4291        }
4292        "#);
4293    }
4294
4295    #[test]
4296    fn test_escape_string_literal() {
4297        // Valid identifiers don't need quoting
4298        assert_eq!(format_symbol("foo"), "foo");
4299        assert_eq!(format_symbol("foo.bar"), "foo.bar");
4300
4301        // Invalid identifiers need quoting
4302        assert_eq!(format_symbol("foo@bar"), r#""foo@bar""#);
4303        assert_eq!(format_symbol("foo bar"), r#""foo bar""#);
4304        assert_eq!(format_symbol(" foo "), r#"" foo ""#);
4305        assert_eq!(format_symbol("(foo)"), r#""(foo)""#);
4306        assert_eq!(format_symbol("all:foo"), r#""all:foo""#);
4307
4308        // Some characters also need escaping
4309        assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
4310        assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
4311        assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
4312        assert_eq!(format_symbol("foo\nbar"), r#""foo\nbar""#);
4313
4314        // Some characters don't technically need escaping, but we escape them for
4315        // clarity
4316        assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
4317        assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
4318        assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
4319        assert_eq!(format_symbol("foo \x01 bar"), r#""foo \x01 bar""#);
4320    }
4321
4322    #[test]
4323    fn test_escape_remote_symbol() {
4324        assert_eq!(format_remote_symbol("foo", "bar"), "foo@bar");
4325        assert_eq!(
4326            format_remote_symbol(" foo ", "bar:baz"),
4327            r#"" foo "@"bar:baz""#
4328        );
4329    }
4330}