jj_lib/
revset.rs

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