ok/jj
1
0
Fork 0
forked from mirrors/jj

templater: turn output parameter of TemplateProperty into associated type

When implementing FormattablePropertyTemplate, I tried a generic 'property: P'
first, and I couldn't figure out how to constrain the output type.

    impl<C, O, P> Template<C> for FormattablePropertyTemplate<P>
    where
        P: TemplateProperty<C, O>, // 'O' isn't constrained by type
        O: Template<()>,

According to the book, the problem is that we can add multiple implementations
of 'TemplateProperty<C, *>'. Since TemplateProperty is basically a function
to extract data from 'C', I think the output parameter shouldn't be freely
chosen.

https://doc.rust-lang.org/book/ch19-03-advanced-traits.html

With this change, I can express the type constraint as follows:

    impl<C, P> Template<C> for FormattablePropertyTemplate<P>
    where
        P: TemplateProperty<C>,
        P::Output: Template<()>,
This commit is contained in:
Yuya Nishihara 2023-01-23 15:26:27 +09:00
parent efe72f714a
commit f47552b744
2 changed files with 113 additions and 65 deletions

View file

@ -58,53 +58,61 @@ fn parse_string_literal(pair: Pair<Rule>) -> String {
struct StringFirstLine;
impl TemplateProperty<String, String> for StringFirstLine {
fn extract(&self, context: &String) -> String {
impl TemplateProperty<String> for StringFirstLine {
type Output = String;
fn extract(&self, context: &String) -> Self::Output {
context.lines().next().unwrap().to_string()
}
}
struct SignatureName;
impl TemplateProperty<Signature, String> for SignatureName {
fn extract(&self, context: &Signature) -> String {
impl TemplateProperty<Signature> for SignatureName {
type Output = String;
fn extract(&self, context: &Signature) -> Self::Output {
context.name.clone()
}
}
struct SignatureEmail;
impl TemplateProperty<Signature, String> for SignatureEmail {
fn extract(&self, context: &Signature) -> String {
impl TemplateProperty<Signature> for SignatureEmail {
type Output = String;
fn extract(&self, context: &Signature) -> Self::Output {
context.email.clone()
}
}
struct RelativeTimestampString;
impl TemplateProperty<Timestamp, String> for RelativeTimestampString {
fn extract(&self, context: &Timestamp) -> String {
impl TemplateProperty<Timestamp> for RelativeTimestampString {
type Output = String;
fn extract(&self, context: &Timestamp) -> Self::Output {
time_util::format_timestamp_relative_to_now(context)
}
}
enum Property<'a, I> {
String(Box<dyn TemplateProperty<I, String> + 'a>),
Boolean(Box<dyn TemplateProperty<I, bool> + 'a>),
String(Box<dyn TemplateProperty<I, Output = String> + 'a>),
Boolean(Box<dyn TemplateProperty<I, Output = bool> + 'a>),
CommitOrChangeId(
Box<dyn TemplateProperty<I, CommitOrChangeId> + 'a>,
Box<dyn TemplateProperty<I, Output = CommitOrChangeId> + 'a>,
RepoRef<'a>,
),
Signature(Box<dyn TemplateProperty<I, Signature> + 'a>),
Timestamp(Box<dyn TemplateProperty<I, Timestamp> + 'a>),
Signature(Box<dyn TemplateProperty<I, Output = Signature> + 'a>),
Timestamp(Box<dyn TemplateProperty<I, Output = Timestamp> + 'a>),
}
impl<'a, I: 'a> Property<'a, I> {
fn after<C: 'a>(self, first: Box<dyn TemplateProperty<C, I> + 'a>) -> Property<'a, C> {
fn after<C: 'a>(self, first: Box<dyn TemplateProperty<C, Output = I> + 'a>) -> Property<'a, C> {
fn chain<'a, C: 'a, I: 'a, O: 'a>(
first: Box<dyn TemplateProperty<C, I> + 'a>,
second: Box<dyn TemplateProperty<I, O> + 'a>,
) -> Box<dyn TemplateProperty<C, O> + 'a> {
first: Box<dyn TemplateProperty<C, Output = I> + 'a>,
second: Box<dyn TemplateProperty<I, Output = O> + 'a>,
) -> Box<dyn TemplateProperty<C, Output = O> + 'a> {
Box::new(TemplateFunction::new(
first,
Box::new(move |value| second.extract(&value)),
@ -123,7 +131,7 @@ impl<'a, I: 'a> Property<'a, I> {
fn into_template(self) -> Box<dyn Template<I> + 'a> {
fn wrap<'a, I: 'a, O: Template<()> + 'a>(
property: Box<dyn TemplateProperty<I, O> + 'a>,
property: Box<dyn TemplateProperty<I, Output = O> + 'a>,
) -> Box<dyn Template<I> + 'a> {
Box::new(FormattablePropertyTemplate::new(property))
}
@ -146,7 +154,7 @@ fn parse_method_chain<'a, I: 'a>(
PropertyAndLabels(input_property, vec![])
} else {
fn chain<'a, I: 'a, O: 'a>(
property: Box<dyn TemplateProperty<I, O> + 'a>,
property: Box<dyn TemplateProperty<I, Output = O> + 'a>,
parse: impl FnOnce() -> PropertyAndLabels<'a, O>,
) -> (Property<'a, I>, Vec<String>) {
let PropertyAndLabels(next_method, labels) = parse();
@ -282,7 +290,7 @@ fn parse_boolean_commit_property<'a>(
repo: RepoRef<'a>,
workspace_id: &WorkspaceId,
pair: Pair<Rule>,
) -> Box<dyn TemplateProperty<Commit, bool> + 'a> {
) -> Box<dyn TemplateProperty<Commit, Output = bool> + 'a> {
let mut inner = pair.into_inner();
let pair = inner.next().unwrap();
let _method = inner.next().unwrap();

View file

@ -130,8 +130,10 @@ impl<'a, C> Template<C> for ListTemplate<'a, C> {
}
}
pub trait TemplateProperty<C, O> {
fn extract(&self, context: &C) -> O;
pub trait TemplateProperty<C> {
type Output;
fn extract(&self, context: &C) -> Self::Output;
}
/// Adapter to drop template context.
@ -143,7 +145,9 @@ impl<C, O: Template<()>> Template<C> for Literal<O> {
}
}
impl<C, O: Clone> TemplateProperty<C, O> for Literal<O> {
impl<C, O: Clone> TemplateProperty<C> for Literal<O> {
type Output = O;
fn extract(&self, _context: &C) -> O {
self.0.clone()
}
@ -151,11 +155,11 @@ impl<C, O: Clone> TemplateProperty<C, O> for Literal<O> {
/// Adapter to extract context-less template value from property for displaying.
pub struct FormattablePropertyTemplate<'a, C, O> {
property: Box<dyn TemplateProperty<C, O> + 'a>,
property: Box<dyn TemplateProperty<C, Output = O> + 'a>,
}
impl<'a, C, O> FormattablePropertyTemplate<'a, C, O> {
pub fn new(property: Box<dyn TemplateProperty<C, O> + 'a>) -> Self {
pub fn new(property: Box<dyn TemplateProperty<C, Output = O> + 'a>) -> Self {
FormattablePropertyTemplate { property }
}
}
@ -172,8 +176,10 @@ where
pub struct DescriptionProperty;
impl TemplateProperty<Commit, String> for DescriptionProperty {
fn extract(&self, context: &Commit) -> String {
impl TemplateProperty<Commit> for DescriptionProperty {
type Output = String;
fn extract(&self, context: &Commit) -> Self::Output {
match context.description() {
s if s.is_empty() => "(no description set)\n".to_owned(),
s if s.ends_with('\n') => s.to_owned(),
@ -184,16 +190,20 @@ impl TemplateProperty<Commit, String> for DescriptionProperty {
pub struct AuthorProperty;
impl TemplateProperty<Commit, Signature> for AuthorProperty {
fn extract(&self, context: &Commit) -> Signature {
impl TemplateProperty<Commit> for AuthorProperty {
type Output = Signature;
fn extract(&self, context: &Commit) -> Self::Output {
context.author().clone()
}
}
pub struct CommitterProperty;
impl TemplateProperty<Commit, Signature> for CommitterProperty {
fn extract(&self, context: &Commit) -> Signature {
impl TemplateProperty<Commit> for CommitterProperty {
type Output = Signature;
fn extract(&self, context: &Commit) -> Self::Output {
context.committer().clone()
}
}
@ -202,8 +212,10 @@ pub struct WorkingCopiesProperty<'a> {
pub repo: RepoRef<'a>,
}
impl TemplateProperty<Commit, String> for WorkingCopiesProperty<'_> {
fn extract(&self, context: &Commit) -> String {
impl TemplateProperty<Commit> for WorkingCopiesProperty<'_> {
type Output = String;
fn extract(&self, context: &Commit) -> Self::Output {
let wc_commit_ids = self.repo.view().wc_commit_ids();
if wc_commit_ids.len() <= 1 {
return "".to_string();
@ -223,8 +235,10 @@ pub struct IsWorkingCopyProperty<'a> {
pub workspace_id: WorkspaceId,
}
impl TemplateProperty<Commit, bool> for IsWorkingCopyProperty<'_> {
fn extract(&self, context: &Commit) -> bool {
impl TemplateProperty<Commit> for IsWorkingCopyProperty<'_> {
type Output = bool;
fn extract(&self, context: &Commit) -> Self::Output {
Some(context.id()) == self.repo.view().get_wc_commit_id(&self.workspace_id)
}
}
@ -233,8 +247,10 @@ pub struct BranchProperty<'a> {
pub repo: RepoRef<'a>,
}
impl TemplateProperty<Commit, String> for BranchProperty<'_> {
fn extract(&self, context: &Commit) -> String {
impl TemplateProperty<Commit> for BranchProperty<'_> {
type Output = String;
fn extract(&self, context: &Commit) -> Self::Output {
let mut names = vec![];
for (branch_name, branch_target) in self.repo.view().branches() {
let local_target = branch_target.local_target.as_ref();
@ -271,8 +287,10 @@ pub struct TagProperty<'a> {
pub repo: RepoRef<'a>,
}
impl TemplateProperty<Commit, String> for TagProperty<'_> {
fn extract(&self, context: &Commit) -> String {
impl TemplateProperty<Commit> for TagProperty<'_> {
type Output = String;
fn extract(&self, context: &Commit) -> Self::Output {
let mut names = vec![];
for (tag_name, target) in self.repo.view().tags() {
if target.has_add(context.id()) {
@ -291,8 +309,10 @@ pub struct GitRefsProperty<'a> {
pub repo: RepoRef<'a>,
}
impl TemplateProperty<Commit, String> for GitRefsProperty<'_> {
fn extract(&self, context: &Commit) -> String {
impl TemplateProperty<Commit> for GitRefsProperty<'_> {
type Output = String;
fn extract(&self, context: &Commit) -> Self::Output {
// TODO: We should keep a map from commit to ref names so we don't have to walk
// all refs here.
let mut names = vec![];
@ -319,8 +339,10 @@ impl<'a> IsGitHeadProperty<'a> {
}
}
impl TemplateProperty<Commit, bool> for IsGitHeadProperty<'_> {
fn extract(&self, context: &Commit) -> bool {
impl TemplateProperty<Commit> for IsGitHeadProperty<'_> {
type Output = bool;
fn extract(&self, context: &Commit) -> Self::Output {
self.repo.view().git_head().as_ref() == Some(context.id())
}
}
@ -350,22 +372,26 @@ impl DivergentProperty {
}
}
impl TemplateProperty<Commit, bool> for DivergentProperty {
fn extract(&self, context: &Commit) -> bool {
impl TemplateProperty<Commit> for DivergentProperty {
type Output = bool;
fn extract(&self, context: &Commit) -> Self::Output {
self.divergent_changes.contains(context.change_id())
}
}
pub struct ConflictProperty;
impl TemplateProperty<Commit, bool> for ConflictProperty {
fn extract(&self, context: &Commit) -> bool {
impl TemplateProperty<Commit> for ConflictProperty {
type Output = bool;
fn extract(&self, context: &Commit) -> Self::Output {
context.tree().has_conflict()
}
}
pub struct ConditionalTemplate<'a, C> {
pub condition: Box<dyn TemplateProperty<C, bool> + 'a>,
pub condition: Box<dyn TemplateProperty<C, Output = bool> + 'a>,
pub true_template: Box<dyn Template<C> + 'a>,
pub false_template: Option<Box<dyn Template<C> + 'a>>,
}
@ -373,7 +399,7 @@ pub struct ConditionalTemplate<'a, C> {
// TODO: figure out why this lifetime is needed
impl<'a, C> ConditionalTemplate<'a, C> {
pub fn new(
condition: Box<dyn TemplateProperty<C, bool> + 'a>,
condition: Box<dyn TemplateProperty<C, Output = bool> + 'a>,
true_template: Box<dyn Template<C> + 'a>,
false_template: Option<Box<dyn Template<C> + 'a>>,
) -> Self {
@ -399,14 +425,14 @@ impl<'a, C> Template<C> for ConditionalTemplate<'a, C> {
// TODO: If needed, add a ContextualTemplateFunction where the function also
// gets the context
pub struct TemplateFunction<'a, C, I, O> {
pub property: Box<dyn TemplateProperty<C, I> + 'a>,
pub property: Box<dyn TemplateProperty<C, Output = I> + 'a>,
pub function: Box<dyn Fn(I) -> O + 'a>,
}
// TODO: figure out why this lifetime is needed
impl<'a, C, I, O> TemplateFunction<'a, C, I, O> {
pub fn new(
template: Box<dyn TemplateProperty<C, I> + 'a>,
template: Box<dyn TemplateProperty<C, Output = I> + 'a>,
function: Box<dyn Fn(I) -> O + 'a>,
) -> Self {
TemplateFunction {
@ -416,8 +442,10 @@ impl<'a, C, I, O> TemplateFunction<'a, C, I, O> {
}
}
impl<'a, C, I, O> TemplateProperty<C, O> for TemplateFunction<'a, C, I, O> {
fn extract(&self, context: &C) -> O {
impl<'a, C, I, O> TemplateProperty<C> for TemplateFunction<'a, C, I, O> {
type Output = O;
fn extract(&self, context: &C) -> Self::Output {
(self.function)(self.property.extract(context))
}
}
@ -473,8 +501,10 @@ pub struct CommitOrChangeIdShort<'a> {
pub repo: RepoRef<'a>,
}
impl TemplateProperty<CommitOrChangeId, String> for CommitOrChangeIdShort<'_> {
fn extract(&self, context: &CommitOrChangeId) -> String {
impl TemplateProperty<CommitOrChangeId> for CommitOrChangeIdShort<'_> {
type Output = String;
fn extract(&self, context: &CommitOrChangeId) -> Self::Output {
context.short()
}
}
@ -483,32 +513,40 @@ pub struct CommitOrChangeIdShortPrefixAndBrackets<'a> {
pub repo: RepoRef<'a>,
}
impl TemplateProperty<CommitOrChangeId, String> for CommitOrChangeIdShortPrefixAndBrackets<'_> {
fn extract(&self, context: &CommitOrChangeId) -> String {
impl TemplateProperty<CommitOrChangeId> for CommitOrChangeIdShortPrefixAndBrackets<'_> {
type Output = String;
fn extract(&self, context: &CommitOrChangeId) -> Self::Output {
context.short_prefix_and_brackets(self.repo)
}
}
pub struct CommitIdProperty;
impl TemplateProperty<Commit, CommitOrChangeId> for CommitIdProperty {
fn extract(&self, context: &Commit) -> CommitOrChangeId {
impl TemplateProperty<Commit> for CommitIdProperty {
type Output = CommitOrChangeId;
fn extract(&self, context: &Commit) -> Self::Output {
CommitOrChangeId(context.id().to_bytes())
}
}
pub struct ChangeIdProperty;
impl TemplateProperty<Commit, CommitOrChangeId> for ChangeIdProperty {
fn extract(&self, context: &Commit) -> CommitOrChangeId {
impl TemplateProperty<Commit> for ChangeIdProperty {
type Output = CommitOrChangeId;
fn extract(&self, context: &Commit) -> Self::Output {
CommitOrChangeId(context.change_id().to_bytes())
}
}
pub struct SignatureTimestamp;
impl TemplateProperty<Signature, Timestamp> for SignatureTimestamp {
fn extract(&self, context: &Signature) -> Timestamp {
impl TemplateProperty<Signature> for SignatureTimestamp {
type Output = Timestamp;
fn extract(&self, context: &Signature) -> Self::Output {
context.timestamp.clone()
}
}
@ -517,8 +555,10 @@ pub struct EmptyProperty<'a> {
pub repo: RepoRef<'a>,
}
impl TemplateProperty<Commit, bool> for EmptyProperty<'_> {
fn extract(&self, context: &Commit) -> bool {
impl TemplateProperty<Commit> for EmptyProperty<'_> {
type Output = bool;
fn extract(&self, context: &Commit) -> Self::Output {
context.tree().id() == merge_commit_trees(self.repo, &context.parents()).id()
}
}