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    /// Streams in topological order with children before parents.
3427    // TODO: Relax to BoxStream?
3428    fn stream<'a>(&self) -> LocalBoxStream<'a, Result<CommitId, RevsetEvaluationError>>
3429    where
3430        Self: 'a;
3431
3432    /// Iterates commit/change id pairs in topological order.
3433    fn commit_change_ids<'a>(
3434        &self,
3435    ) -> Box<dyn Iterator<Item = Result<(CommitId, ChangeId), RevsetEvaluationError>> + 'a>
3436    where
3437        Self: 'a;
3438
3439    /// Streams graphs nodes (commit ID and edges) in topological order with
3440    /// children before parents.
3441    fn stream_graph<'a>(
3442        &self,
3443    ) -> LocalBoxStream<'a, Result<GraphNode<CommitId>, RevsetEvaluationError>>
3444    where
3445        Self: 'a;
3446
3447    /// Returns true if iterator will emit no commit nor error.
3448    fn is_empty(&self) -> bool;
3449
3450    /// Inclusive lower bound and, optionally, inclusive upper bound of how many
3451    /// commits are in the revset. The implementation can use its discretion as
3452    /// to how much effort should be put into the estimation, and how accurate
3453    /// the resulting estimate should be.
3454    fn count_estimate(&self) -> Result<(usize, Option<usize>), RevsetEvaluationError>;
3455
3456    /// Returns a closure that checks if a commit is contained within the
3457    /// revset.
3458    ///
3459    /// The implementation may construct and maintain any necessary internal
3460    /// context to optimize the performance of the check.
3461    fn containing_fn<'a>(&self) -> Box<RevsetContainingFn<'a>>
3462    where
3463        Self: 'a;
3464}
3465
3466/// Function that checks if a commit is contained within the revset.
3467pub type RevsetContainingFn<'a> = dyn Fn(&CommitId) -> Result<bool, RevsetEvaluationError> + 'a;
3468
3469pub trait RevsetStreamExt {
3470    fn commits(
3471        self,
3472        store: &Arc<Store>,
3473    ) -> impl Stream<Item = Result<Commit, RevsetEvaluationError>> + use<'_, Self>;
3474}
3475
3476impl<S: Stream<Item = Result<CommitId, RevsetEvaluationError>>> RevsetStreamExt for S {
3477    fn commits(
3478        self,
3479        store: &Arc<Store>,
3480    ) -> impl Stream<Item = Result<Commit, RevsetEvaluationError>> + use<'_, S> {
3481        self.map(async move |result| {
3482            let commit_id = result?;
3483            let commit = store
3484                .get_commit_async(&commit_id)
3485                .await
3486                .map_err(RevsetEvaluationError::Backend)?;
3487            Ok(commit)
3488        })
3489        .buffered(store.concurrency())
3490    }
3491}
3492
3493/// A set of extensions for revset evaluation.
3494pub struct RevsetExtensions {
3495    symbol_resolvers: Vec<Box<dyn SymbolResolverExtension>>,
3496    function_map: HashMap<&'static str, RevsetFunction>,
3497}
3498
3499impl Default for RevsetExtensions {
3500    fn default() -> Self {
3501        Self::new()
3502    }
3503}
3504
3505impl RevsetExtensions {
3506    pub fn new() -> Self {
3507        Self {
3508            symbol_resolvers: vec![],
3509            function_map: BUILTIN_FUNCTION_MAP.clone(),
3510        }
3511    }
3512
3513    pub fn symbol_resolvers(&self) -> &[Box<dyn SymbolResolverExtension>] {
3514        &self.symbol_resolvers
3515    }
3516
3517    pub fn add_symbol_resolver(&mut self, symbol_resolver: Box<dyn SymbolResolverExtension>) {
3518        self.symbol_resolvers.push(symbol_resolver);
3519    }
3520
3521    pub fn add_custom_function(&mut self, name: &'static str, func: RevsetFunction) {
3522        match self.function_map.entry(name) {
3523            hash_map::Entry::Occupied(_) => {
3524                panic!("Conflict registering revset function '{name}'")
3525            }
3526            hash_map::Entry::Vacant(v) => v.insert(func),
3527        };
3528    }
3529}
3530
3531/// Information needed to parse revset expression.
3532#[derive(Clone)]
3533pub struct RevsetParseContext<'a> {
3534    pub aliases_map: &'a RevsetAliasesMap,
3535    pub local_variables: HashMap<&'a str, ExpressionNode<'a>>,
3536    pub user_email: &'a str,
3537    pub date_pattern_context: DatePatternContext,
3538    /// Special remote that should be ignored by default. (e.g. "git")
3539    pub default_ignored_remote: Option<&'a RemoteName>,
3540    pub fileset_aliases_map: &'a FilesetAliasesMap,
3541    pub use_glob_by_default: bool,
3542    pub extensions: &'a RevsetExtensions,
3543    pub workspace: Option<RevsetWorkspaceContext<'a>>,
3544}
3545
3546impl<'a> RevsetParseContext<'a> {
3547    fn to_lowering_context(&self) -> LoweringContext<'a> {
3548        let RevsetParseContext {
3549            aliases_map: _,
3550            local_variables: _,
3551            user_email,
3552            date_pattern_context,
3553            default_ignored_remote,
3554            fileset_aliases_map,
3555            use_glob_by_default,
3556            extensions,
3557            workspace,
3558        } = *self;
3559        LoweringContext {
3560            user_email,
3561            date_pattern_context,
3562            default_ignored_remote,
3563            fileset_aliases_map,
3564            use_glob_by_default,
3565            extensions,
3566            workspace,
3567        }
3568    }
3569}
3570
3571/// Information needed to transform revset AST into `UserRevsetExpression`.
3572#[derive(Clone)]
3573pub struct LoweringContext<'a> {
3574    user_email: &'a str,
3575    date_pattern_context: DatePatternContext,
3576    default_ignored_remote: Option<&'a RemoteName>,
3577    fileset_aliases_map: &'a FilesetAliasesMap,
3578    use_glob_by_default: bool,
3579    extensions: &'a RevsetExtensions,
3580    workspace: Option<RevsetWorkspaceContext<'a>>,
3581}
3582
3583impl<'a> LoweringContext<'a> {
3584    pub fn user_email(&self) -> &'a str {
3585        self.user_email
3586    }
3587
3588    pub fn date_pattern_context(&self) -> &DatePatternContext {
3589        &self.date_pattern_context
3590    }
3591
3592    pub fn fileset_parse_context(&self) -> Option<FilesetParseContext<'_>> {
3593        Some(FilesetParseContext {
3594            aliases_map: self.fileset_aliases_map,
3595            path_converter: self.workspace?.path_converter,
3596        })
3597    }
3598
3599    pub fn symbol_resolvers(&self) -> &'a [impl AsRef<dyn SymbolResolverExtension> + use<>] {
3600        self.extensions.symbol_resolvers()
3601    }
3602}
3603
3604/// Workspace information needed to parse revset expression.
3605#[derive(Clone, Copy, Debug)]
3606pub struct RevsetWorkspaceContext<'a> {
3607    pub path_converter: &'a RepoPathUiConverter,
3608    pub workspace_name: &'a WorkspaceName,
3609}
3610
3611/// Formats a string as symbol by quoting and escaping it if necessary.
3612///
3613/// Note that symbols may be substituted to user aliases. Use
3614/// [`format_string()`] to ensure that the provided string is resolved as a
3615/// tag/bookmark name, commit/change ID prefix, etc.
3616pub fn format_symbol(literal: &str) -> String {
3617    if revset_parser::is_identifier(literal) {
3618        literal.to_string()
3619    } else {
3620        format_string(literal)
3621    }
3622}
3623
3624/// Formats a string by quoting and escaping it.
3625pub fn format_string(literal: &str) -> String {
3626    format!(r#""{}""#, dsl_util::escape_string(literal))
3627}
3628
3629/// Formats a `name@remote` symbol, applies quoting and escaping if necessary.
3630pub fn format_remote_symbol(name: &str, remote: &str) -> String {
3631    let name = format_symbol(name);
3632    let remote = format_symbol(remote);
3633    format!("{name}@{remote}")
3634}
3635
3636#[cfg(test)]
3637#[rustversion::attr(
3638    since(1.89),
3639    expect(clippy::cloned_ref_to_slice_refs, reason = "makes tests more readable")
3640)]
3641mod tests {
3642    use std::path::PathBuf;
3643
3644    use assert_matches::assert_matches;
3645
3646    use super::*;
3647    use crate::tests::TestResult;
3648
3649    fn parse(revset_str: &str) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3650        parse_with_aliases(revset_str, [] as [(&str, &str); 0])
3651    }
3652
3653    fn parse_with_workspace(
3654        revset_str: &str,
3655        workspace_name: &WorkspaceName,
3656    ) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3657        parse_with_aliases_and_workspace(revset_str, [] as [(&str, &str); 0], workspace_name)
3658    }
3659
3660    fn parse_with_aliases(
3661        revset_str: &str,
3662        aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
3663    ) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3664        let mut aliases_map = RevsetAliasesMap::new();
3665        for (decl, defn) in aliases {
3666            aliases_map.insert(decl, defn)?;
3667        }
3668        let context = RevsetParseContext {
3669            aliases_map: &aliases_map,
3670            local_variables: HashMap::new(),
3671            user_email: "test.user@example.com",
3672            date_pattern_context: chrono::Utc::now().fixed_offset().into(),
3673            default_ignored_remote: Some("ignored".as_ref()),
3674            fileset_aliases_map: &FilesetAliasesMap::new(),
3675            use_glob_by_default: true,
3676            extensions: &RevsetExtensions::default(),
3677            workspace: None,
3678        };
3679        super::parse(&mut RevsetDiagnostics::new(), revset_str, &context)
3680    }
3681
3682    fn parse_with_aliases_and_workspace(
3683        revset_str: &str,
3684        aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
3685        workspace_name: &WorkspaceName,
3686    ) -> Result<Arc<UserRevsetExpression>, RevsetParseError> {
3687        // Set up pseudo context to resolve `workspace_name@` and `file(path)`
3688        let path_converter = RepoPathUiConverter::Fs {
3689            cwd: PathBuf::from("/"),
3690            base: PathBuf::from("/"),
3691        };
3692        let workspace_ctx = RevsetWorkspaceContext {
3693            path_converter: &path_converter,
3694            workspace_name,
3695        };
3696        let mut aliases_map = RevsetAliasesMap::new();
3697        for (decl, defn) in aliases {
3698            aliases_map.insert(decl, defn)?;
3699        }
3700        let context = RevsetParseContext {
3701            aliases_map: &aliases_map,
3702            local_variables: HashMap::new(),
3703            user_email: "test.user@example.com",
3704            date_pattern_context: chrono::Utc::now().fixed_offset().into(),
3705            default_ignored_remote: Some("ignored".as_ref()),
3706            fileset_aliases_map: &FilesetAliasesMap::new(),
3707            use_glob_by_default: true,
3708            extensions: &RevsetExtensions::default(),
3709            workspace: Some(workspace_ctx),
3710        };
3711        super::parse(&mut RevsetDiagnostics::new(), revset_str, &context)
3712    }
3713
3714    fn insta_settings() -> insta::Settings {
3715        let mut settings = insta::Settings::clone_current();
3716        // Collapse short "Thing(_,)" repeatedly to save vertical space and make
3717        // the output more readable.
3718        for _ in 0..4 {
3719            settings.add_filter(
3720                r"(?x)
3721                \b([A-Z]\w*)\(\n
3722                    \s*(.{1,60}),\n
3723                \s*\)",
3724                "$1($2)",
3725            );
3726        }
3727        settings
3728    }
3729
3730    #[test]
3731    #[expect(clippy::redundant_clone)] // allow symbol.clone()
3732    fn test_revset_expression_building() {
3733        let settings = insta_settings();
3734        let _guard = settings.bind_to_scope();
3735        let current_wc = UserRevsetExpression::working_copy(WorkspaceName::DEFAULT.to_owned());
3736        let foo_symbol = UserRevsetExpression::symbol("foo".to_string());
3737        let bar_symbol = UserRevsetExpression::symbol("bar".to_string());
3738        let baz_symbol = UserRevsetExpression::symbol("baz".to_string());
3739
3740        insta::assert_debug_snapshot!(
3741            current_wc,
3742            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3743        insta::assert_debug_snapshot!(
3744            current_wc.heads(),
3745            @r#"Heads(CommitRef(WorkingCopy(WorkspaceNameBuf("default"))))"#);
3746        insta::assert_debug_snapshot!(
3747            current_wc.roots(),
3748            @r#"Roots(CommitRef(WorkingCopy(WorkspaceNameBuf("default"))))"#);
3749        insta::assert_debug_snapshot!(
3750            current_wc.parents(), @r#"
3751        Ancestors {
3752            heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3753            generation: 1..2,
3754            parents_range: 0..4294967295,
3755        }
3756        "#);
3757        insta::assert_debug_snapshot!(
3758            current_wc.ancestors(), @r#"
3759        Ancestors {
3760            heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3761            generation: 0..18446744073709551615,
3762            parents_range: 0..4294967295,
3763        }
3764        "#);
3765        insta::assert_debug_snapshot!(
3766            foo_symbol.children(), @r#"
3767        Descendants {
3768            roots: CommitRef(Symbol("foo")),
3769            generation: 1..2,
3770        }
3771        "#);
3772        insta::assert_debug_snapshot!(
3773            foo_symbol.descendants(), @r#"
3774        Descendants {
3775            roots: CommitRef(Symbol("foo")),
3776            generation: 0..18446744073709551615,
3777        }
3778        "#);
3779        insta::assert_debug_snapshot!(
3780            foo_symbol.dag_range_to(&current_wc), @r#"
3781        DagRange {
3782            roots: CommitRef(Symbol("foo")),
3783            heads: CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3784        }
3785        "#);
3786        insta::assert_debug_snapshot!(
3787            foo_symbol.connected(), @r#"
3788        DagRange {
3789            roots: CommitRef(Symbol("foo")),
3790            heads: CommitRef(Symbol("foo")),
3791        }
3792        "#);
3793        insta::assert_debug_snapshot!(
3794            foo_symbol.range(&current_wc), @r#"
3795        Range {
3796            roots: CommitRef(Symbol("foo")),
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.negated(),
3804            @r#"NotIn(CommitRef(Symbol("foo")))"#);
3805        insta::assert_debug_snapshot!(
3806            foo_symbol.union(&current_wc), @r#"
3807        Union(
3808            CommitRef(Symbol("foo")),
3809            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3810        )
3811        "#);
3812        insta::assert_debug_snapshot!(
3813            UserRevsetExpression::union_all(&[]),
3814            @"None");
3815        insta::assert_debug_snapshot!(
3816            RevsetExpression::union_all(&[current_wc.clone()]),
3817            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3818        insta::assert_debug_snapshot!(
3819            RevsetExpression::union_all(&[current_wc.clone(), foo_symbol.clone()]),
3820            @r#"
3821        Union(
3822            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3823            CommitRef(Symbol("foo")),
3824        )
3825        "#);
3826        insta::assert_debug_snapshot!(
3827            RevsetExpression::union_all(&[
3828                current_wc.clone(),
3829                foo_symbol.clone(),
3830                bar_symbol.clone(),
3831            ]),
3832            @r#"
3833        Union(
3834            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3835            Union(
3836                CommitRef(Symbol("foo")),
3837                CommitRef(Symbol("bar")),
3838            ),
3839        )
3840        "#);
3841        insta::assert_debug_snapshot!(
3842            RevsetExpression::union_all(&[
3843                current_wc.clone(),
3844                foo_symbol.clone(),
3845                bar_symbol.clone(),
3846                baz_symbol.clone(),
3847            ]),
3848            @r#"
3849        Union(
3850            Union(
3851                CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3852                CommitRef(Symbol("foo")),
3853            ),
3854            Union(
3855                CommitRef(Symbol("bar")),
3856                CommitRef(Symbol("baz")),
3857            ),
3858        )
3859        "#);
3860        insta::assert_debug_snapshot!(
3861            foo_symbol.intersection(&current_wc), @r#"
3862        Intersection(
3863            CommitRef(Symbol("foo")),
3864            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3865        )
3866        "#);
3867        insta::assert_debug_snapshot!(
3868            foo_symbol.minus(&current_wc), @r#"
3869        Difference(
3870            CommitRef(Symbol("foo")),
3871            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3872        )
3873        "#);
3874        insta::assert_debug_snapshot!(
3875            UserRevsetExpression::coalesce(&[]),
3876            @"None");
3877        insta::assert_debug_snapshot!(
3878            RevsetExpression::coalesce(&[current_wc.clone()]),
3879            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("default")))"#);
3880        insta::assert_debug_snapshot!(
3881            RevsetExpression::coalesce(&[current_wc.clone(), foo_symbol.clone()]),
3882            @r#"
3883        Coalesce(
3884            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3885            CommitRef(Symbol("foo")),
3886        )
3887        "#);
3888        insta::assert_debug_snapshot!(
3889            RevsetExpression::coalesce(&[
3890                current_wc.clone(),
3891                foo_symbol.clone(),
3892                bar_symbol.clone(),
3893            ]),
3894            @r#"
3895        Coalesce(
3896            CommitRef(WorkingCopy(WorkspaceNameBuf("default"))),
3897            Coalesce(
3898                CommitRef(Symbol("foo")),
3899                CommitRef(Symbol("bar")),
3900            ),
3901        )
3902        "#);
3903    }
3904
3905    #[test]
3906    fn test_parse_revset() -> TestResult {
3907        let settings = insta_settings();
3908        let _guard = settings.bind_to_scope();
3909        let main_workspace_name = WorkspaceNameBuf::from("main");
3910        let other_workspace_name = WorkspaceNameBuf::from("other");
3911
3912        // Parse "@" (the current working copy)
3913        insta::assert_debug_snapshot!(
3914            parse("@").unwrap_err().kind(),
3915            @"WorkingCopyWithoutWorkspace");
3916        insta::assert_debug_snapshot!(
3917            parse("main@")?,
3918            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3919        insta::assert_debug_snapshot!(
3920            parse_with_workspace("@", &main_workspace_name)?,
3921            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3922        insta::assert_debug_snapshot!(
3923            parse_with_workspace("main@", &other_workspace_name)?,
3924            @r#"CommitRef(WorkingCopy(WorkspaceNameBuf("main")))"#);
3925        // "@" in function argument must be quoted
3926        insta::assert_debug_snapshot!(
3927            parse("author_name(foo@)").unwrap_err().kind(),
3928            @r#"Expression("Invalid string expression")"#);
3929        insta::assert_debug_snapshot!(
3930            parse(r#"author_name("foo@")"#)?,
3931            @r#"Filter(AuthorName(Pattern(Exact("foo@"))))"#);
3932        // Parse a single symbol
3933        insta::assert_debug_snapshot!(
3934            parse("foo")?,
3935            @r#"CommitRef(Symbol("foo"))"#);
3936        // Default arguments for *bookmarks() are all ""
3937        insta::assert_debug_snapshot!(
3938            parse("bookmarks()")?,
3939            @r#"CommitRef(Bookmarks(Pattern(Substring(""))))"#);
3940        // Default argument for tags() is ""
3941        insta::assert_debug_snapshot!(
3942            parse("tags()")?,
3943            @r#"CommitRef(Tags(Pattern(Substring(""))))"#);
3944        insta::assert_debug_snapshot!(parse("remote_bookmarks()")?, @r#"
3945        CommitRef(
3946            RemoteBookmarks {
3947                symbol: RemoteRefSymbolExpression {
3948                    name: Pattern(Substring("")),
3949                    remote: NotIn(Pattern(Exact("ignored"))),
3950                },
3951                remote_ref_state: None,
3952            },
3953        )
3954        "#);
3955        insta::assert_debug_snapshot!(parse("tracked_remote_bookmarks()")?, @r#"
3956        CommitRef(
3957            RemoteBookmarks {
3958                symbol: RemoteRefSymbolExpression {
3959                    name: Pattern(Substring("")),
3960                    remote: NotIn(Pattern(Exact("ignored"))),
3961                },
3962                remote_ref_state: Some(Tracked),
3963            },
3964        )
3965        "#);
3966        insta::assert_debug_snapshot!(parse("untracked_remote_bookmarks()")?, @r#"
3967        CommitRef(
3968            RemoteBookmarks {
3969                symbol: RemoteRefSymbolExpression {
3970                    name: Pattern(Substring("")),
3971                    remote: NotIn(Pattern(Exact("ignored"))),
3972                },
3973                remote_ref_state: Some(New),
3974            },
3975        )
3976        "#);
3977        insta::assert_debug_snapshot!(parse("remote_tags()")?, @r#"
3978        CommitRef(
3979            RemoteTags {
3980                symbol: RemoteRefSymbolExpression {
3981                    name: Pattern(Substring("")),
3982                    remote: NotIn(Pattern(Exact("ignored"))),
3983                },
3984                remote_ref_state: None,
3985            },
3986        )
3987        "#);
3988        insta::assert_debug_snapshot!(parse("tracked_remote_tags()")?, @r#"
3989        CommitRef(
3990            RemoteTags {
3991                symbol: RemoteRefSymbolExpression {
3992                    name: Pattern(Substring("")),
3993                    remote: NotIn(Pattern(Exact("ignored"))),
3994                },
3995                remote_ref_state: Some(Tracked),
3996            },
3997        )
3998        "#);
3999        insta::assert_debug_snapshot!(parse("untracked_remote_tags()")?, @r#"
4000        CommitRef(
4001            RemoteTags {
4002                symbol: RemoteRefSymbolExpression {
4003                    name: Pattern(Substring("")),
4004                    remote: NotIn(Pattern(Exact("ignored"))),
4005                },
4006                remote_ref_state: Some(New),
4007            },
4008        )
4009        "#);
4010        // Parse a quoted symbol
4011        insta::assert_debug_snapshot!(
4012            parse("'foo'")?,
4013            @r#"CommitRef(Symbol("foo"))"#);
4014        // Parse the "parents" operator
4015        insta::assert_debug_snapshot!(parse("foo-")?, @r#"
4016        Ancestors {
4017            heads: CommitRef(Symbol("foo")),
4018            generation: 1..2,
4019            parents_range: 0..4294967295,
4020        }
4021        "#);
4022        // Parse the "children" operator
4023        insta::assert_debug_snapshot!(parse("foo+")?, @r#"
4024        Descendants {
4025            roots: CommitRef(Symbol("foo")),
4026            generation: 1..2,
4027        }
4028        "#);
4029        // Parse the "ancestors" operator
4030        insta::assert_debug_snapshot!(parse("::foo")?, @r#"
4031        Ancestors {
4032            heads: CommitRef(Symbol("foo")),
4033            generation: 0..18446744073709551615,
4034            parents_range: 0..4294967295,
4035        }
4036        "#);
4037        // Parse the "descendants" operator
4038        insta::assert_debug_snapshot!(parse("foo::")?, @r#"
4039        Descendants {
4040            roots: CommitRef(Symbol("foo")),
4041            generation: 0..18446744073709551615,
4042        }
4043        "#);
4044        // Parse the "dag range" operator
4045        insta::assert_debug_snapshot!(parse("foo::bar")?, @r#"
4046        DagRange {
4047            roots: CommitRef(Symbol("foo")),
4048            heads: CommitRef(Symbol("bar")),
4049        }
4050        "#);
4051        // Parse the nullary "dag range" operator
4052        insta::assert_debug_snapshot!(parse("::")?, @"All");
4053        // Parse the "range" prefix operator
4054        insta::assert_debug_snapshot!(parse("..foo")?, @r#"
4055        Range {
4056            roots: Root,
4057            heads: CommitRef(Symbol("foo")),
4058            generation: 0..18446744073709551615,
4059            parents_range: 0..4294967295,
4060        }
4061        "#);
4062        insta::assert_debug_snapshot!(parse("foo..")?, @r#"
4063        NotIn(
4064            Ancestors {
4065                heads: CommitRef(Symbol("foo")),
4066                generation: 0..18446744073709551615,
4067                parents_range: 0..4294967295,
4068            },
4069        )
4070        "#);
4071        insta::assert_debug_snapshot!(parse("foo..bar")?, @r#"
4072        Range {
4073            roots: CommitRef(Symbol("foo")),
4074            heads: CommitRef(Symbol("bar")),
4075            generation: 0..18446744073709551615,
4076            parents_range: 0..4294967295,
4077        }
4078        "#);
4079        // Parse the nullary "range" operator
4080        insta::assert_debug_snapshot!(parse("..")?, @"NotIn(Root)");
4081        // Parse the "negate" operator
4082        insta::assert_debug_snapshot!(
4083            parse("~ foo")?,
4084            @r#"NotIn(CommitRef(Symbol("foo")))"#);
4085        // Parse the "intersection" operator
4086        insta::assert_debug_snapshot!(parse("foo & bar")?, @r#"
4087        Intersection(
4088            CommitRef(Symbol("foo")),
4089            CommitRef(Symbol("bar")),
4090        )
4091        "#);
4092        // Parse the "union" operator
4093        insta::assert_debug_snapshot!(parse("foo | bar")?, @r#"
4094        Union(
4095            CommitRef(Symbol("foo")),
4096            CommitRef(Symbol("bar")),
4097        )
4098        "#);
4099        // Parse the "difference" operator
4100        insta::assert_debug_snapshot!(parse("foo ~ bar")?, @r#"
4101        Difference(
4102            CommitRef(Symbol("foo")),
4103            CommitRef(Symbol("bar")),
4104        )
4105        "#);
4106        Ok(())
4107    }
4108
4109    #[test]
4110    fn test_parse_string_pattern() -> TestResult {
4111        let settings = insta_settings();
4112        let _guard = settings.bind_to_scope();
4113
4114        insta::assert_debug_snapshot!(
4115            parse(r#"bookmarks("foo")"#)?,
4116            @r#"CommitRef(Bookmarks(Pattern(Exact("foo"))))"#);
4117        insta::assert_debug_snapshot!(
4118            parse(r#"bookmarks(exact:"foo")"#)?,
4119            @r#"CommitRef(Bookmarks(Pattern(Exact("foo"))))"#);
4120        insta::assert_debug_snapshot!(
4121            parse(r#"bookmarks(substring:"foo")"#)?,
4122            @r#"CommitRef(Bookmarks(Pattern(Substring("foo"))))"#);
4123        insta::assert_debug_snapshot!(
4124            parse(r#"bookmarks(bad:"foo")"#).unwrap_err().kind(),
4125            @r#"Expression("Invalid string pattern")"#);
4126        insta::assert_debug_snapshot!(
4127            parse(r#"bookmarks(exact::"foo")"#).unwrap_err().kind(),
4128            @r#"Expression("Invalid string expression")"#);
4129        insta::assert_debug_snapshot!(
4130            parse(r#"bookmarks(exact:"foo"+)"#).unwrap_err().kind(),
4131            @r#"Expression("Expected string")"#);
4132
4133        insta::assert_debug_snapshot!(
4134            parse(r#"tags("foo")"#)?,
4135            @r#"CommitRef(Tags(Pattern(Exact("foo"))))"#);
4136        insta::assert_debug_snapshot!(
4137            parse(r#"tags(exact:"foo")"#)?,
4138            @r#"CommitRef(Tags(Pattern(Exact("foo"))))"#);
4139        insta::assert_debug_snapshot!(
4140            parse(r#"tags(substring:"foo")"#)?,
4141            @r#"CommitRef(Tags(Pattern(Substring("foo"))))"#);
4142        insta::assert_debug_snapshot!(
4143            parse(r#"tags(bad:"foo")"#).unwrap_err().kind(),
4144            @r#"Expression("Invalid string pattern")"#);
4145        insta::assert_debug_snapshot!(
4146            parse(r#"tags(exact::"foo")"#).unwrap_err().kind(),
4147            @r#"Expression("Invalid string expression")"#);
4148        insta::assert_debug_snapshot!(
4149            parse(r#"tags(exact:"foo"+)"#).unwrap_err().kind(),
4150            @r#"Expression("Expected string")"#);
4151
4152        // String pattern isn't allowed at top level.
4153        assert_matches!(
4154            parse(r#"(exact:"foo")"#).unwrap_err().kind(),
4155            RevsetParseErrorKind::NotInfixOperator { .. }
4156        );
4157        Ok(())
4158    }
4159
4160    #[test]
4161    fn test_parse_compound_string_expression() -> TestResult {
4162        let settings = insta_settings();
4163        let _guard = settings.bind_to_scope();
4164
4165        insta::assert_debug_snapshot!(
4166            parse(r#"tags(~a)"#)?,
4167            @r#"
4168        CommitRef(
4169            Tags(NotIn(Pattern(Exact("a")))),
4170        )
4171        "#);
4172        insta::assert_debug_snapshot!(
4173            parse(r#"tags(a|b&c)"#)?,
4174            @r#"
4175        CommitRef(
4176            Tags(
4177                Union(
4178                    Pattern(Exact("a")),
4179                    Intersection(
4180                        Pattern(Exact("b")),
4181                        Pattern(Exact("c")),
4182                    ),
4183                ),
4184            ),
4185        )
4186        "#);
4187        insta::assert_debug_snapshot!(
4188            parse(r#"tags(a|b|c)"#)?,
4189            @r#"
4190        CommitRef(
4191            Tags(
4192                Union(
4193                    Pattern(Exact("a")),
4194                    Union(
4195                        Pattern(Exact("b")),
4196                        Pattern(Exact("c")),
4197                    ),
4198                ),
4199            ),
4200        )
4201        "#);
4202        insta::assert_debug_snapshot!(
4203            parse(r#"tags(a~(b|c))"#)?,
4204            @r#"
4205        CommitRef(
4206            Tags(
4207                Intersection(
4208                    Pattern(Exact("a")),
4209                    NotIn(
4210                        Union(
4211                            Pattern(Exact("b")),
4212                            Pattern(Exact("c")),
4213                        ),
4214                    ),
4215                ),
4216            ),
4217        )
4218        "#);
4219        Ok(())
4220    }
4221
4222    #[test]
4223    fn test_parse_revset_function() -> TestResult {
4224        let settings = insta_settings();
4225        let _guard = settings.bind_to_scope();
4226
4227        insta::assert_debug_snapshot!(
4228            parse("parents(foo)")?, @r#"
4229        Ancestors {
4230            heads: CommitRef(Symbol("foo")),
4231            generation: 1..2,
4232            parents_range: 0..4294967295,
4233        }
4234        "#);
4235        insta::assert_debug_snapshot!(
4236            parse("parents(\"foo\")")?, @r#"
4237        Ancestors {
4238            heads: CommitRef(Symbol("foo")),
4239            generation: 1..2,
4240            parents_range: 0..4294967295,
4241        }
4242        "#);
4243        insta::assert_debug_snapshot!(
4244            parse("ancestors(parents(foo))")?, @r#"
4245        Ancestors {
4246            heads: Ancestors {
4247                heads: CommitRef(Symbol("foo")),
4248                generation: 1..2,
4249                parents_range: 0..4294967295,
4250            },
4251            generation: 0..18446744073709551615,
4252            parents_range: 0..4294967295,
4253        }
4254        "#);
4255        insta::assert_debug_snapshot!(
4256            parse("parents(foo, bar, baz)").unwrap_err().kind(), @r#"
4257        InvalidFunctionArguments {
4258            name: "parents",
4259            message: "Expected 1 to 2 arguments",
4260        }
4261        "#);
4262        insta::assert_debug_snapshot!(
4263            parse("parents(foo, 2)")?, @r#"
4264        Ancestors {
4265            heads: CommitRef(Symbol("foo")),
4266            generation: 2..3,
4267            parents_range: 0..4294967295,
4268        }
4269        "#);
4270        insta::assert_debug_snapshot!(
4271            parse("root()")?,
4272            @"Root");
4273        assert!(parse("root(a)").is_err());
4274        insta::assert_debug_snapshot!(
4275            parse(r#"description("")"#)?,
4276            @r#"Filter(Description(Pattern(Exact(""))))"#);
4277        insta::assert_debug_snapshot!(
4278            parse("description(foo)")?,
4279            @r#"Filter(Description(Pattern(Exact("foo"))))"#);
4280        insta::assert_debug_snapshot!(
4281            parse("description(visible_heads())").unwrap_err().kind(),
4282            @r#"Expression("Invalid string expression")"#);
4283        insta::assert_debug_snapshot!(
4284            parse("description(\"(foo)\")")?,
4285            @r#"Filter(Description(Pattern(Exact("(foo)"))))"#);
4286        assert!(parse("mine(foo)").is_err());
4287        insta::assert_debug_snapshot!(
4288            parse_with_workspace("empty()", WorkspaceName::DEFAULT)?,
4289            @"NotIn(Filter(File(All)))");
4290        assert!(parse_with_workspace("empty(foo)", WorkspaceName::DEFAULT).is_err());
4291        assert!(parse_with_workspace("file()", WorkspaceName::DEFAULT).is_err());
4292        insta::assert_debug_snapshot!(
4293            parse_with_workspace("files(foo)", WorkspaceName::DEFAULT)?,
4294            @r#"Filter(File(Pattern(PrefixPath("foo"))))"#);
4295        insta::assert_debug_snapshot!(
4296            parse_with_workspace("files(all())", WorkspaceName::DEFAULT)?,
4297            @"Filter(File(All))");
4298        insta::assert_debug_snapshot!(
4299            parse_with_workspace(r#"files(file:"foo")"#, WorkspaceName::DEFAULT)?,
4300            @r#"Filter(File(Pattern(FilePath("foo"))))"#);
4301        insta::assert_debug_snapshot!(
4302            parse_with_workspace("files(foo|bar&baz)", WorkspaceName::DEFAULT)?, @r#"
4303        Filter(
4304            File(
4305                UnionAll(
4306                    [
4307                        Pattern(PrefixPath("foo")),
4308                        Intersection(
4309                            Pattern(PrefixPath("bar")),
4310                            Pattern(PrefixPath("baz")),
4311                        ),
4312                    ],
4313                ),
4314            ),
4315        )
4316        "#);
4317        insta::assert_debug_snapshot!(
4318            parse_with_workspace(r#"files(~(foo))"#, WorkspaceName::DEFAULT)?,
4319            @r#"
4320        Filter(
4321            File(
4322                Difference(
4323                    All,
4324                    Pattern(PrefixPath("foo")),
4325                ),
4326            ),
4327        )
4328        "#);
4329        insta::assert_debug_snapshot!(parse("signed()")?, @"Filter(Signed)");
4330        Ok(())
4331    }
4332
4333    #[test]
4334    fn test_parse_revset_change_commit_id_functions() -> TestResult {
4335        let settings = insta_settings();
4336        let _guard = settings.bind_to_scope();
4337
4338        insta::assert_debug_snapshot!(
4339            parse("change_id(z)")?,
4340            @r#"CommitRef(ChangeId(HexPrefix("0")))"#);
4341        insta::assert_debug_snapshot!(
4342            parse("change_id('zk')")?,
4343            @r#"CommitRef(ChangeId(HexPrefix("0f")))"#);
4344        insta::assert_debug_snapshot!(
4345            parse("change_id(01234)").unwrap_err().kind(),
4346            @r#"Expression("Invalid change ID prefix")"#);
4347
4348        insta::assert_debug_snapshot!(
4349            parse("commit_id(0)")?,
4350            @r#"CommitRef(CommitId(HexPrefix("0")))"#);
4351        insta::assert_debug_snapshot!(
4352            parse("commit_id('0f')")?,
4353            @r#"CommitRef(CommitId(HexPrefix("0f")))"#);
4354        insta::assert_debug_snapshot!(
4355            parse("commit_id(xyzzy)").unwrap_err().kind(),
4356            @r#"Expression("Invalid commit ID prefix")"#);
4357        Ok(())
4358    }
4359
4360    #[test]
4361    fn test_parse_revset_author_committer_functions() -> TestResult {
4362        let settings = insta_settings();
4363        let _guard = settings.bind_to_scope();
4364
4365        insta::assert_debug_snapshot!(
4366            parse("author(foo)")?, @r#"
4367        Union(
4368            Filter(AuthorName(Pattern(Exact("foo")))),
4369            Filter(AuthorEmail(Pattern(Exact("foo")))),
4370        )
4371        "#);
4372        insta::assert_debug_snapshot!(
4373            parse("author_name(foo)")?,
4374            @r#"Filter(AuthorName(Pattern(Exact("foo"))))"#);
4375        insta::assert_debug_snapshot!(
4376            parse("author_email(foo)")?,
4377            @r#"Filter(AuthorEmail(Pattern(Exact("foo"))))"#);
4378
4379        insta::assert_debug_snapshot!(
4380            parse("committer(foo)")?, @r#"
4381        Union(
4382            Filter(CommitterName(Pattern(Exact("foo")))),
4383            Filter(CommitterEmail(Pattern(Exact("foo")))),
4384        )
4385        "#);
4386        insta::assert_debug_snapshot!(
4387            parse("committer_name(foo)")?,
4388            @r#"Filter(CommitterName(Pattern(Exact("foo"))))"#);
4389        insta::assert_debug_snapshot!(
4390            parse("committer_email(foo)")?,
4391            @r#"Filter(CommitterEmail(Pattern(Exact("foo"))))"#);
4392
4393        insta::assert_debug_snapshot!(
4394            parse("mine()")?,
4395            @r#"Filter(AuthorEmail(Pattern(ExactI("test.user@example.com"))))"#);
4396        Ok(())
4397    }
4398
4399    #[test]
4400    fn test_parse_revset_keyword_arguments() -> TestResult {
4401        let settings = insta_settings();
4402        let _guard = settings.bind_to_scope();
4403
4404        insta::assert_debug_snapshot!(
4405            parse("remote_bookmarks(remote=foo)")?, @r#"
4406        CommitRef(
4407            RemoteBookmarks {
4408                symbol: RemoteRefSymbolExpression {
4409                    name: Pattern(Substring("")),
4410                    remote: Pattern(Exact("foo")),
4411                },
4412                remote_ref_state: None,
4413            },
4414        )
4415        "#);
4416        insta::assert_debug_snapshot!(
4417            parse("remote_bookmarks(foo, remote=bar)")?, @r#"
4418        CommitRef(
4419            RemoteBookmarks {
4420                symbol: RemoteRefSymbolExpression {
4421                    name: Pattern(Exact("foo")),
4422                    remote: Pattern(Exact("bar")),
4423                },
4424                remote_ref_state: None,
4425            },
4426        )
4427        "#);
4428        insta::assert_debug_snapshot!(
4429            parse("tracked_remote_bookmarks(foo, remote=bar)")?, @r#"
4430        CommitRef(
4431            RemoteBookmarks {
4432                symbol: RemoteRefSymbolExpression {
4433                    name: Pattern(Exact("foo")),
4434                    remote: Pattern(Exact("bar")),
4435                },
4436                remote_ref_state: Some(Tracked),
4437            },
4438        )
4439        "#);
4440        insta::assert_debug_snapshot!(
4441            parse("untracked_remote_bookmarks(foo, remote=bar)")?, @r#"
4442        CommitRef(
4443            RemoteBookmarks {
4444                symbol: RemoteRefSymbolExpression {
4445                    name: Pattern(Exact("foo")),
4446                    remote: Pattern(Exact("bar")),
4447                },
4448                remote_ref_state: Some(New),
4449            },
4450        )
4451        "#);
4452        insta::assert_debug_snapshot!(
4453            parse(r#"remote_bookmarks(remote=foo, bar)"#).unwrap_err().kind(),
4454            @r#"
4455        InvalidFunctionArguments {
4456            name: "remote_bookmarks",
4457            message: "Positional argument follows keyword argument",
4458        }
4459        "#);
4460        insta::assert_debug_snapshot!(
4461            parse(r#"remote_bookmarks("", foo, remote=bar)"#).unwrap_err().kind(),
4462            @r#"
4463        InvalidFunctionArguments {
4464            name: "remote_bookmarks",
4465            message: "Got multiple values for keyword \"remote\"",
4466        }
4467        "#);
4468        insta::assert_debug_snapshot!(
4469            parse(r#"remote_bookmarks(remote=bar, remote=bar)"#).unwrap_err().kind(),
4470            @r#"
4471        InvalidFunctionArguments {
4472            name: "remote_bookmarks",
4473            message: "Got multiple values for keyword \"remote\"",
4474        }
4475        "#);
4476        insta::assert_debug_snapshot!(
4477            parse(r#"remote_bookmarks(unknown=bar)"#).unwrap_err().kind(),
4478            @r#"
4479        InvalidFunctionArguments {
4480            name: "remote_bookmarks",
4481            message: "Unexpected keyword argument \"unknown\"",
4482        }
4483        "#);
4484        Ok(())
4485    }
4486
4487    #[test]
4488    fn test_expand_symbol_alias() -> TestResult {
4489        let settings = insta_settings();
4490        let _guard = settings.bind_to_scope();
4491
4492        insta::assert_debug_snapshot!(
4493            parse_with_aliases("AB|c", [("AB", "a|b")])?, @r#"
4494        Union(
4495            Union(
4496                CommitRef(Symbol("a")),
4497                CommitRef(Symbol("b")),
4498            ),
4499            CommitRef(Symbol("c")),
4500        )
4501        "#);
4502
4503        // Alias can be substituted to string literal.
4504        insta::assert_debug_snapshot!(
4505            parse_with_aliases_and_workspace("files(A)", [("A", "a")], WorkspaceName::DEFAULT)
4506                ?,
4507            @r#"Filter(File(Pattern(PrefixPath("a"))))"#);
4508
4509        // Alias can be substituted to string pattern.
4510        insta::assert_debug_snapshot!(
4511            parse_with_aliases("author_name(A)", [("A", "a")])?,
4512            @r#"Filter(AuthorName(Pattern(Exact("a"))))"#);
4513        insta::assert_debug_snapshot!(
4514            parse_with_aliases("author_name(A)", [("A", "exact:a")])?,
4515            @r#"Filter(AuthorName(Pattern(Exact("a"))))"#);
4516        Ok(())
4517    }
4518
4519    #[test]
4520    fn test_expand_function_alias() -> TestResult {
4521        let settings = insta_settings();
4522        let _guard = settings.bind_to_scope();
4523
4524        // Pass string literal as parameter.
4525        insta::assert_debug_snapshot!(
4526            parse_with_aliases("F(a)", [("F(x)", "author_name(x)|committer_name(x)")])?,
4527            @r#"
4528        Union(
4529            Filter(AuthorName(Pattern(Exact("a")))),
4530            Filter(CommitterName(Pattern(Exact("a")))),
4531        )
4532        "#);
4533        Ok(())
4534    }
4535
4536    #[test]
4537    fn test_transform_expression() {
4538        let settings = insta_settings();
4539        let _guard = settings.bind_to_scope();
4540
4541        // Break without pre transformation
4542        insta::assert_debug_snapshot!(
4543            transform_expression(
4544                &ResolvedRevsetExpression::root(),
4545                |_| ControlFlow::Break(None),
4546                |_| Some(RevsetExpression::none()),
4547            ), @"None");
4548
4549        // Break with pre transformation
4550        insta::assert_debug_snapshot!(
4551            transform_expression(
4552                &ResolvedRevsetExpression::root(),
4553                |_| ControlFlow::Break(Some(RevsetExpression::all())),
4554                |_| Some(RevsetExpression::none()),
4555            ), @"Some(All)");
4556
4557        // Continue without pre transformation, do transform child
4558        insta::assert_debug_snapshot!(
4559            transform_expression(
4560                &ResolvedRevsetExpression::root().heads(),
4561                |_| ControlFlow::Continue(()),
4562                |x| match x.as_ref() {
4563                    RevsetExpression::Root => Some(RevsetExpression::none()),
4564                    _ => None,
4565                },
4566            ), @"Some(Heads(None))");
4567
4568        // Continue without pre transformation, do transform self
4569        insta::assert_debug_snapshot!(
4570            transform_expression(
4571                &ResolvedRevsetExpression::root().heads(),
4572                |_| ControlFlow::Continue(()),
4573                |x| match x.as_ref() {
4574                    RevsetExpression::Heads(y) => Some(y.clone()),
4575                    _ => None,
4576                },
4577            ), @"Some(Root)");
4578    }
4579
4580    #[test]
4581    fn test_resolve_referenced_commits() {
4582        let settings = insta_settings();
4583        let _guard = settings.bind_to_scope();
4584
4585        let visibility1 = Arc::new(ResolvedRevsetExpression::WithinVisibility {
4586            candidates: RevsetExpression::commit(CommitId::from_hex("100001")),
4587            visible_heads: vec![CommitId::from_hex("100000")],
4588        });
4589        let visibility2 = Arc::new(ResolvedRevsetExpression::WithinVisibility {
4590            candidates: RevsetExpression::filter(RevsetFilterPredicate::HasConflict),
4591            visible_heads: vec![CommitId::from_hex("200000")],
4592        });
4593        let commit3 = RevsetExpression::commit(CommitId::from_hex("000003"));
4594
4595        // Inner commits should be scoped. Both inner commits and visible heads
4596        // should be added to the outer scope.
4597        insta::assert_debug_snapshot!(
4598            resolve_referenced_commits(&visibility1.intersection(&RevsetExpression::all())), @r#"
4599        Some(
4600            WithinReference {
4601                candidates: Intersection(
4602                    WithinVisibility {
4603                        candidates: WithinReference {
4604                            candidates: Commits(
4605                                [
4606                                    CommitId("100001"),
4607                                ],
4608                            ),
4609                            commits: [
4610                                CommitId("100001"),
4611                            ],
4612                        },
4613                        visible_heads: [
4614                            CommitId("100000"),
4615                        ],
4616                    },
4617                    All,
4618                ),
4619                commits: [
4620                    CommitId("100000"),
4621                    CommitId("100001"),
4622                ],
4623            },
4624        )
4625        "#);
4626
4627        // Inner scope has no references, so WithinReference should be omitted.
4628        insta::assert_debug_snapshot!(
4629            resolve_referenced_commits(
4630                &visibility2
4631                    .intersection(&RevsetExpression::all())
4632                    .union(&commit3),
4633            ), @r#"
4634        Some(
4635            WithinReference {
4636                candidates: Union(
4637                    Intersection(
4638                        WithinVisibility {
4639                            candidates: Filter(HasConflict),
4640                            visible_heads: [
4641                                CommitId("200000"),
4642                            ],
4643                        },
4644                        All,
4645                    ),
4646                    Commits(
4647                        [
4648                            CommitId("000003"),
4649                        ],
4650                    ),
4651                ),
4652                commits: [
4653                    CommitId("000003"),
4654                    CommitId("200000"),
4655                ],
4656            },
4657        )
4658        "#);
4659
4660        // Sibling scopes should track referenced commits individually.
4661        insta::assert_debug_snapshot!(
4662            resolve_referenced_commits(
4663                &visibility1
4664                    .union(&visibility2)
4665                    .union(&commit3)
4666                    .intersection(&RevsetExpression::all())
4667            ), @r#"
4668        Some(
4669            WithinReference {
4670                candidates: Intersection(
4671                    Union(
4672                        Union(
4673                            WithinVisibility {
4674                                candidates: WithinReference {
4675                                    candidates: Commits(
4676                                        [
4677                                            CommitId("100001"),
4678                                        ],
4679                                    ),
4680                                    commits: [
4681                                        CommitId("100001"),
4682                                    ],
4683                                },
4684                                visible_heads: [
4685                                    CommitId("100000"),
4686                                ],
4687                            },
4688                            WithinVisibility {
4689                                candidates: Filter(HasConflict),
4690                                visible_heads: [
4691                                    CommitId("200000"),
4692                                ],
4693                            },
4694                        ),
4695                        Commits(
4696                            [
4697                                CommitId("000003"),
4698                            ],
4699                        ),
4700                    ),
4701                    All,
4702                ),
4703                commits: [
4704                    CommitId("000003"),
4705                    CommitId("100000"),
4706                    CommitId("100001"),
4707                    CommitId("200000"),
4708                ],
4709            },
4710        )
4711        "#);
4712
4713        // Referenced commits should be propagated from the innermost scope.
4714        insta::assert_debug_snapshot!(
4715            resolve_referenced_commits(&Arc::new(ResolvedRevsetExpression::WithinVisibility {
4716                candidates: visibility1.clone(),
4717                visible_heads: vec![CommitId::from_hex("400000")],
4718            })), @r#"
4719        Some(
4720            WithinReference {
4721                candidates: WithinVisibility {
4722                    candidates: WithinReference {
4723                        candidates: WithinVisibility {
4724                            candidates: WithinReference {
4725                                candidates: Commits(
4726                                    [
4727                                        CommitId("100001"),
4728                                    ],
4729                                ),
4730                                commits: [
4731                                    CommitId("100001"),
4732                                ],
4733                            },
4734                            visible_heads: [
4735                                CommitId("100000"),
4736                            ],
4737                        },
4738                        commits: [
4739                            CommitId("100000"),
4740                            CommitId("100001"),
4741                        ],
4742                    },
4743                    visible_heads: [
4744                        CommitId("400000"),
4745                    ],
4746                },
4747                commits: [
4748                    CommitId("400000"),
4749                    CommitId("100000"),
4750                    CommitId("100001"),
4751                ],
4752            },
4753        )
4754        "#);
4755
4756        // Resolved expression should be reused.
4757        let resolved = Arc::new(ResolvedRevsetExpression::WithinReference {
4758            // No referenced commits within the scope to test whether the
4759            // precomputed value is reused.
4760            candidates: RevsetExpression::none(),
4761            commits: vec![CommitId::from_hex("100000")],
4762        });
4763        insta::assert_debug_snapshot!(
4764            resolve_referenced_commits(&resolved), @"None");
4765        insta::assert_debug_snapshot!(
4766            resolve_referenced_commits(&resolved.intersection(&RevsetExpression::all())), @r#"
4767        Some(
4768            WithinReference {
4769                candidates: Intersection(
4770                    WithinReference {
4771                        candidates: None,
4772                        commits: [
4773                            CommitId("100000"),
4774                        ],
4775                    },
4776                    All,
4777                ),
4778                commits: [
4779                    CommitId("100000"),
4780                ],
4781            },
4782        )
4783        "#);
4784        insta::assert_debug_snapshot!(
4785            resolve_referenced_commits(&Arc::new(ResolvedRevsetExpression::WithinVisibility {
4786                candidates: resolved.clone(),
4787                visible_heads: vec![CommitId::from_hex("400000")],
4788            })), @r#"
4789        Some(
4790            WithinReference {
4791                candidates: WithinVisibility {
4792                    candidates: WithinReference {
4793                        candidates: None,
4794                        commits: [
4795                            CommitId("100000"),
4796                        ],
4797                    },
4798                    visible_heads: [
4799                        CommitId("400000"),
4800                    ],
4801                },
4802                commits: [
4803                    CommitId("400000"),
4804                    CommitId("100000"),
4805                ],
4806            },
4807        )
4808        "#);
4809    }
4810
4811    #[test]
4812    fn test_optimize_subtree() -> TestResult {
4813        let settings = insta_settings();
4814        let _guard = settings.bind_to_scope();
4815
4816        // Check that transform_expression_bottom_up() never rewrites enum variant
4817        // (e.g. Range -> DagRange) nor reorders arguments unintentionally.
4818
4819        insta::assert_debug_snapshot!(
4820            optimize(parse("parents(bookmarks() & all())")?), @r#"
4821        Ancestors {
4822            heads: CommitRef(Bookmarks(Pattern(Substring("")))),
4823            generation: 1..2,
4824            parents_range: 0..4294967295,
4825        }
4826        "#);
4827        insta::assert_debug_snapshot!(
4828            optimize(parse("children(bookmarks() & all())")?), @r#"
4829        Descendants {
4830            roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4831            generation: 1..2,
4832        }
4833        "#);
4834        insta::assert_debug_snapshot!(
4835            optimize(parse("ancestors(bookmarks() & all())")?), @r#"
4836        Ancestors {
4837            heads: CommitRef(Bookmarks(Pattern(Substring("")))),
4838            generation: 0..18446744073709551615,
4839            parents_range: 0..4294967295,
4840        }
4841        "#);
4842        insta::assert_debug_snapshot!(
4843            optimize(parse("descendants(bookmarks() & all())")?), @r#"
4844        Descendants {
4845            roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4846            generation: 0..18446744073709551615,
4847        }
4848        "#);
4849
4850        insta::assert_debug_snapshot!(
4851            optimize(parse("(bookmarks() & all())..(all() & tags())")?), @r#"
4852        Range {
4853            roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4854            heads: CommitRef(Tags(Pattern(Substring("")))),
4855            generation: 0..18446744073709551615,
4856            parents_range: 0..4294967295,
4857        }
4858        "#);
4859        insta::assert_debug_snapshot!(
4860            optimize(parse("(bookmarks() & all())::(all() & tags())")?), @r#"
4861        DagRange {
4862            roots: CommitRef(Bookmarks(Pattern(Substring("")))),
4863            heads: CommitRef(Tags(Pattern(Substring("")))),
4864        }
4865        "#);
4866
4867        insta::assert_debug_snapshot!(
4868            optimize(parse("heads(bookmarks() & all())")?),
4869            @r#"
4870        Heads(
4871            CommitRef(Bookmarks(Pattern(Substring("")))),
4872        )
4873        "#);
4874        insta::assert_debug_snapshot!(
4875            optimize(parse("roots(bookmarks() & all())")?),
4876            @r#"
4877        Roots(
4878            CommitRef(Bookmarks(Pattern(Substring("")))),
4879        )
4880        "#);
4881
4882        insta::assert_debug_snapshot!(
4883            optimize(parse("latest(bookmarks() & all(), 2)")?), @r#"
4884        Latest {
4885            candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4886            count: 2,
4887        }
4888        "#);
4889
4890        insta::assert_debug_snapshot!(
4891            optimize(parse("present(foo ~ bar)")?), @r#"
4892        Present(
4893            Difference(
4894                CommitRef(Symbol("foo")),
4895                CommitRef(Symbol("bar")),
4896            ),
4897        )
4898        "#);
4899        insta::assert_debug_snapshot!(
4900            optimize(parse("present(bookmarks() & all())")?),
4901            @r#"
4902        Present(
4903            CommitRef(Bookmarks(Pattern(Substring("")))),
4904        )
4905        "#);
4906
4907        insta::assert_debug_snapshot!(
4908            optimize(parse("at_operation(@-, bookmarks() & all())")?), @r#"
4909        AtOperation {
4910            operation: "@-",
4911            candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4912        }
4913        "#);
4914        insta::assert_debug_snapshot!(
4915            optimize(Arc::new(RevsetExpression::WithinReference {
4916                candidates: parse("bookmarks() & all()")?,
4917                commits: vec![CommitId::from_hex("012345")],
4918            })), @r#"
4919        WithinReference {
4920            candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4921            commits: [
4922                CommitId("012345"),
4923            ],
4924        }
4925        "#);
4926        insta::assert_debug_snapshot!(
4927            optimize(Arc::new(RevsetExpression::WithinVisibility {
4928                candidates: parse("bookmarks() & all()")?,
4929                visible_heads: vec![CommitId::from_hex("012345")],
4930            })), @r#"
4931        WithinReference {
4932            candidates: WithinVisibility {
4933                candidates: CommitRef(Bookmarks(Pattern(Substring("")))),
4934                visible_heads: [
4935                    CommitId("012345"),
4936                ],
4937            },
4938            commits: [
4939                CommitId("012345"),
4940            ],
4941        }
4942        "#);
4943
4944        insta::assert_debug_snapshot!(
4945            optimize(parse("~bookmarks() & all()")?),
4946            @r#"
4947        NotIn(
4948            CommitRef(Bookmarks(Pattern(Substring("")))),
4949        )
4950        "#);
4951        insta::assert_debug_snapshot!(
4952            optimize(parse("(bookmarks() & all()) | (all() & tags())")?), @r#"
4953        Union(
4954            CommitRef(Bookmarks(Pattern(Substring("")))),
4955            CommitRef(Tags(Pattern(Substring("")))),
4956        )
4957        "#);
4958        insta::assert_debug_snapshot!(
4959            optimize(parse("(bookmarks() & all()) & (all() & tags())")?), @r#"
4960        Intersection(
4961            CommitRef(Bookmarks(Pattern(Substring("")))),
4962            CommitRef(Tags(Pattern(Substring("")))),
4963        )
4964        "#);
4965        insta::assert_debug_snapshot!(
4966            optimize(parse("(bookmarks() & all()) ~ (all() & tags())")?), @r#"
4967        Difference(
4968            CommitRef(Bookmarks(Pattern(Substring("")))),
4969            CommitRef(Tags(Pattern(Substring("")))),
4970        )
4971        "#);
4972        Ok(())
4973    }
4974
4975    #[test]
4976    fn test_optimize_unchanged_subtree() -> TestResult {
4977        fn unwrap_union(
4978            expression: &UserRevsetExpression,
4979        ) -> (&Arc<UserRevsetExpression>, &Arc<UserRevsetExpression>) {
4980            match expression {
4981                RevsetExpression::Union(left, right) => (left, right),
4982                _ => panic!("unexpected expression: {expression:?}"),
4983            }
4984        }
4985
4986        // transform_expression_bottom_up() should not recreate tree unnecessarily.
4987        let parsed = parse("foo-")?;
4988        let optimized = optimize(parsed.clone());
4989        assert!(Arc::ptr_eq(&parsed, &optimized));
4990
4991        let parsed = parse("bookmarks() | tags()")?;
4992        let optimized = optimize(parsed.clone());
4993        assert!(Arc::ptr_eq(&parsed, &optimized));
4994
4995        let parsed = parse("bookmarks() & tags()")?;
4996        let optimized = optimize(parsed.clone());
4997        assert!(Arc::ptr_eq(&parsed, &optimized));
4998
4999        // Only left subtree should be rewritten.
5000        let parsed = parse("(bookmarks() & all()) | tags()")?;
5001        let optimized = optimize(parsed.clone());
5002        assert_matches!(
5003            unwrap_union(&optimized).0.as_ref(),
5004            RevsetExpression::CommitRef(RevsetCommitRef::Bookmarks(_))
5005        );
5006        assert!(Arc::ptr_eq(
5007            unwrap_union(&parsed).1,
5008            unwrap_union(&optimized).1
5009        ));
5010
5011        // Only right subtree should be rewritten.
5012        let parsed = parse("bookmarks() | (all() & tags())")?;
5013        let optimized = optimize(parsed.clone());
5014        assert!(Arc::ptr_eq(
5015            unwrap_union(&parsed).0,
5016            unwrap_union(&optimized).0
5017        ));
5018        assert_matches!(
5019            unwrap_union(&optimized).1.as_ref(),
5020            RevsetExpression::CommitRef(RevsetCommitRef::Tags(_))
5021        );
5022        Ok(())
5023    }
5024
5025    #[test]
5026    fn test_optimize_basic() -> TestResult {
5027        let settings = insta_settings();
5028        let _guard = settings.bind_to_scope();
5029
5030        insta::assert_debug_snapshot!(optimize(parse("all() | none()")?), @"All");
5031        insta::assert_debug_snapshot!(optimize(parse("all() & none()")?), @"None");
5032        insta::assert_debug_snapshot!(optimize(parse("root() | all()")?), @"All");
5033        insta::assert_debug_snapshot!(optimize(parse("root() & all()")?), @"Root");
5034        insta::assert_debug_snapshot!(optimize(parse("none() | root()")?), @"Root");
5035        insta::assert_debug_snapshot!(optimize(parse("none() & root()")?), @"None");
5036        insta::assert_debug_snapshot!(optimize(parse("~none()")?), @"All");
5037        insta::assert_debug_snapshot!(optimize(parse("~~none()")?), @"None");
5038        insta::assert_debug_snapshot!(optimize(parse("~all()")?), @"None");
5039        insta::assert_debug_snapshot!(optimize(parse("~~all()")?), @"All");
5040        insta::assert_debug_snapshot!(optimize(parse("~~foo")?), @r#"CommitRef(Symbol("foo"))"#);
5041        insta::assert_debug_snapshot!(
5042            optimize(parse("(root() | none()) & (visible_heads() | ~~all())")?), @"Root");
5043        insta::assert_debug_snapshot!(
5044            optimize(UserRevsetExpression::commits(vec![])), @"None");
5045        insta::assert_debug_snapshot!(
5046            optimize(UserRevsetExpression::commits(vec![]).negated()), @"All");
5047        Ok(())
5048    }
5049
5050    #[test]
5051    fn test_optimize_difference() -> TestResult {
5052        let settings = insta_settings();
5053        let _guard = settings.bind_to_scope();
5054
5055        insta::assert_debug_snapshot!(optimize(parse("foo & ~bar")?), @r#"
5056        Difference(
5057            CommitRef(Symbol("foo")),
5058            CommitRef(Symbol("bar")),
5059        )
5060        "#);
5061        insta::assert_debug_snapshot!(optimize(parse("~foo & bar")?), @r#"
5062        Difference(
5063            CommitRef(Symbol("bar")),
5064            CommitRef(Symbol("foo")),
5065        )
5066        "#);
5067        insta::assert_debug_snapshot!(optimize(parse("~foo & bar & ~baz")?), @r#"
5068        Difference(
5069            Difference(
5070                CommitRef(Symbol("bar")),
5071                CommitRef(Symbol("foo")),
5072            ),
5073            CommitRef(Symbol("baz")),
5074        )
5075        "#);
5076        insta::assert_debug_snapshot!(optimize(parse("(all() & ~foo) & bar")?), @r#"
5077        Difference(
5078            CommitRef(Symbol("bar")),
5079            CommitRef(Symbol("foo")),
5080        )
5081        "#);
5082
5083        // Binary difference operation should go through the same optimization passes.
5084        insta::assert_debug_snapshot!(
5085            optimize(parse("all() ~ foo")?),
5086            @r#"NotIn(CommitRef(Symbol("foo")))"#);
5087        insta::assert_debug_snapshot!(optimize(parse("foo ~ bar")?), @r#"
5088        Difference(
5089            CommitRef(Symbol("foo")),
5090            CommitRef(Symbol("bar")),
5091        )
5092        "#);
5093        insta::assert_debug_snapshot!(optimize(parse("(all() ~ foo) & bar")?), @r#"
5094        Difference(
5095            CommitRef(Symbol("bar")),
5096            CommitRef(Symbol("foo")),
5097        )
5098        "#);
5099
5100        // Range expression.
5101        insta::assert_debug_snapshot!(optimize(parse("::foo & ~::bar")?), @r#"
5102        Range {
5103            roots: CommitRef(Symbol("bar")),
5104            heads: CommitRef(Symbol("foo")),
5105            generation: 0..18446744073709551615,
5106            parents_range: 0..4294967295,
5107        }
5108        "#);
5109        insta::assert_debug_snapshot!(optimize(parse("~::foo & ::bar")?), @r#"
5110        Range {
5111            roots: CommitRef(Symbol("foo")),
5112            heads: CommitRef(Symbol("bar")),
5113            generation: 0..18446744073709551615,
5114            parents_range: 0..4294967295,
5115        }
5116        "#);
5117        insta::assert_debug_snapshot!(optimize(parse("foo..")?), @r#"
5118        Range {
5119            roots: CommitRef(Symbol("foo")),
5120            heads: VisibleHeadsOrReferenced,
5121            generation: 0..18446744073709551615,
5122            parents_range: 0..4294967295,
5123        }
5124        "#);
5125        insta::assert_debug_snapshot!(optimize(parse("foo..bar")?), @r#"
5126        Range {
5127            roots: CommitRef(Symbol("foo")),
5128            heads: CommitRef(Symbol("bar")),
5129            generation: 0..18446744073709551615,
5130            parents_range: 0..4294967295,
5131        }
5132        "#);
5133        insta::assert_debug_snapshot!(optimize(parse("foo.. & ::bar")?), @r#"
5134        Range {
5135            roots: CommitRef(Symbol("foo")),
5136            heads: CommitRef(Symbol("bar")),
5137            generation: 0..18446744073709551615,
5138            parents_range: 0..4294967295,
5139        }
5140        "#);
5141        insta::assert_debug_snapshot!(optimize(parse("foo.. & first_ancestors(bar)")?), @r#"
5142        Range {
5143            roots: CommitRef(Symbol("foo")),
5144            heads: CommitRef(Symbol("bar")),
5145            generation: 0..18446744073709551615,
5146            parents_range: 0..1,
5147        }
5148        "#);
5149
5150        // Double/triple negates.
5151        insta::assert_debug_snapshot!(optimize(parse("foo & ~~bar")?), @r#"
5152        Intersection(
5153            CommitRef(Symbol("foo")),
5154            CommitRef(Symbol("bar")),
5155        )
5156        "#);
5157        insta::assert_debug_snapshot!(optimize(parse("foo & ~~~bar")?), @r#"
5158        Difference(
5159            CommitRef(Symbol("foo")),
5160            CommitRef(Symbol("bar")),
5161        )
5162        "#);
5163        insta::assert_debug_snapshot!(optimize(parse("~(all() & ~foo) & bar")?), @r#"
5164        Intersection(
5165            CommitRef(Symbol("foo")),
5166            CommitRef(Symbol("bar")),
5167        )
5168        "#);
5169
5170        // Should be better than '(all() & ~foo) & (all() & ~bar)'.
5171        insta::assert_debug_snapshot!(optimize(parse("~foo & ~bar")?), @r#"
5172        Difference(
5173            NotIn(CommitRef(Symbol("foo"))),
5174            CommitRef(Symbol("bar")),
5175        )
5176        "#);
5177
5178        // The roots of multiple ranges can be folded after being unfolded.
5179        insta::assert_debug_snapshot!(optimize(parse("a..b & c..d")?), @r#"
5180        Intersection(
5181            Range {
5182                roots: Union(
5183                    CommitRef(Symbol("a")),
5184                    CommitRef(Symbol("c")),
5185                ),
5186                heads: CommitRef(Symbol("b")),
5187                generation: 0..18446744073709551615,
5188                parents_range: 0..4294967295,
5189            },
5190            Ancestors {
5191                heads: CommitRef(Symbol("d")),
5192                generation: 0..18446744073709551615,
5193                parents_range: 0..4294967295,
5194            },
5195        )
5196        "#);
5197
5198        // Negated `first_ancestors()` doesn't prevent re-folding.
5199        insta::assert_debug_snapshot!(optimize(parse("foo..bar ~ first_ancestors(baz)")?), @r#"
5200        Difference(
5201            Range {
5202                roots: CommitRef(Symbol("foo")),
5203                heads: CommitRef(Symbol("bar")),
5204                generation: 0..18446744073709551615,
5205                parents_range: 0..4294967295,
5206            },
5207            Ancestors {
5208                heads: CommitRef(Symbol("baz")),
5209                generation: 0..18446744073709551615,
5210                parents_range: 0..1,
5211            },
5212        )
5213        "#);
5214
5215        // Negated ancestors can be combined into a range regardless of intersection
5216        // grouping order and intervening expressions.
5217        insta::assert_debug_snapshot!(optimize(parse("foo ~ ::a & (::b & bar & ::c) & (baz ~ ::d)")?), @r#"
5218        Intersection(
5219            Intersection(
5220                Intersection(
5221                    Intersection(
5222                        Range {
5223                            roots: Union(
5224                                CommitRef(Symbol("a")),
5225                                CommitRef(Symbol("d")),
5226                            ),
5227                            heads: CommitRef(Symbol("b")),
5228                            generation: 0..18446744073709551615,
5229                            parents_range: 0..4294967295,
5230                        },
5231                        Ancestors {
5232                            heads: CommitRef(Symbol("c")),
5233                            generation: 0..18446744073709551615,
5234                            parents_range: 0..4294967295,
5235                        },
5236                    ),
5237                    CommitRef(Symbol("foo")),
5238                ),
5239                CommitRef(Symbol("bar")),
5240            ),
5241            CommitRef(Symbol("baz")),
5242        )
5243        "#);
5244        Ok(())
5245    }
5246
5247    #[test]
5248    fn test_optimize_not_in_ancestors() -> TestResult {
5249        let settings = insta_settings();
5250        let _guard = settings.bind_to_scope();
5251
5252        // '~(::foo)' is equivalent to 'foo..'.
5253        insta::assert_debug_snapshot!(optimize(parse("~(::foo)")?), @r#"
5254        Range {
5255            roots: CommitRef(Symbol("foo")),
5256            heads: VisibleHeadsOrReferenced,
5257            generation: 0..18446744073709551615,
5258            parents_range: 0..4294967295,
5259        }
5260        "#);
5261
5262        // '~(::foo-)' is equivalent to 'foo-..'.
5263        insta::assert_debug_snapshot!(optimize(parse("~(::foo-)")?), @r#"
5264        Range {
5265            roots: Ancestors {
5266                heads: CommitRef(Symbol("foo")),
5267                generation: 1..2,
5268                parents_range: 0..4294967295,
5269            },
5270            heads: VisibleHeadsOrReferenced,
5271            generation: 0..18446744073709551615,
5272            parents_range: 0..4294967295,
5273        }
5274        "#);
5275        insta::assert_debug_snapshot!(optimize(parse("~(::foo--)")?), @r#"
5276        Range {
5277            roots: Ancestors {
5278                heads: CommitRef(Symbol("foo")),
5279                generation: 2..3,
5280                parents_range: 0..4294967295,
5281            },
5282            heads: VisibleHeadsOrReferenced,
5283            generation: 0..18446744073709551615,
5284            parents_range: 0..4294967295,
5285        }
5286        "#);
5287
5288        // Bounded ancestors shouldn't be substituted.
5289        insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo, 1)")?), @r#"
5290        NotIn(
5291            Ancestors {
5292                heads: CommitRef(Symbol("foo")),
5293                generation: 0..1,
5294                parents_range: 0..4294967295,
5295            },
5296        )
5297        "#);
5298        insta::assert_debug_snapshot!(optimize(parse("~ancestors(foo-, 1)")?), @r#"
5299        NotIn(
5300            Ancestors {
5301                heads: CommitRef(Symbol("foo")),
5302                generation: 1..2,
5303                parents_range: 0..4294967295,
5304            },
5305        )
5306        "#);
5307        Ok(())
5308    }
5309
5310    #[test]
5311    fn test_optimize_filter_difference() -> TestResult {
5312        let settings = insta_settings();
5313        let _guard = settings.bind_to_scope();
5314
5315        // '~empty()' -> '~~file(*)' -> 'file(*)'
5316        insta::assert_debug_snapshot!(optimize(parse("~empty()")?), @"Filter(File(All))");
5317
5318        // '& baz' can be moved into the filter node, and form a difference node.
5319        insta::assert_debug_snapshot!(
5320            optimize(parse("(author_name(foo) & ~bar) & baz")?), @r#"
5321        Intersection(
5322            Difference(
5323                CommitRef(Symbol("baz")),
5324                CommitRef(Symbol("bar")),
5325            ),
5326            Filter(AuthorName(Pattern(Exact("foo")))),
5327        )
5328        "#);
5329
5330        // '~set & filter()' shouldn't be substituted.
5331        insta::assert_debug_snapshot!(
5332            optimize(parse("~foo & author_name(bar)")?), @r#"
5333        Intersection(
5334            NotIn(CommitRef(Symbol("foo"))),
5335            Filter(AuthorName(Pattern(Exact("bar")))),
5336        )
5337        "#);
5338        insta::assert_debug_snapshot!(
5339            optimize(parse("~foo & (author_name(bar) | baz)")?), @r#"
5340        Intersection(
5341            NotIn(CommitRef(Symbol("foo"))),
5342            AsFilter(
5343                Union(
5344                    Filter(AuthorName(Pattern(Exact("bar")))),
5345                    CommitRef(Symbol("baz")),
5346                ),
5347            ),
5348        )
5349        "#);
5350
5351        // Filter should be moved right of the intersection.
5352        insta::assert_debug_snapshot!(
5353            optimize(parse("author_name(foo) ~ bar")?), @r#"
5354        Intersection(
5355            NotIn(CommitRef(Symbol("bar"))),
5356            Filter(AuthorName(Pattern(Exact("foo")))),
5357        )
5358        "#);
5359        Ok(())
5360    }
5361
5362    #[test]
5363    fn test_optimize_filter_intersection() -> TestResult {
5364        let settings = insta_settings();
5365        let _guard = settings.bind_to_scope();
5366
5367        insta::assert_debug_snapshot!(
5368            optimize(parse("author_name(foo)")?),
5369            @r#"Filter(AuthorName(Pattern(Exact("foo"))))"#);
5370
5371        insta::assert_debug_snapshot!(optimize(parse("foo & description(bar)")?), @r#"
5372        Intersection(
5373            CommitRef(Symbol("foo")),
5374            Filter(Description(Pattern(Exact("bar")))),
5375        )
5376        "#);
5377        insta::assert_debug_snapshot!(optimize(parse("author_name(foo) & bar")?), @r#"
5378        Intersection(
5379            CommitRef(Symbol("bar")),
5380            Filter(AuthorName(Pattern(Exact("foo")))),
5381        )
5382        "#);
5383        insta::assert_debug_snapshot!(
5384            optimize(parse("author_name(foo) & committer_name(bar)")?), @r#"
5385        AsFilter(
5386            Intersection(
5387                Filter(AuthorName(Pattern(Exact("foo")))),
5388                Filter(CommitterName(Pattern(Exact("bar")))),
5389            ),
5390        )
5391        "#);
5392        insta::assert_debug_snapshot!(optimize(parse("divergent() & foo")?), @r#"
5393        Intersection(
5394            CommitRef(Symbol("foo")),
5395            AsFilter(Divergent),
5396        )
5397        "#);
5398
5399        insta::assert_debug_snapshot!(
5400            optimize(parse("foo & description(bar) & author_name(baz)")?), @r#"
5401        Intersection(
5402            CommitRef(Symbol("foo")),
5403            AsFilter(
5404                Intersection(
5405                    Filter(Description(Pattern(Exact("bar")))),
5406                    Filter(AuthorName(Pattern(Exact("baz")))),
5407                ),
5408            ),
5409        )
5410        "#);
5411        insta::assert_debug_snapshot!(
5412            optimize(parse("committer_name(foo) & bar & author_name(baz)")?), @r#"
5413        Intersection(
5414            CommitRef(Symbol("bar")),
5415            AsFilter(
5416                Intersection(
5417                    Filter(CommitterName(Pattern(Exact("foo")))),
5418                    Filter(AuthorName(Pattern(Exact("baz")))),
5419                ),
5420            ),
5421        )
5422        "#);
5423        insta::assert_debug_snapshot!(
5424            optimize(parse_with_workspace(
5425                "committer_name(foo) & files(bar) & baz",
5426                WorkspaceName::DEFAULT)?,
5427            ), @r#"
5428        Intersection(
5429            CommitRef(Symbol("baz")),
5430            AsFilter(
5431                Intersection(
5432                    Filter(CommitterName(Pattern(Exact("foo")))),
5433                    Filter(File(Pattern(PrefixPath("bar")))),
5434                ),
5435            ),
5436        )
5437        "#);
5438        insta::assert_debug_snapshot!(
5439            optimize(parse_with_workspace(
5440                "committer_name(foo) & files(bar) & author_name(baz)",
5441                WorkspaceName::DEFAULT)?,
5442            ), @r#"
5443        AsFilter(
5444            Intersection(
5445                Intersection(
5446                    Filter(CommitterName(Pattern(Exact("foo")))),
5447                    Filter(File(Pattern(PrefixPath("bar")))),
5448                ),
5449                Filter(AuthorName(Pattern(Exact("baz")))),
5450            ),
5451        )
5452        "#);
5453        insta::assert_debug_snapshot!(
5454            optimize(parse_with_workspace(
5455                "foo & files(bar) & baz",
5456                WorkspaceName::DEFAULT)?,
5457            ), @r#"
5458        Intersection(
5459            Intersection(
5460                CommitRef(Symbol("foo")),
5461                CommitRef(Symbol("baz")),
5462            ),
5463            Filter(File(Pattern(PrefixPath("bar")))),
5464        )
5465        "#);
5466
5467        insta::assert_debug_snapshot!(
5468            optimize(parse("foo & description(bar) & author_name(baz) & qux")?), @r#"
5469        Intersection(
5470            Intersection(
5471                CommitRef(Symbol("foo")),
5472                CommitRef(Symbol("qux")),
5473            ),
5474            AsFilter(
5475                Intersection(
5476                    Filter(Description(Pattern(Exact("bar")))),
5477                    Filter(AuthorName(Pattern(Exact("baz")))),
5478                ),
5479            ),
5480        )
5481        "#);
5482        insta::assert_debug_snapshot!(
5483            optimize(parse("foo & description(bar) & parents(author_name(baz)) & qux")?),
5484            @r#"
5485        Intersection(
5486            Intersection(
5487                Intersection(
5488                    CommitRef(Symbol("foo")),
5489                    Ancestors {
5490                        heads: Filter(AuthorName(Pattern(Exact("baz")))),
5491                        generation: 1..2,
5492                        parents_range: 0..4294967295,
5493                    },
5494                ),
5495                CommitRef(Symbol("qux")),
5496            ),
5497            Filter(Description(Pattern(Exact("bar")))),
5498        )
5499        "#);
5500        insta::assert_debug_snapshot!(
5501            optimize(parse("foo & description(bar) & parents(author_name(baz) & qux)")?),
5502            @r#"
5503        Intersection(
5504            Intersection(
5505                CommitRef(Symbol("foo")),
5506                Ancestors {
5507                    heads: Intersection(
5508                        CommitRef(Symbol("qux")),
5509                        Filter(AuthorName(Pattern(Exact("baz")))),
5510                    ),
5511                    generation: 1..2,
5512                    parents_range: 0..4294967295,
5513                },
5514            ),
5515            Filter(Description(Pattern(Exact("bar")))),
5516        )
5517        "#);
5518
5519        // Symbols have to be pushed down to the innermost filter node.
5520        insta::assert_debug_snapshot!(
5521            optimize(parse("(a & author_name(A)) & (b & author_name(B)) & (c & author_name(C))")?),
5522            @r#"
5523        Intersection(
5524            Intersection(
5525                Intersection(
5526                    CommitRef(Symbol("a")),
5527                    CommitRef(Symbol("b")),
5528                ),
5529                CommitRef(Symbol("c")),
5530            ),
5531            AsFilter(
5532                Intersection(
5533                    Intersection(
5534                        Filter(AuthorName(Pattern(Exact("A")))),
5535                        Filter(AuthorName(Pattern(Exact("B")))),
5536                    ),
5537                    Filter(AuthorName(Pattern(Exact("C")))),
5538                ),
5539            ),
5540        )
5541        "#);
5542        insta::assert_debug_snapshot!(
5543            optimize(parse("(a & author_name(A)) & ((b & author_name(B)) & (c & author_name(C))) & d")?),
5544            @r#"
5545        Intersection(
5546            Intersection(
5547                Intersection(
5548                    Intersection(
5549                        CommitRef(Symbol("a")),
5550                        CommitRef(Symbol("b")),
5551                    ),
5552                    CommitRef(Symbol("c")),
5553                ),
5554                CommitRef(Symbol("d")),
5555            ),
5556            AsFilter(
5557                Intersection(
5558                    Intersection(
5559                        Filter(AuthorName(Pattern(Exact("A")))),
5560                        Filter(AuthorName(Pattern(Exact("B")))),
5561                    ),
5562                    Filter(AuthorName(Pattern(Exact("C")))),
5563                ),
5564            ),
5565        )
5566        "#);
5567
5568        // 'all()' moves in to 'filter()' first, so 'A & filter()' can be found.
5569        insta::assert_debug_snapshot!(
5570            optimize(parse("foo & (all() & description(bar)) & (author_name(baz) & all())")?),
5571            @r#"
5572        Intersection(
5573            CommitRef(Symbol("foo")),
5574            AsFilter(
5575                Intersection(
5576                    Filter(Description(Pattern(Exact("bar")))),
5577                    Filter(AuthorName(Pattern(Exact("baz")))),
5578                ),
5579            ),
5580        )
5581        "#);
5582
5583        // Filter node shouldn't move across at_operation() boundary.
5584        insta::assert_debug_snapshot!(
5585            optimize(parse("author_name(foo) & bar & at_operation(@-, committer_name(baz))")?),
5586            @r#"
5587        Intersection(
5588            Intersection(
5589                CommitRef(Symbol("bar")),
5590                AtOperation {
5591                    operation: "@-",
5592                    candidates: Filter(CommitterName(Pattern(Exact("baz")))),
5593                },
5594            ),
5595            Filter(AuthorName(Pattern(Exact("foo")))),
5596        )
5597        "#);
5598        Ok(())
5599    }
5600
5601    #[test]
5602    fn test_optimize_filter_subtree() -> TestResult {
5603        let settings = insta_settings();
5604        let _guard = settings.bind_to_scope();
5605
5606        insta::assert_debug_snapshot!(
5607            optimize(parse("(author_name(foo) | bar) & baz")?), @r#"
5608        Intersection(
5609            CommitRef(Symbol("baz")),
5610            AsFilter(
5611                Union(
5612                    Filter(AuthorName(Pattern(Exact("foo")))),
5613                    CommitRef(Symbol("bar")),
5614                ),
5615            ),
5616        )
5617        "#);
5618
5619        // 'merges() & foo' can be evaluated independently
5620        insta::assert_debug_snapshot!(
5621            optimize(parse("merges() & foo | bar")?), @r#"
5622        Union(
5623            Intersection(
5624                CommitRef(Symbol("foo")),
5625                Filter(ParentCount(2..4294967295)),
5626            ),
5627            CommitRef(Symbol("bar")),
5628        )
5629        "#);
5630
5631        // 'merges() & foo' can be evaluated independently, but 'conflicts()'
5632        // can't. We'll need implicit 'all() & _' anyway.
5633        insta::assert_debug_snapshot!(
5634            optimize(parse("merges() & foo | conflicts()")?), @r#"
5635        AsFilter(
5636            Union(
5637                Intersection(
5638                    CommitRef(Symbol("foo")),
5639                    Filter(ParentCount(2..4294967295)),
5640                ),
5641                Filter(HasConflict),
5642            ),
5643        )
5644        "#);
5645
5646        // Nested filter intersection with union
5647        insta::assert_debug_snapshot!(
5648            optimize(parse("foo | conflicts() & merges() & signed()")?), @r#"
5649        AsFilter(
5650            Union(
5651                CommitRef(Symbol("foo")),
5652                Intersection(
5653                    Intersection(
5654                        Filter(HasConflict),
5655                        Filter(ParentCount(2..4294967295)),
5656                    ),
5657                    Filter(Signed),
5658                ),
5659            ),
5660        )
5661        "#);
5662
5663        insta::assert_debug_snapshot!(
5664            optimize(parse("(foo | committer_name(bar)) & description(baz) & qux")?), @r#"
5665        Intersection(
5666            CommitRef(Symbol("qux")),
5667            AsFilter(
5668                Intersection(
5669                    Union(
5670                        CommitRef(Symbol("foo")),
5671                        Filter(CommitterName(Pattern(Exact("bar")))),
5672                    ),
5673                    Filter(Description(Pattern(Exact("baz")))),
5674                ),
5675            ),
5676        )
5677        "#);
5678
5679        insta::assert_debug_snapshot!(
5680            optimize(parse(
5681                "(~present(author_name(foo) & description(bar)) | baz) & qux")?), @r#"
5682        Intersection(
5683            CommitRef(Symbol("qux")),
5684            AsFilter(
5685                Union(
5686                    NotIn(
5687                        Present(
5688                            Intersection(
5689                                Filter(AuthorName(Pattern(Exact("foo")))),
5690                                Filter(Description(Pattern(Exact("bar")))),
5691                            ),
5692                        ),
5693                    ),
5694                    CommitRef(Symbol("baz")),
5695                ),
5696            ),
5697        )
5698        "#);
5699
5700        // Symbols have to be pushed down to the innermost filter node.
5701        insta::assert_debug_snapshot!(
5702            optimize(parse(
5703                "(a & (author_name(A) | 0)) & (b & (author_name(B) | 1)) & (c & (author_name(C) | 2))")?),
5704            @r#"
5705        Intersection(
5706            Intersection(
5707                Intersection(
5708                    CommitRef(Symbol("a")),
5709                    CommitRef(Symbol("b")),
5710                ),
5711                CommitRef(Symbol("c")),
5712            ),
5713            AsFilter(
5714                Intersection(
5715                    Intersection(
5716                        Union(
5717                            Filter(AuthorName(Pattern(Exact("A")))),
5718                            CommitRef(Symbol("0")),
5719                        ),
5720                        Union(
5721                            Filter(AuthorName(Pattern(Exact("B")))),
5722                            CommitRef(Symbol("1")),
5723                        ),
5724                    ),
5725                    Union(
5726                        Filter(AuthorName(Pattern(Exact("C")))),
5727                        CommitRef(Symbol("2")),
5728                    ),
5729                ),
5730            ),
5731        )
5732        "#);
5733
5734        // Filters can be merged after ancestor unions are folded.
5735        insta::assert_debug_snapshot!(optimize(parse("::foo | ::author_name(bar)")?), @r#"
5736        Ancestors {
5737            heads: HeadsRange {
5738                roots: None,
5739                heads: VisibleHeadsOrReferenced,
5740                parents_range: 0..4294967295,
5741                filter: AsFilter(
5742                    Union(
5743                        CommitRef(Symbol("foo")),
5744                        Filter(AuthorName(Pattern(Exact("bar")))),
5745                    ),
5746                ),
5747            },
5748            generation: 0..18446744073709551615,
5749            parents_range: 0..4294967295,
5750        }
5751        "#);
5752        Ok(())
5753    }
5754
5755    #[test]
5756    fn test_optimize_ancestors() -> TestResult {
5757        let settings = insta_settings();
5758        let _guard = settings.bind_to_scope();
5759
5760        // Typical scenario: fold nested parents()
5761        insta::assert_debug_snapshot!(optimize(parse("foo--")?), @r#"
5762        Ancestors {
5763            heads: CommitRef(Symbol("foo")),
5764            generation: 2..3,
5765            parents_range: 0..4294967295,
5766        }
5767        "#);
5768        insta::assert_debug_snapshot!(optimize(parse("::(foo---)")?), @r#"
5769        Ancestors {
5770            heads: CommitRef(Symbol("foo")),
5771            generation: 3..18446744073709551615,
5772            parents_range: 0..4294967295,
5773        }
5774        "#);
5775        insta::assert_debug_snapshot!(optimize(parse("(::foo)---")?), @r#"
5776        Ancestors {
5777            heads: CommitRef(Symbol("foo")),
5778            generation: 3..18446744073709551615,
5779            parents_range: 0..4294967295,
5780        }
5781        "#);
5782
5783        // 'foo-+' is not 'foo'.
5784        insta::assert_debug_snapshot!(optimize(parse("foo---+")?), @r#"
5785        Descendants {
5786            roots: Ancestors {
5787                heads: CommitRef(Symbol("foo")),
5788                generation: 3..4,
5789                parents_range: 0..4294967295,
5790            },
5791            generation: 1..2,
5792        }
5793        "#);
5794
5795        // For 'roots..heads', heads can be folded.
5796        insta::assert_debug_snapshot!(optimize(parse("foo..(bar--)")?), @r#"
5797        Range {
5798            roots: CommitRef(Symbol("foo")),
5799            heads: CommitRef(Symbol("bar")),
5800            generation: 2..18446744073709551615,
5801            parents_range: 0..4294967295,
5802        }
5803        "#);
5804        // roots can also be folded, and the range expression is reconstructed.
5805        insta::assert_debug_snapshot!(optimize(parse("(foo--)..(bar---)")?), @r#"
5806        Range {
5807            roots: Ancestors {
5808                heads: CommitRef(Symbol("foo")),
5809                generation: 2..3,
5810                parents_range: 0..4294967295,
5811            },
5812            heads: CommitRef(Symbol("bar")),
5813            generation: 3..18446744073709551615,
5814            parents_range: 0..4294967295,
5815        }
5816        "#);
5817        // Bounded ancestors shouldn't be substituted to range.
5818        insta::assert_debug_snapshot!(
5819            optimize(parse("~ancestors(foo, 2) & ::bar")?), @r#"
5820        Difference(
5821            Ancestors {
5822                heads: CommitRef(Symbol("bar")),
5823                generation: 0..18446744073709551615,
5824                parents_range: 0..4294967295,
5825            },
5826            Ancestors {
5827                heads: CommitRef(Symbol("foo")),
5828                generation: 0..2,
5829                parents_range: 0..4294967295,
5830            },
5831        )
5832        "#);
5833
5834        // If inner range is bounded by roots, it cannot be merged.
5835        // e.g. '..(foo..foo)' is equivalent to '..none()', not to '..foo'
5836        insta::assert_debug_snapshot!(optimize(parse("(foo..bar)--")?), @r#"
5837        Ancestors {
5838            heads: Range {
5839                roots: CommitRef(Symbol("foo")),
5840                heads: CommitRef(Symbol("bar")),
5841                generation: 0..18446744073709551615,
5842                parents_range: 0..4294967295,
5843            },
5844            generation: 2..3,
5845            parents_range: 0..4294967295,
5846        }
5847        "#);
5848        insta::assert_debug_snapshot!(optimize(parse("foo..(bar..baz)")?), @r#"
5849        Range {
5850            roots: CommitRef(Symbol("foo")),
5851            heads: HeadsRange {
5852                roots: CommitRef(Symbol("bar")),
5853                heads: CommitRef(Symbol("baz")),
5854                parents_range: 0..4294967295,
5855                filter: All,
5856            },
5857            generation: 0..18446744073709551615,
5858            parents_range: 0..4294967295,
5859        }
5860        "#);
5861
5862        // Ancestors of empty generation range should be empty.
5863        insta::assert_debug_snapshot!(
5864            optimize(parse("ancestors(ancestors(foo), 0)")?), @r#"
5865        Ancestors {
5866            heads: CommitRef(Symbol("foo")),
5867            generation: 0..0,
5868            parents_range: 0..4294967295,
5869        }
5870        "#
5871        );
5872        insta::assert_debug_snapshot!(
5873            optimize(parse("ancestors(ancestors(foo, 0))")?), @r#"
5874        Ancestors {
5875            heads: CommitRef(Symbol("foo")),
5876            generation: 0..0,
5877            parents_range: 0..4294967295,
5878        }
5879        "#
5880        );
5881
5882        // Ancestors can only be folded if parent ranges match.
5883        insta::assert_debug_snapshot!(
5884            optimize(parse("first_ancestors(first_ancestors(foo, 5), 5)")?), @r#"
5885        Ancestors {
5886            heads: CommitRef(Symbol("foo")),
5887            generation: 0..9,
5888            parents_range: 0..1,
5889        }
5890        "#
5891        );
5892        insta::assert_debug_snapshot!(
5893            optimize(parse("first_ancestors(first_parent(foo), 5)")?), @r#"
5894        Ancestors {
5895            heads: CommitRef(Symbol("foo")),
5896            generation: 1..6,
5897            parents_range: 0..1,
5898        }
5899        "#
5900        );
5901        insta::assert_debug_snapshot!(
5902            optimize(parse("first_ancestors(ancestors(foo, 5), 5)")?), @r#"
5903        Ancestors {
5904            heads: Ancestors {
5905                heads: CommitRef(Symbol("foo")),
5906                generation: 0..5,
5907                parents_range: 0..4294967295,
5908            },
5909            generation: 0..5,
5910            parents_range: 0..1,
5911        }
5912        "#
5913        );
5914        insta::assert_debug_snapshot!(
5915            optimize(parse("ancestors(first_ancestors(foo, 5), 5)")?), @r#"
5916        Ancestors {
5917            heads: Ancestors {
5918                heads: CommitRef(Symbol("foo")),
5919                generation: 0..5,
5920                parents_range: 0..1,
5921            },
5922            generation: 0..5,
5923            parents_range: 0..4294967295,
5924        }
5925        "#
5926        );
5927        Ok(())
5928    }
5929
5930    #[test]
5931    fn test_optimize_descendants() -> TestResult {
5932        let settings = insta_settings();
5933        let _guard = settings.bind_to_scope();
5934
5935        // Typical scenario: fold nested children()
5936        insta::assert_debug_snapshot!(optimize(parse("foo++")?), @r#"
5937        Descendants {
5938            roots: CommitRef(Symbol("foo")),
5939            generation: 2..3,
5940        }
5941        "#);
5942        insta::assert_debug_snapshot!(optimize(parse("(foo+++)::")?), @r#"
5943        Descendants {
5944            roots: CommitRef(Symbol("foo")),
5945            generation: 3..18446744073709551615,
5946        }
5947        "#);
5948        insta::assert_debug_snapshot!(optimize(parse("(foo::)+++")?), @r#"
5949        Descendants {
5950            roots: CommitRef(Symbol("foo")),
5951            generation: 3..18446744073709551615,
5952        }
5953        "#);
5954
5955        // 'foo+-' is not 'foo'.
5956        insta::assert_debug_snapshot!(optimize(parse("foo+++-")?), @r#"
5957        Ancestors {
5958            heads: Descendants {
5959                roots: CommitRef(Symbol("foo")),
5960                generation: 3..4,
5961            },
5962            generation: 1..2,
5963            parents_range: 0..4294967295,
5964        }
5965        "#);
5966
5967        // TODO: Inner Descendants can be folded into DagRange. Perhaps, we can rewrite
5968        // 'x::y' to 'x:: & ::y' first, so the common substitution rule can handle both
5969        // 'x+::y' and 'x+ & ::y'.
5970        insta::assert_debug_snapshot!(optimize(parse("(foo++)::bar")?), @r#"
5971        DagRange {
5972            roots: Descendants {
5973                roots: CommitRef(Symbol("foo")),
5974                generation: 2..3,
5975            },
5976            heads: CommitRef(Symbol("bar")),
5977        }
5978        "#);
5979        Ok(())
5980    }
5981
5982    #[test]
5983    fn test_optimize_flatten_intersection() -> TestResult {
5984        let settings = insta_settings();
5985        let _guard = settings.bind_to_scope();
5986
5987        // Nested intersections should be flattened.
5988        insta::assert_debug_snapshot!(optimize(parse("a & ((b & c) & (d & e))")?), @r#"
5989        Intersection(
5990            Intersection(
5991                Intersection(
5992                    Intersection(
5993                        CommitRef(Symbol("a")),
5994                        CommitRef(Symbol("b")),
5995                    ),
5996                    CommitRef(Symbol("c")),
5997                ),
5998                CommitRef(Symbol("d")),
5999            ),
6000            CommitRef(Symbol("e")),
6001        )
6002        "#);
6003        Ok(())
6004    }
6005
6006    #[test]
6007    fn test_optimize_ancestors_union() -> TestResult {
6008        let settings = insta_settings();
6009        let _guard = settings.bind_to_scope();
6010
6011        // Ancestors should be folded in unions.
6012        insta::assert_debug_snapshot!(optimize(parse("::a | ::b | ::c | ::d")?), @r#"
6013        Ancestors {
6014            heads: Union(
6015                Union(
6016                    CommitRef(Symbol("a")),
6017                    CommitRef(Symbol("b")),
6018                ),
6019                Union(
6020                    CommitRef(Symbol("c")),
6021                    CommitRef(Symbol("d")),
6022                ),
6023            ),
6024            generation: 0..18446744073709551615,
6025            parents_range: 0..4294967295,
6026        }
6027        "#);
6028        insta::assert_debug_snapshot!(optimize(parse("ancestors(a-) | ancestors(b)")?), @r#"
6029        Ancestors {
6030            heads: Union(
6031                Ancestors {
6032                    heads: CommitRef(Symbol("a")),
6033                    generation: 1..2,
6034                    parents_range: 0..4294967295,
6035                },
6036                CommitRef(Symbol("b")),
6037            ),
6038            generation: 0..18446744073709551615,
6039            parents_range: 0..4294967295,
6040        }
6041        "#);
6042
6043        // Negated ancestors should be folded.
6044        insta::assert_debug_snapshot!(optimize(parse("~::a- & ~::b & ~::c & ::d")?), @r#"
6045        Range {
6046            roots: Union(
6047                Union(
6048                    Ancestors {
6049                        heads: CommitRef(Symbol("a")),
6050                        generation: 1..2,
6051                        parents_range: 0..4294967295,
6052                    },
6053                    CommitRef(Symbol("b")),
6054                ),
6055                CommitRef(Symbol("c")),
6056            ),
6057            heads: CommitRef(Symbol("d")),
6058            generation: 0..18446744073709551615,
6059            parents_range: 0..4294967295,
6060        }
6061        "#);
6062        insta::assert_debug_snapshot!(optimize(parse("a..b ~ ::c- ~ ::d")?), @r#"
6063        Range {
6064            roots: Union(
6065                Union(
6066                    CommitRef(Symbol("a")),
6067                    Ancestors {
6068                        heads: CommitRef(Symbol("c")),
6069                        generation: 1..2,
6070                        parents_range: 0..4294967295,
6071                    },
6072                ),
6073                CommitRef(Symbol("d")),
6074            ),
6075            heads: CommitRef(Symbol("b")),
6076            generation: 0..18446744073709551615,
6077            parents_range: 0..4294967295,
6078        }
6079        "#);
6080
6081        // Ancestors with a bounded generation range should not be merged.
6082        insta::assert_debug_snapshot!(optimize(parse("ancestors(a, 2) | ancestors(b)")?), @r#"
6083        Union(
6084            Ancestors {
6085                heads: CommitRef(Symbol("a")),
6086                generation: 0..2,
6087                parents_range: 0..4294967295,
6088            },
6089            Ancestors {
6090                heads: CommitRef(Symbol("b")),
6091                generation: 0..18446744073709551615,
6092                parents_range: 0..4294967295,
6093            },
6094        )
6095        "#);
6096        Ok(())
6097    }
6098
6099    #[test]
6100    fn test_optimize_sort_negations_and_ancestors() -> TestResult {
6101        let settings = insta_settings();
6102        let _guard = settings.bind_to_scope();
6103
6104        // Negated ancestors and ancestors should be moved to the left, and other
6105        // negations should be moved to the right.
6106        insta::assert_debug_snapshot!(optimize(parse("~a & ::b & ~::c & d ~ e & f & ::g & ~::h")?), @r#"
6107        Difference(
6108            Difference(
6109                Intersection(
6110                    Intersection(
6111                        Intersection(
6112                            Range {
6113                                roots: Union(
6114                                    CommitRef(Symbol("c")),
6115                                    CommitRef(Symbol("h")),
6116                                ),
6117                                heads: CommitRef(Symbol("b")),
6118                                generation: 0..18446744073709551615,
6119                                parents_range: 0..4294967295,
6120                            },
6121                            Ancestors {
6122                                heads: CommitRef(Symbol("g")),
6123                                generation: 0..18446744073709551615,
6124                                parents_range: 0..4294967295,
6125                            },
6126                        ),
6127                        CommitRef(Symbol("d")),
6128                    ),
6129                    CommitRef(Symbol("f")),
6130                ),
6131                CommitRef(Symbol("a")),
6132            ),
6133            CommitRef(Symbol("e")),
6134        )
6135        "#);
6136        Ok(())
6137    }
6138
6139    #[test]
6140    fn test_optimize_heads_range() -> TestResult {
6141        let settings = insta_settings();
6142        let _guard = settings.bind_to_scope();
6143
6144        // Heads of basic range operators can be folded.
6145        insta::assert_debug_snapshot!(optimize(parse("heads(::)")?), @"
6146        HeadsRange {
6147            roots: None,
6148            heads: VisibleHeadsOrReferenced,
6149            parents_range: 0..4294967295,
6150            filter: All,
6151        }
6152        ");
6153        insta::assert_debug_snapshot!(optimize(parse("heads(::foo)")?), @r#"
6154        HeadsRange {
6155            roots: None,
6156            heads: CommitRef(Symbol("foo")),
6157            parents_range: 0..4294967295,
6158            filter: All,
6159        }
6160        "#);
6161        // It might be better to use `roots: Root`, but it would require adding a
6162        // special case for `~root()`, and this should be similar in performance.
6163        insta::assert_debug_snapshot!(optimize(parse("heads(..)")?), @"
6164        HeadsRange {
6165            roots: None,
6166            heads: VisibleHeadsOrReferenced,
6167            parents_range: 0..4294967295,
6168            filter: NotIn(Root),
6169        }
6170        ");
6171        insta::assert_debug_snapshot!(optimize(parse("heads(foo..)")?), @r#"
6172        HeadsRange {
6173            roots: CommitRef(Symbol("foo")),
6174            heads: VisibleHeadsOrReferenced,
6175            parents_range: 0..4294967295,
6176            filter: All,
6177        }
6178        "#);
6179        insta::assert_debug_snapshot!(optimize(parse("heads(..bar)")?), @r#"
6180        HeadsRange {
6181            roots: Root,
6182            heads: CommitRef(Symbol("bar")),
6183            parents_range: 0..4294967295,
6184            filter: All,
6185        }
6186        "#);
6187        insta::assert_debug_snapshot!(optimize(parse("heads(foo..bar)")?), @r#"
6188        HeadsRange {
6189            roots: CommitRef(Symbol("foo")),
6190            heads: CommitRef(Symbol("bar")),
6191            parents_range: 0..4294967295,
6192            filter: All,
6193        }
6194        "#);
6195        insta::assert_debug_snapshot!(optimize(parse("heads(~::foo & ::bar)")?), @r#"
6196        HeadsRange {
6197            roots: CommitRef(Symbol("foo")),
6198            heads: CommitRef(Symbol("bar")),
6199            parents_range: 0..4294967295,
6200            filter: All,
6201        }
6202        "#);
6203        insta::assert_debug_snapshot!(optimize(parse("heads(~::foo)")?), @r#"
6204        HeadsRange {
6205            roots: CommitRef(Symbol("foo")),
6206            heads: VisibleHeadsOrReferenced,
6207            parents_range: 0..4294967295,
6208            filter: All,
6209        }
6210        "#);
6211        insta::assert_debug_snapshot!(optimize(parse("heads(a..b & c..d)")?), @r#"
6212        HeadsRange {
6213            roots: Union(
6214                CommitRef(Symbol("a")),
6215                CommitRef(Symbol("c")),
6216            ),
6217            heads: CommitRef(Symbol("b")),
6218            parents_range: 0..4294967295,
6219            filter: Ancestors {
6220                heads: CommitRef(Symbol("d")),
6221                generation: 0..18446744073709551615,
6222                parents_range: 0..4294967295,
6223            },
6224        }
6225        "#);
6226
6227        // Heads of first-parent ancestors can also be folded.
6228        insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(foo))")?), @r#"
6229        HeadsRange {
6230            roots: None,
6231            heads: CommitRef(Symbol("foo")),
6232            parents_range: 0..1,
6233            filter: All,
6234        }
6235        "#);
6236        insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(foo) & bar..)")?), @r#"
6237        HeadsRange {
6238            roots: CommitRef(Symbol("bar")),
6239            heads: CommitRef(Symbol("foo")),
6240            parents_range: 0..1,
6241            filter: All,
6242        }
6243        "#);
6244        insta::assert_debug_snapshot!(optimize(parse("heads(foo.. & first_ancestors(bar) & ::baz)")?), @r#"
6245        HeadsRange {
6246            roots: CommitRef(Symbol("foo")),
6247            heads: CommitRef(Symbol("bar")),
6248            parents_range: 0..1,
6249            filter: Ancestors {
6250                heads: CommitRef(Symbol("baz")),
6251                generation: 0..18446744073709551615,
6252                parents_range: 0..4294967295,
6253            },
6254        }
6255        "#);
6256
6257        // Ancestors with a limited depth should not be optimized.
6258        insta::assert_debug_snapshot!(optimize(parse("heads(ancestors(foo, 2))")?), @r#"
6259        Heads(
6260            Ancestors {
6261                heads: CommitRef(Symbol("foo")),
6262                generation: 0..2,
6263                parents_range: 0..4294967295,
6264            },
6265        )
6266        "#);
6267        insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(foo, 2))")?), @r#"
6268        Heads(
6269            Ancestors {
6270                heads: CommitRef(Symbol("foo")),
6271                generation: 0..2,
6272                parents_range: 0..1,
6273            },
6274        )
6275        "#);
6276
6277        // Generation folding should not prevent optimizing heads.
6278        insta::assert_debug_snapshot!(optimize(parse("heads(ancestors(foo--))")?), @r#"
6279        HeadsRange {
6280            roots: None,
6281            heads: Ancestors {
6282                heads: CommitRef(Symbol("foo")),
6283                generation: 2..3,
6284                parents_range: 0..4294967295,
6285            },
6286            parents_range: 0..4294967295,
6287            filter: All,
6288        }
6289        "#);
6290        insta::assert_debug_snapshot!(optimize(parse("heads(first_ancestors(first_parent(foo, 2)))")?), @r#"
6291        HeadsRange {
6292            roots: None,
6293            heads: Ancestors {
6294                heads: CommitRef(Symbol("foo")),
6295                generation: 2..3,
6296                parents_range: 0..1,
6297            },
6298            parents_range: 0..1,
6299            filter: All,
6300        }
6301        "#);
6302
6303        // Heads of filters and negations can be folded.
6304        insta::assert_debug_snapshot!(optimize(parse("heads(author_name(A) | author_name(B))")?), @r#"
6305        HeadsRange {
6306            roots: None,
6307            heads: VisibleHeadsOrReferenced,
6308            parents_range: 0..4294967295,
6309            filter: AsFilter(
6310                Union(
6311                    Filter(AuthorName(Pattern(Exact("A")))),
6312                    Filter(AuthorName(Pattern(Exact("B")))),
6313                ),
6314            ),
6315        }
6316        "#);
6317        insta::assert_debug_snapshot!(optimize(parse("heads(~author_name(A))")?), @r#"
6318        HeadsRange {
6319            roots: None,
6320            heads: VisibleHeadsOrReferenced,
6321            parents_range: 0..4294967295,
6322            filter: AsFilter(
6323                NotIn(
6324                    Filter(AuthorName(Pattern(Exact("A")))),
6325                ),
6326            ),
6327        }
6328        "#);
6329        insta::assert_debug_snapshot!(optimize(parse("heads(~foo)")?), @r#"
6330        HeadsRange {
6331            roots: None,
6332            heads: VisibleHeadsOrReferenced,
6333            parents_range: 0..4294967295,
6334            filter: NotIn(CommitRef(Symbol("foo"))),
6335        }
6336        "#);
6337
6338        // Heads of intersections with filters can be folded.
6339        insta::assert_debug_snapshot!(optimize(parse("heads(author_name(A) & ::foo ~ author_name(B))")?), @r#"
6340        HeadsRange {
6341            roots: None,
6342            heads: CommitRef(Symbol("foo")),
6343            parents_range: 0..4294967295,
6344            filter: AsFilter(
6345                Difference(
6346                    Filter(AuthorName(Pattern(Exact("A")))),
6347                    Filter(AuthorName(Pattern(Exact("B")))),
6348                ),
6349            ),
6350        }
6351        "#);
6352
6353        // Heads of intersections with negations can be folded.
6354        insta::assert_debug_snapshot!(optimize(parse("heads(~foo & ~roots(bar) & ::baz)")?), @r#"
6355        HeadsRange {
6356            roots: None,
6357            heads: CommitRef(Symbol("baz")),
6358            parents_range: 0..4294967295,
6359            filter: Difference(
6360                NotIn(CommitRef(Symbol("foo"))),
6361                Roots(CommitRef(Symbol("bar"))),
6362            ),
6363        }
6364        "#);
6365        Ok(())
6366    }
6367
6368    #[test]
6369    fn test_optimize_ancestors_heads_range() -> TestResult {
6370        // Can use heads range to optimize ancestors of filter.
6371        insta::assert_debug_snapshot!(optimize(parse("::description(bar)")?), @r#"
6372        Ancestors {
6373            heads: HeadsRange {
6374                roots: None,
6375                heads: VisibleHeadsOrReferenced,
6376                parents_range: 0..4294967295,
6377                filter: Filter(
6378                    Description(
6379                        Pattern(
6380                            Exact(
6381                                "bar",
6382                            ),
6383                        ),
6384                    ),
6385                ),
6386            },
6387            generation: 0..18446744073709551615,
6388            parents_range: 0..4294967295,
6389        }
6390        "#);
6391        insta::assert_debug_snapshot!(optimize(parse("author_name(foo)..")?), @r#"
6392        Range {
6393            roots: HeadsRange {
6394                roots: None,
6395                heads: VisibleHeadsOrReferenced,
6396                parents_range: 0..4294967295,
6397                filter: Filter(
6398                    AuthorName(
6399                        Pattern(
6400                            Exact(
6401                                "foo",
6402                            ),
6403                        ),
6404                    ),
6405                ),
6406            },
6407            heads: VisibleHeadsOrReferenced,
6408            generation: 0..18446744073709551615,
6409            parents_range: 0..4294967295,
6410        }
6411        "#);
6412
6413        // Can use heads range to optimize ancestors of range.
6414        insta::assert_debug_snapshot!(optimize(parse("::(foo..bar)")?), @r#"
6415        Ancestors {
6416            heads: HeadsRange {
6417                roots: CommitRef(
6418                    Symbol(
6419                        "foo",
6420                    ),
6421                ),
6422                heads: CommitRef(
6423                    Symbol(
6424                        "bar",
6425                    ),
6426                ),
6427                parents_range: 0..4294967295,
6428                filter: All,
6429            },
6430            generation: 0..18446744073709551615,
6431            parents_range: 0..4294967295,
6432        }
6433        "#);
6434
6435        // Can't optimize if not using full generation and parents ranges.
6436        insta::assert_debug_snapshot!(optimize(parse("ancestors(author_name(foo), 5)")?), @r#"
6437        Ancestors {
6438            heads: Filter(
6439                AuthorName(
6440                    Pattern(
6441                        Exact(
6442                            "foo",
6443                        ),
6444                    ),
6445                ),
6446            ),
6447            generation: 0..5,
6448            parents_range: 0..4294967295,
6449        }
6450        "#);
6451        insta::assert_debug_snapshot!(optimize(parse("first_ancestors(author_name(foo))")?), @r#"
6452        Ancestors {
6453            heads: Filter(
6454                AuthorName(
6455                    Pattern(
6456                        Exact(
6457                            "foo",
6458                        ),
6459                    ),
6460                ),
6461            ),
6462            generation: 0..18446744073709551615,
6463            parents_range: 0..1,
6464        }
6465        "#);
6466        Ok(())
6467    }
6468
6469    #[test]
6470    fn test_escape_string_literal() {
6471        // Valid identifiers don't need quoting
6472        assert_eq!(format_symbol("foo"), "foo");
6473        assert_eq!(format_symbol("foo.bar"), "foo.bar");
6474
6475        // Invalid identifiers need quoting
6476        assert_eq!(format_symbol("foo@bar"), r#""foo@bar""#);
6477        assert_eq!(format_symbol("foo bar"), r#""foo bar""#);
6478        assert_eq!(format_symbol(" foo "), r#"" foo ""#);
6479        assert_eq!(format_symbol("(foo)"), r#""(foo)""#);
6480        assert_eq!(format_symbol("all:foo"), r#""all:foo""#);
6481
6482        // Some characters also need escaping
6483        assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
6484        assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
6485        assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
6486        assert_eq!(format_symbol("foo\nbar"), r#""foo\nbar""#);
6487
6488        // Some characters don't technically need escaping, but we escape them for
6489        // clarity
6490        assert_eq!(format_symbol("foo\"bar"), r#""foo\"bar""#);
6491        assert_eq!(format_symbol("foo\\bar"), r#""foo\\bar""#);
6492        assert_eq!(format_symbol("foo\\\"bar"), r#""foo\\\"bar""#);
6493        assert_eq!(format_symbol("foo \x01 bar"), r#""foo \x01 bar""#);
6494    }
6495
6496    #[test]
6497    fn test_escape_remote_symbol() {
6498        assert_eq!(format_remote_symbol("foo", "bar"), "foo@bar");
6499        assert_eq!(
6500            format_remote_symbol(" foo ", "bar:baz"),
6501            r#"" foo "@"bar:baz""#
6502        );
6503    }
6504}