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