Skip to main content

jj_lib/
revset.rs

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