Skip to main content

jj_lib/
revset.rs

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