Skip to main content

jj_lib/
revset.rs

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