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