Skip to main content

jj_lib/
revset.rs

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