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