Skip to main content

jj_lib/
revset.rs

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