templater: convert property to trait object by caller

I'm trying to refactor property wrapping functions, and noticed that it's odd
that .wrap_<property>() does boxing internally whereas .wrap_template() doesn't.

Also, it sometimes makes sense to turn property into trait object earlier. For
example, we can deduplicate L::wrap_boolean() in build_binary_operation().
This commit is contained in:
Yuya Nishihara 2025-04-30 11:04:23 +09:00
parent 96b633a091
commit b96924d17f
8 changed files with 294 additions and 283 deletions

View File

@ -131,18 +131,17 @@ impl CommitTemplateLanguageExtension for HexCounter {
.cache_extension::<MostDigitsInId>() .cache_extension::<MostDigitsInId>()
.unwrap() .unwrap()
.count(language.repo()); .count(language.repo());
Ok(L::wrap_boolean(property.map(move |commit| { let out_property =
num_digits_in_id(commit.id()) == most_digits property.map(move |commit| num_digits_in_id(commit.id()) == most_digits);
}))) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
table.commit_methods.insert( table.commit_methods.insert(
"num_digits_in_id", "num_digits_in_id",
|_language, _diagnostics, _build_context, property, call| { |_language, _diagnostics, _build_context, property, call| {
call.expect_no_arguments()?; call.expect_no_arguments()?;
Ok(L::wrap_integer( let out_property = property.map(|commit| num_digits_in_id(commit.id()));
property.map(|commit| num_digits_in_id(commit.id())), Ok(L::wrap_integer(out_property.into_dyn()))
))
}, },
); );
table.commit_methods.insert( table.commit_methods.insert(
@ -161,9 +160,8 @@ impl CommitTemplateLanguageExtension for HexCounter {
} }
})?; })?;
Ok(L::wrap_integer( let out_property = property.map(move |commit| num_char_in_id(commit, char_arg));
property.map(move |commit| num_char_in_id(commit, char_arg)), Ok(L::wrap_integer(out_property.into_dyn()))
))
}, },
); );

View File

@ -55,9 +55,8 @@ impl OperationTemplateLanguageExtension for HexCounter {
"num_digits_in_id", "num_digits_in_id",
|_language, _diagnostics, _build_context, property, call| { |_language, _diagnostics, _build_context, property, call| {
call.expect_no_arguments()?; call.expect_no_arguments()?;
Ok(L::wrap_integer( let out_property = property.map(|operation| num_digits_in_id(operation.id()));
property.map(|operation| num_digits_in_id(operation.id())), Ok(L::wrap_integer(out_property.into_dyn()))
))
}, },
); );
table.operation_methods.insert( table.operation_methods.insert(
@ -76,9 +75,9 @@ impl OperationTemplateLanguageExtension for HexCounter {
} }
})?; })?;
Ok(L::wrap_integer( let out_property =
property.map(move |operation| num_char_in_id(operation, char_arg)), property.map(move |operation| num_char_in_id(operation, char_arg));
)) Ok(L::wrap_integer(out_property.into_dyn()))
}, },
); );

View File

@ -177,7 +177,7 @@ use crate::template_builder;
use crate::template_builder::TemplateLanguage; use crate::template_builder::TemplateLanguage;
use crate::template_parser::TemplateAliasesMap; use crate::template_parser::TemplateAliasesMap;
use crate::template_parser::TemplateDiagnostics; use crate::template_parser::TemplateDiagnostics;
use crate::templater::PropertyPlaceholder; use crate::templater::TemplateProperty;
use crate::templater::TemplateRenderer; use crate::templater::TemplateRenderer;
use crate::text_util; use crate::text_util;
use crate::ui::ColorChoice; use crate::ui::ColorChoice;
@ -386,7 +386,7 @@ impl CommandHelper {
ui: &Ui, ui: &Ui,
language: &L, language: &L,
template_text: &str, template_text: &str,
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property, wrap_self: impl Fn(Box<dyn TemplateProperty<Output = C> + 'a>) -> L::Property,
) -> Result<TemplateRenderer<'a, C>, CommandError> { ) -> Result<TemplateRenderer<'a, C>, CommandError> {
let mut diagnostics = TemplateDiagnostics::new(); let mut diagnostics = TemplateDiagnostics::new();
let aliases = load_template_aliases(ui, self.settings().config())?; let aliases = load_template_aliases(ui, self.settings().config())?;
@ -974,7 +974,7 @@ impl WorkspaceCommandEnvironment {
ui: &Ui, ui: &Ui,
language: &L, language: &L,
template_text: &str, template_text: &str,
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property, wrap_self: impl Fn(Box<dyn TemplateProperty<Output = C> + 'a>) -> L::Property,
) -> Result<TemplateRenderer<'a, C>, CommandError> { ) -> Result<TemplateRenderer<'a, C>, CommandError> {
let mut diagnostics = TemplateDiagnostics::new(); let mut diagnostics = TemplateDiagnostics::new();
let template = template_builder::parse( let template = template_builder::parse(
@ -1704,7 +1704,7 @@ to the current parents may contain changes from multiple commits.
ui: &Ui, ui: &Ui,
language: &L, language: &L,
template_text: &str, template_text: &str,
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property, wrap_self: impl Fn(Box<dyn TemplateProperty<Output = C> + 'a>) -> L::Property,
) -> Result<TemplateRenderer<'a, C>, CommandError> { ) -> Result<TemplateRenderer<'a, C>, CommandError> {
self.env self.env
.parse_template(ui, language, template_text, wrap_self) .parse_template(ui, language, template_text, wrap_self)
@ -1715,7 +1715,7 @@ to the current parents may contain changes from multiple commits.
&self, &self,
language: &L, language: &L,
template_text: &str, template_text: &str,
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property, wrap_self: impl Fn(Box<dyn TemplateProperty<Output = C> + 'a>) -> L::Property,
) -> TemplateRenderer<'a, C> { ) -> TemplateRenderer<'a, C> {
template_builder::parse( template_builder::parse(
language, language,

View File

@ -127,16 +127,16 @@ fn config_template_language(
let mut language = L::new(settings); let mut language = L::new(settings);
language.add_keyword("name", |self_property| { language.add_keyword("name", |self_property| {
let out_property = self_property.map(|annotated| annotated.name.to_string()); let out_property = self_property.map(|annotated| annotated.name.to_string());
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}); });
language.add_keyword("value", |self_property| { language.add_keyword("value", |self_property| {
// .decorated("", "") to trim leading/trailing whitespace // .decorated("", "") to trim leading/trailing whitespace
let out_property = self_property.map(|annotated| annotated.value.decorated("", "")); let out_property = self_property.map(|annotated| annotated.value.decorated("", ""));
Ok(L::wrap_config_value(out_property)) Ok(L::wrap_config_value(out_property.into_dyn()))
}); });
language.add_keyword("source", |self_property| { language.add_keyword("source", |self_property| {
let out_property = self_property.map(|annotated| annotated.source.to_string()); let out_property = self_property.map(|annotated| annotated.source.to_string());
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}); });
language.add_keyword("path", |self_property| { language.add_keyword("path", |self_property| {
let out_property = self_property.map(|annotated| { let out_property = self_property.map(|annotated| {
@ -146,11 +146,11 @@ fn config_template_language(
.as_ref() .as_ref()
.map_or_else(String::new, |path| path.to_string_lossy().into_owned()) .map_or_else(String::new, |path| path.to_string_lossy().into_owned())
}); });
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}); });
language.add_keyword("overridden", |self_property| { language.add_keyword("overridden", |self_property| {
let out_property = self_property.map(|annotated| annotated.is_overridden); let out_property = self_property.map(|annotated| annotated.is_overridden);
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}); });
language language
} }

View File

@ -324,7 +324,7 @@ impl<'repo> TemplateLanguage<'repo> for CommitTemplateLanguage<'repo> {
expect_plain_text_expression(self, diagnostics, build_ctx, key_node)?; expect_plain_text_expression(self, diagnostics, build_ctx, key_node)?;
let out_property = (property, key_property) let out_property = (property, key_property)
.map(|(trailers, key)| trailers.iter().any(|t| t.key == key)); .map(|(trailers, key)| trailers.iter().any(|t| t.key == key));
Ok(Self::wrap_boolean(out_property)) Ok(Self::wrap_boolean(out_property.into_dyn()))
} else { } else {
template_builder::build_formattable_list_method( template_builder::build_formattable_list_method(
self, self,
@ -361,117 +361,117 @@ impl<'repo> CommitTemplateLanguage<'repo> {
} }
pub fn wrap_commit( pub fn wrap_commit(
property: impl TemplateProperty<Output = Commit> + 'repo, property: Box<dyn TemplateProperty<Output = Commit> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::Commit(Box::new(property)) CommitTemplatePropertyKind::Commit(property)
} }
pub fn wrap_commit_opt( pub fn wrap_commit_opt(
property: impl TemplateProperty<Output = Option<Commit>> + 'repo, property: Box<dyn TemplateProperty<Output = Option<Commit>> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::CommitOpt(Box::new(property)) CommitTemplatePropertyKind::CommitOpt(property)
} }
pub fn wrap_commit_list( pub fn wrap_commit_list(
property: impl TemplateProperty<Output = Vec<Commit>> + 'repo, property: Box<dyn TemplateProperty<Output = Vec<Commit>> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::CommitList(Box::new(property)) CommitTemplatePropertyKind::CommitList(property)
} }
pub fn wrap_commit_ref( pub fn wrap_commit_ref(
property: impl TemplateProperty<Output = Rc<CommitRef>> + 'repo, property: Box<dyn TemplateProperty<Output = Rc<CommitRef>> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::CommitRef(Box::new(property)) CommitTemplatePropertyKind::CommitRef(property)
} }
pub fn wrap_commit_ref_opt( pub fn wrap_commit_ref_opt(
property: impl TemplateProperty<Output = Option<Rc<CommitRef>>> + 'repo, property: Box<dyn TemplateProperty<Output = Option<Rc<CommitRef>>> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::CommitRefOpt(Box::new(property)) CommitTemplatePropertyKind::CommitRefOpt(property)
} }
pub fn wrap_commit_ref_list( pub fn wrap_commit_ref_list(
property: impl TemplateProperty<Output = Vec<Rc<CommitRef>>> + 'repo, property: Box<dyn TemplateProperty<Output = Vec<Rc<CommitRef>>> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::CommitRefList(Box::new(property)) CommitTemplatePropertyKind::CommitRefList(property)
} }
pub fn wrap_repo_path( pub fn wrap_repo_path(
property: impl TemplateProperty<Output = RepoPathBuf> + 'repo, property: Box<dyn TemplateProperty<Output = RepoPathBuf> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::RepoPath(Box::new(property)) CommitTemplatePropertyKind::RepoPath(property)
} }
pub fn wrap_repo_path_opt( pub fn wrap_repo_path_opt(
property: impl TemplateProperty<Output = Option<RepoPathBuf>> + 'repo, property: Box<dyn TemplateProperty<Output = Option<RepoPathBuf>> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::RepoPathOpt(Box::new(property)) CommitTemplatePropertyKind::RepoPathOpt(property)
} }
pub fn wrap_commit_or_change_id( pub fn wrap_commit_or_change_id(
property: impl TemplateProperty<Output = CommitOrChangeId> + 'repo, property: Box<dyn TemplateProperty<Output = CommitOrChangeId> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::CommitOrChangeId(Box::new(property)) CommitTemplatePropertyKind::CommitOrChangeId(property)
} }
pub fn wrap_shortest_id_prefix( pub fn wrap_shortest_id_prefix(
property: impl TemplateProperty<Output = ShortestIdPrefix> + 'repo, property: Box<dyn TemplateProperty<Output = ShortestIdPrefix> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::ShortestIdPrefix(Box::new(property)) CommitTemplatePropertyKind::ShortestIdPrefix(property)
} }
pub fn wrap_tree_diff( pub fn wrap_tree_diff(
property: impl TemplateProperty<Output = TreeDiff> + 'repo, property: Box<dyn TemplateProperty<Output = TreeDiff> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::TreeDiff(Box::new(property)) CommitTemplatePropertyKind::TreeDiff(property)
} }
pub fn wrap_tree_diff_entry( pub fn wrap_tree_diff_entry(
property: impl TemplateProperty<Output = TreeDiffEntry> + 'repo, property: Box<dyn TemplateProperty<Output = TreeDiffEntry> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::TreeDiffEntry(Box::new(property)) CommitTemplatePropertyKind::TreeDiffEntry(property)
} }
pub fn wrap_tree_diff_entry_list( pub fn wrap_tree_diff_entry_list(
property: impl TemplateProperty<Output = Vec<TreeDiffEntry>> + 'repo, property: Box<dyn TemplateProperty<Output = Vec<TreeDiffEntry>> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::TreeDiffEntryList(Box::new(property)) CommitTemplatePropertyKind::TreeDiffEntryList(property)
} }
pub fn wrap_tree_entry( pub fn wrap_tree_entry(
property: impl TemplateProperty<Output = TreeEntry> + 'repo, property: Box<dyn TemplateProperty<Output = TreeEntry> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::TreeEntry(Box::new(property)) CommitTemplatePropertyKind::TreeEntry(property)
} }
pub fn wrap_diff_stats( pub fn wrap_diff_stats(
property: impl TemplateProperty<Output = DiffStatsFormatted<'repo>> + 'repo, property: Box<dyn TemplateProperty<Output = DiffStatsFormatted<'repo>> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::DiffStats(Box::new(property)) CommitTemplatePropertyKind::DiffStats(property)
} }
fn wrap_cryptographic_signature_opt( fn wrap_cryptographic_signature_opt(
property: impl TemplateProperty<Output = Option<CryptographicSignature>> + 'repo, property: Box<dyn TemplateProperty<Output = Option<CryptographicSignature>> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::CryptographicSignatureOpt(Box::new(property)) CommitTemplatePropertyKind::CryptographicSignatureOpt(property)
} }
pub fn wrap_annotation_line( pub fn wrap_annotation_line(
property: impl TemplateProperty<Output = AnnotationLine> + 'repo, property: Box<dyn TemplateProperty<Output = AnnotationLine> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::AnnotationLine(Box::new(property)) CommitTemplatePropertyKind::AnnotationLine(property)
} }
pub fn wrap_trailer( pub fn wrap_trailer(
property: impl TemplateProperty<Output = Trailer> + 'repo, property: Box<dyn TemplateProperty<Output = Trailer> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::Trailer(Box::new(property)) CommitTemplatePropertyKind::Trailer(property)
} }
pub fn wrap_trailer_list( pub fn wrap_trailer_list(
property: impl TemplateProperty<Output = Vec<Trailer>> + 'repo, property: Box<dyn TemplateProperty<Output = Vec<Trailer>> + 'repo>,
) -> CommitTemplatePropertyKind<'repo> { ) -> CommitTemplatePropertyKind<'repo> {
CommitTemplatePropertyKind::TrailerList(Box::new(property)) CommitTemplatePropertyKind::TrailerList(property)
} }
} }
@ -831,7 +831,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = let out_property =
self_property.map(|commit| text_util::complete_newline(commit.description())); self_property.map(|commit| text_util::complete_newline(commit.description()));
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -840,7 +840,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property let out_property = self_property
.map(|commit| trailer::parse_description_trailers(commit.description())); .map(|commit| trailer::parse_description_trailers(commit.description()));
Ok(L::wrap_trailer_list(out_property)) Ok(L::wrap_trailer_list(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -849,7 +849,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = let out_property =
self_property.map(|commit| CommitOrChangeId::Change(commit.change_id().to_owned())); self_property.map(|commit| CommitOrChangeId::Change(commit.change_id().to_owned()));
Ok(L::wrap_commit_or_change_id(out_property)) Ok(L::wrap_commit_or_change_id(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -858,7 +858,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = let out_property =
self_property.map(|commit| CommitOrChangeId::Commit(commit.id().to_owned())); self_property.map(|commit| CommitOrChangeId::Commit(commit.id().to_owned()));
Ok(L::wrap_commit_or_change_id(out_property)) Ok(L::wrap_commit_or_change_id(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -867,7 +867,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = let out_property =
self_property.and_then(|commit| Ok(commit.parents().try_collect()?)); self_property.and_then(|commit| Ok(commit.parents().try_collect()?));
Ok(L::wrap_commit_list(out_property)) Ok(L::wrap_commit_list(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -875,7 +875,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|commit| commit.author().clone()); let out_property = self_property.map(|commit| commit.author().clone());
Ok(L::wrap_signature(out_property)) Ok(L::wrap_signature(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -883,7 +883,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|commit| commit.committer().clone()); let out_property = self_property.map(|commit| commit.committer().clone());
Ok(L::wrap_signature(out_property)) Ok(L::wrap_signature(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -892,7 +892,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
function.expect_no_arguments()?; function.expect_no_arguments()?;
let user_email = language.revset_parse_context.user_email.to_owned(); let user_email = language.revset_parse_context.user_email.to_owned();
let out_property = self_property.map(move |commit| commit.author().email == user_email); let out_property = self_property.map(move |commit| commit.author().email == user_email);
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -900,7 +900,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(CryptographicSignature::new); let out_property = self_property.map(CryptographicSignature::new);
Ok(L::wrap_cryptographic_signature_opt(out_property)) Ok(L::wrap_cryptographic_signature_opt(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -909,7 +909,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
function.expect_no_arguments()?; function.expect_no_arguments()?;
let repo = language.repo; let repo = language.repo;
let out_property = self_property.map(|commit| extract_working_copies(repo, &commit)); let out_property = self_property.map(|commit| extract_working_copies(repo, &commit));
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -920,7 +920,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
let name = language.workspace_name.clone(); let name = language.workspace_name.clone();
let out_property = self_property let out_property = self_property
.map(move |commit| Some(commit.id()) == repo.view().get_wc_commit_id(&name)); .map(move |commit| Some(commit.id()) == repo.view().get_wc_commit_id(&name));
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -939,7 +939,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
.cloned() .cloned()
.collect() .collect()
}); });
Ok(L::wrap_commit_ref_list(out_property)) Ok(L::wrap_commit_ref_list(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -958,7 +958,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
.cloned() .cloned()
.collect() .collect()
}); });
Ok(L::wrap_commit_ref_list(out_property)) Ok(L::wrap_commit_ref_list(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -977,7 +977,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
.cloned() .cloned()
.collect() .collect()
}); });
Ok(L::wrap_commit_ref_list(out_property)) Ok(L::wrap_commit_ref_list(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -986,7 +986,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
function.expect_no_arguments()?; function.expect_no_arguments()?;
let index = language.keyword_cache.tags_index(language.repo).clone(); let index = language.keyword_cache.tags_index(language.repo).clone();
let out_property = self_property.map(move |commit| index.get(commit.id()).to_vec()); let out_property = self_property.map(move |commit| index.get(commit.id()).to_vec());
Ok(L::wrap_commit_ref_list(out_property)) Ok(L::wrap_commit_ref_list(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -995,7 +995,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
function.expect_no_arguments()?; function.expect_no_arguments()?;
let index = language.keyword_cache.git_refs_index(language.repo).clone(); let index = language.keyword_cache.git_refs_index(language.repo).clone();
let out_property = self_property.map(move |commit| index.get(commit.id()).to_vec()); let out_property = self_property.map(move |commit| index.get(commit.id()).to_vec());
Ok(L::wrap_commit_ref_list(out_property)) Ok(L::wrap_commit_ref_list(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1007,7 +1007,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
let target = repo.view().git_head(); let target = repo.view().git_head();
target.added_ids().contains(commit.id()) target.added_ids().contains(commit.id())
}); });
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1020,7 +1020,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
let maybe_entries = repo.resolve_change_id(commit.change_id()); let maybe_entries = repo.resolve_change_id(commit.change_id());
maybe_entries.map_or(0, |entries| entries.len()) > 1 maybe_entries.map_or(0, |entries| entries.len()) > 1
}); });
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1029,7 +1029,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
function.expect_no_arguments()?; function.expect_no_arguments()?;
let repo = language.repo; let repo = language.repo;
let out_property = self_property.map(|commit| commit.is_hidden(repo)); let out_property = self_property.map(|commit| commit.is_hidden(repo));
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1041,7 +1041,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
.is_immutable_fn(language, function.name_span)? .is_immutable_fn(language, function.name_span)?
.clone(); .clone();
let out_property = self_property.and_then(move |commit| Ok(is_immutable(commit.id())?)); let out_property = self_property.and_then(move |commit| Ok(is_immutable(commit.id())?));
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1055,7 +1055,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
})?; })?;
let out_property = self_property.and_then(move |commit| Ok(is_contained(commit.id())?)); let out_property = self_property.and_then(move |commit| Ok(is_contained(commit.id())?));
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1063,7 +1063,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.and_then(|commit| Ok(commit.has_conflict()?)); let out_property = self_property.and_then(|commit| Ok(commit.has_conflict()?));
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1072,7 +1072,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
function.expect_no_arguments()?; function.expect_no_arguments()?;
let repo = language.repo; let repo = language.repo;
let out_property = self_property.and_then(|commit| Ok(commit.is_empty(repo)?)); let out_property = self_property.and_then(|commit| Ok(commit.is_empty(repo)?));
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1090,7 +1090,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
let matcher: Rc<dyn Matcher> = files.to_matcher().into(); let matcher: Rc<dyn Matcher> = files.to_matcher().into();
let out_property = self_property let out_property = self_property
.and_then(move |commit| Ok(TreeDiff::from_commit(repo, &commit, matcher.clone())?)); .and_then(move |commit| Ok(TreeDiff::from_commit(repo, &commit, matcher.clone())?));
Ok(L::wrap_tree_diff(out_property)) Ok(L::wrap_tree_diff(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1100,7 +1100,7 @@ fn builtin_commit_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Comm
let repo = language.repo; let repo = language.repo;
let out_property = let out_property =
self_property.map(|commit| commit.id() == repo.store().root_commit_id()); self_property.map(|commit| commit.id() == repo.store().root_commit_id());
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map map
@ -1399,7 +1399,7 @@ fn builtin_commit_ref_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|commit_ref| commit_ref.name.clone()); let out_property = self_property.map(|commit_ref| commit_ref.name.clone());
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1408,7 +1408,7 @@ fn builtin_commit_ref_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = let out_property =
self_property.map(|commit_ref| commit_ref.remote.clone().unwrap_or_default()); self_property.map(|commit_ref| commit_ref.remote.clone().unwrap_or_default());
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1416,7 +1416,7 @@ fn builtin_commit_ref_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|commit_ref| commit_ref.is_present()); let out_property = self_property.map(|commit_ref| commit_ref.is_present());
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1424,7 +1424,7 @@ fn builtin_commit_ref_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|commit_ref| commit_ref.has_conflict()); let out_property = self_property.map(|commit_ref| commit_ref.has_conflict());
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1436,7 +1436,7 @@ fn builtin_commit_ref_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
let maybe_id = commit_ref.target.as_normal(); let maybe_id = commit_ref.target.as_normal();
Ok(maybe_id.map(|id| repo.store().get_commit(id)).transpose()?) Ok(maybe_id.map(|id| repo.store().get_commit(id)).transpose()?)
}); });
Ok(L::wrap_commit_opt(out_property)) Ok(L::wrap_commit_opt(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1448,7 +1448,7 @@ fn builtin_commit_ref_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
let ids = commit_ref.target.removed_ids(); let ids = commit_ref.target.removed_ids();
Ok(ids.map(|id| repo.store().get_commit(id)).try_collect()?) Ok(ids.map(|id| repo.store().get_commit(id)).try_collect()?)
}); });
Ok(L::wrap_commit_list(out_property)) Ok(L::wrap_commit_list(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1460,7 +1460,7 @@ fn builtin_commit_ref_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
let ids = commit_ref.target.added_ids(); let ids = commit_ref.target.added_ids();
Ok(ids.map(|id| repo.store().get_commit(id)).try_collect()?) Ok(ids.map(|id| repo.store().get_commit(id)).try_collect()?)
}); });
Ok(L::wrap_commit_list(out_property)) Ok(L::wrap_commit_list(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1468,7 +1468,7 @@ fn builtin_commit_ref_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|commit_ref| commit_ref.is_tracked()); let out_property = self_property.map(|commit_ref| commit_ref.is_tracked());
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1476,7 +1476,7 @@ fn builtin_commit_ref_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|commit_ref| commit_ref.is_tracking_present()); let out_property = self_property.map(|commit_ref| commit_ref.is_tracking_present());
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1486,7 +1486,7 @@ fn builtin_commit_ref_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
let repo = language.repo; let repo = language.repo;
let out_property = let out_property =
self_property.and_then(|commit_ref| commit_ref.tracking_ahead_count(repo)); self_property.and_then(|commit_ref| commit_ref.tracking_ahead_count(repo));
Ok(L::wrap_size_hint(out_property)) Ok(L::wrap_size_hint(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1496,7 +1496,7 @@ fn builtin_commit_ref_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
let repo = language.repo; let repo = language.repo;
let out_property = let out_property =
self_property.and_then(|commit_ref| commit_ref.tracking_behind_count(repo)); self_property.and_then(|commit_ref| commit_ref.tracking_behind_count(repo));
Ok(L::wrap_size_hint(out_property)) Ok(L::wrap_size_hint(out_property.into_dyn()))
}, },
); );
map map
@ -1571,7 +1571,7 @@ fn builtin_repo_path_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, R
function.expect_no_arguments()?; function.expect_no_arguments()?;
let path_converter = language.path_converter; let path_converter = language.path_converter;
let out_property = self_property.map(|path| path_converter.format_file_path(&path)); let out_property = self_property.map(|path| path_converter.format_file_path(&path));
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1579,7 +1579,7 @@ fn builtin_repo_path_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, R
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|path| Some(path.parent()?.to_owned())); let out_property = self_property.map(|path| Some(path.parent()?.to_owned()));
Ok(L::wrap_repo_path_opt(out_property)) Ok(L::wrap_repo_path_opt(out_property.into_dyn()))
}, },
); );
map map
@ -1640,7 +1640,7 @@ fn builtin_commit_or_change_id_methods<'repo>(
"normal_hex", "normal_hex",
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
Ok(L::wrap_string(self_property.map(|id| { let out_property = self_property.map(|id| {
// Note: this is _not_ the same as id.hex() for ChangeId, which // Note: this is _not_ the same as id.hex() for ChangeId, which
// returns the "reverse" hex (z-k), instead of the "forward" / // returns the "reverse" hex (z-k), instead of the "forward" /
// normal hex (0-9a-f) we want here. // normal hex (0-9a-f) we want here.
@ -1648,7 +1648,8 @@ fn builtin_commit_or_change_id_methods<'repo>(
CommitOrChangeId::Commit(id) => id.hex(), CommitOrChangeId::Commit(id) => id.hex(),
CommitOrChangeId::Change(id) => id.hex(), CommitOrChangeId::Change(id) => id.hex(),
} }
}))) });
Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1667,7 +1668,7 @@ fn builtin_commit_or_change_id_methods<'repo>(
.transpose()?; .transpose()?;
let out_property = let out_property =
(self_property, len_property).map(|(id, len)| id.short(len.unwrap_or(12))); (self_property, len_property).map(|(id, len)| id.short(len.unwrap_or(12)));
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1702,7 +1703,7 @@ fn builtin_commit_or_change_id_methods<'repo>(
}; };
let out_property = (self_property, len_property) let out_property = (self_property, len_property)
.map(move |(id, len)| id.shortest(repo, &index, len.unwrap_or(0))); .map(move |(id, len)| id.shortest(repo, &index, len.unwrap_or(0)));
Ok(L::wrap_shortest_id_prefix(out_property)) Ok(L::wrap_shortest_id_prefix(out_property.into_dyn()))
}, },
); );
map map
@ -1747,7 +1748,7 @@ fn builtin_shortest_id_prefix_methods<'repo>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|id| id.prefix); let out_property = self_property.map(|id| id.prefix);
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1755,7 +1756,7 @@ fn builtin_shortest_id_prefix_methods<'repo>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|id| id.rest); let out_property = self_property.map(|id| id.rest);
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1763,7 +1764,7 @@ fn builtin_shortest_id_prefix_methods<'repo>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|id| id.to_upper()); let out_property = self_property.map(|id| id.to_upper());
Ok(L::wrap_shortest_id_prefix(out_property)) Ok(L::wrap_shortest_id_prefix(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1771,7 +1772,7 @@ fn builtin_shortest_id_prefix_methods<'repo>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|id| id.to_lower()); let out_property = self_property.map(|id| id.to_lower());
Ok(L::wrap_shortest_id_prefix(out_property)) Ok(L::wrap_shortest_id_prefix(out_property.into_dyn()))
}, },
); );
map map
@ -1858,7 +1859,7 @@ fn builtin_tree_diff_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, T
// TODO: cache and reuse diff entries within the current evaluation? // TODO: cache and reuse diff entries within the current evaluation?
let out_property = let out_property =
self_property.and_then(|diff| Ok(diff.collect_entries().block_on()?)); self_property.and_then(|diff| Ok(diff.collect_entries().block_on()?));
Ok(L::wrap_tree_diff_entry_list(out_property)) Ok(L::wrap_tree_diff_entry_list(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1974,7 +1975,7 @@ fn builtin_tree_diff_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, T
width: width.unwrap_or(80), width: width.unwrap_or(80),
}) })
}); });
Ok(L::wrap_diff_stats(out_property)) Ok(L::wrap_diff_stats(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -2049,7 +2050,7 @@ fn builtin_tree_diff_entry_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'r
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|entry| entry.path.target); let out_property = self_property.map(|entry| entry.path.target);
Ok(L::wrap_repo_path(out_property)) Ok(L::wrap_repo_path(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -2057,7 +2058,7 @@ fn builtin_tree_diff_entry_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'r
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|entry| entry.status_label().to_owned()); let out_property = self_property.map(|entry| entry.status_label().to_owned());
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
// TODO: add status_code() or status_char()? // TODO: add status_code() or status_char()?
@ -2066,7 +2067,7 @@ fn builtin_tree_diff_entry_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'r
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(TreeDiffEntry::into_source_entry); let out_property = self_property.map(TreeDiffEntry::into_source_entry);
Ok(L::wrap_tree_entry(out_property)) Ok(L::wrap_tree_entry(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -2074,7 +2075,7 @@ fn builtin_tree_diff_entry_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'r
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(TreeDiffEntry::into_target_entry); let out_property = self_property.map(TreeDiffEntry::into_target_entry);
Ok(L::wrap_tree_entry(out_property)) Ok(L::wrap_tree_entry(out_property.into_dyn()))
}, },
); );
map map
@ -2097,7 +2098,7 @@ fn builtin_tree_entry_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|entry| entry.path); let out_property = self_property.map(|entry| entry.path);
Ok(L::wrap_repo_path(out_property)) Ok(L::wrap_repo_path(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -2105,7 +2106,7 @@ fn builtin_tree_entry_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|entry| !entry.value.is_resolved()); let out_property = self_property.map(|entry| !entry.value.is_resolved());
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -2114,7 +2115,7 @@ fn builtin_tree_entry_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = let out_property =
self_property.map(|entry| describe_file_type(&entry.value).to_owned()); self_property.map(|entry| describe_file_type(&entry.value).to_owned());
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -2123,7 +2124,7 @@ fn builtin_tree_entry_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = let out_property =
self_property.map(|entry| is_executable_file(&entry.value).unwrap_or_default()); self_property.map(|entry| is_executable_file(&entry.value).unwrap_or_default());
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map map
@ -2176,7 +2177,7 @@ fn builtin_diff_stats_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = let out_property =
self_property.and_then(|stats| Ok(stats.count_total_added().try_into()?)); self_property.and_then(|stats| Ok(stats.count_total_added().try_into()?));
Ok(L::wrap_integer(out_property)) Ok(L::wrap_integer(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -2185,7 +2186,7 @@ fn builtin_diff_stats_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo,
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = let out_property =
self_property.and_then(|stats| Ok(stats.count_total_removed().try_into()?)); self_property.and_then(|stats| Ok(stats.count_total_removed().try_into()?));
Ok(L::wrap_integer(out_property)) Ok(L::wrap_integer(out_property.into_dyn()))
}, },
); );
map map
@ -2240,7 +2241,7 @@ fn builtin_cryptographic_signature_methods<'repo>(
Err(SignError::InvalidSignatureFormat) => Ok("invalid".to_string()), Err(SignError::InvalidSignatureFormat) => Ok("invalid".to_string()),
Err(err) => Err(err.into()), Err(err) => Err(err.into()),
}); });
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -2248,7 +2249,7 @@ fn builtin_cryptographic_signature_methods<'repo>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.and_then(|sig| Ok(sig.key()?)); let out_property = self_property.and_then(|sig| Ok(sig.key()?));
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -2256,7 +2257,7 @@ fn builtin_cryptographic_signature_methods<'repo>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.and_then(|sig| Ok(sig.display()?)); let out_property = self_property.and_then(|sig| Ok(sig.display()?));
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map map
@ -2279,7 +2280,7 @@ fn builtin_annotation_line_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'r
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|line| line.commit); let out_property = self_property.map(|line| line.commit);
Ok(L::wrap_commit(out_property)) Ok(L::wrap_commit(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -2296,7 +2297,7 @@ fn builtin_annotation_line_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'r
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.and_then(|line| Ok(line.line_number.try_into()?)); let out_property = self_property.and_then(|line| Ok(line.line_number.try_into()?));
Ok(L::wrap_integer(out_property)) Ok(L::wrap_integer(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -2304,7 +2305,7 @@ fn builtin_annotation_line_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'r
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|line| line.first_line_in_hunk); let out_property = self_property.map(|line| line.first_line_in_hunk);
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map map
@ -2330,7 +2331,7 @@ fn builtin_trailer_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Tra
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|trailer| trailer.key); let out_property = self_property.map(|trailer| trailer.key);
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -2338,7 +2339,7 @@ fn builtin_trailer_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Tra
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|trailer| trailer.value); let out_property = self_property.map(|trailer| trailer.value);
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map map

View File

@ -132,9 +132,9 @@ impl<'a, C> TemplateLanguage<'a> for GenericTemplateLanguage<'a, C> {
impl<'a, C> GenericTemplateLanguage<'a, C> { impl<'a, C> GenericTemplateLanguage<'a, C> {
pub fn wrap_self( pub fn wrap_self(
property: impl TemplateProperty<Output = C> + 'a, property: Box<dyn TemplateProperty<Output = C> + 'a>,
) -> GenericTemplatePropertyKind<'a, C> { ) -> GenericTemplatePropertyKind<'a, C> {
GenericTemplatePropertyKind::Self_(Box::new(property)) GenericTemplatePropertyKind::Self_(property)
} }
} }

View File

@ -137,15 +137,15 @@ impl OperationTemplateLanguage {
} }
pub fn wrap_operation( pub fn wrap_operation(
property: impl TemplateProperty<Output = Operation> + 'static, property: Box<dyn TemplateProperty<Output = Operation> + 'static>,
) -> OperationTemplatePropertyKind { ) -> OperationTemplatePropertyKind {
OperationTemplatePropertyKind::Operation(Box::new(property)) OperationTemplatePropertyKind::Operation(property)
} }
pub fn wrap_operation_id( pub fn wrap_operation_id(
property: impl TemplateProperty<Output = OperationId> + 'static, property: Box<dyn TemplateProperty<Output = OperationId> + 'static>,
) -> OperationTemplatePropertyKind { ) -> OperationTemplatePropertyKind {
OperationTemplatePropertyKind::OperationId(Box::new(property)) OperationTemplatePropertyKind::OperationId(property)
} }
} }
@ -275,7 +275,7 @@ fn builtin_operation_methods() -> OperationTemplateBuildMethodFnMap<Operation> {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let current_op_id = language.current_op_id.clone(); let current_op_id = language.current_op_id.clone();
let out_property = self_property.map(move |op| Some(op.id()) == current_op_id.as_ref()); let out_property = self_property.map(move |op| Some(op.id()) == current_op_id.as_ref());
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -283,7 +283,7 @@ fn builtin_operation_methods() -> OperationTemplateBuildMethodFnMap<Operation> {
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|op| op.metadata().description.clone()); let out_property = self_property.map(|op| op.metadata().description.clone());
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -291,7 +291,7 @@ fn builtin_operation_methods() -> OperationTemplateBuildMethodFnMap<Operation> {
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|op| op.id().clone()); let out_property = self_property.map(|op| op.id().clone());
Ok(L::wrap_operation_id(out_property)) Ok(L::wrap_operation_id(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -306,7 +306,7 @@ fn builtin_operation_methods() -> OperationTemplateBuildMethodFnMap<Operation> {
.map(|(key, value)| format!("{key}: {value}")) .map(|(key, value)| format!("{key}: {value}"))
.join("\n") .join("\n")
}); });
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -314,7 +314,7 @@ fn builtin_operation_methods() -> OperationTemplateBuildMethodFnMap<Operation> {
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|op| op.metadata().is_snapshot); let out_property = self_property.map(|op| op.metadata().is_snapshot);
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -325,7 +325,7 @@ fn builtin_operation_methods() -> OperationTemplateBuildMethodFnMap<Operation> {
start: op.metadata().start_time, start: op.metadata().start_time,
end: op.metadata().end_time, end: op.metadata().end_time,
}); });
Ok(L::wrap_timestamp_range(out_property)) Ok(L::wrap_timestamp_range(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -336,7 +336,7 @@ fn builtin_operation_methods() -> OperationTemplateBuildMethodFnMap<Operation> {
// TODO: introduce dedicated type and provide accessors? // TODO: introduce dedicated type and provide accessors?
format!("{}@{}", op.metadata().username, op.metadata().hostname) format!("{}@{}", op.metadata().username, op.metadata().hostname)
}); });
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -345,7 +345,7 @@ fn builtin_operation_methods() -> OperationTemplateBuildMethodFnMap<Operation> {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let root_op_id = language.repo_loader.op_store().root_operation_id().clone(); let root_op_id = language.repo_loader.op_store().root_operation_id().clone();
let out_property = self_property.map(move |op| op.id() == &root_op_id); let out_property = self_property.map(move |op| op.id() == &root_op_id);
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map map
@ -381,7 +381,7 @@ fn builtin_operation_id_methods() -> OperationTemplateBuildMethodFnMap<Operation
hex.truncate(len.unwrap_or(12)); hex.truncate(len.unwrap_or(12));
hex hex
}); });
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map map

View File

@ -69,24 +69,30 @@ use crate::time_util;
pub trait TemplateLanguage<'a> { pub trait TemplateLanguage<'a> {
type Property: IntoTemplateProperty<'a>; type Property: IntoTemplateProperty<'a>;
fn wrap_string(property: impl TemplateProperty<Output = String> + 'a) -> Self::Property; fn wrap_string(property: Box<dyn TemplateProperty<Output = String> + 'a>) -> Self::Property;
fn wrap_string_list( fn wrap_string_list(
property: impl TemplateProperty<Output = Vec<String>> + 'a, property: Box<dyn TemplateProperty<Output = Vec<String>> + 'a>,
) -> Self::Property; ) -> Self::Property;
fn wrap_boolean(property: impl TemplateProperty<Output = bool> + 'a) -> Self::Property; fn wrap_boolean(property: Box<dyn TemplateProperty<Output = bool> + 'a>) -> Self::Property;
fn wrap_integer(property: impl TemplateProperty<Output = i64> + 'a) -> Self::Property; fn wrap_integer(property: Box<dyn TemplateProperty<Output = i64> + 'a>) -> Self::Property;
fn wrap_integer_opt( fn wrap_integer_opt(
property: impl TemplateProperty<Output = Option<i64>> + 'a, property: Box<dyn TemplateProperty<Output = Option<i64>> + 'a>,
) -> Self::Property; ) -> Self::Property;
fn wrap_config_value( fn wrap_config_value(
property: impl TemplateProperty<Output = ConfigValue> + 'a, property: Box<dyn TemplateProperty<Output = ConfigValue> + 'a>,
) -> Self::Property;
fn wrap_signature(
property: Box<dyn TemplateProperty<Output = Signature> + 'a>,
) -> Self::Property;
fn wrap_email(property: Box<dyn TemplateProperty<Output = Email> + 'a>) -> Self::Property;
fn wrap_size_hint(
property: Box<dyn TemplateProperty<Output = SizeHint> + 'a>,
) -> Self::Property;
fn wrap_timestamp(
property: Box<dyn TemplateProperty<Output = Timestamp> + 'a>,
) -> Self::Property; ) -> Self::Property;
fn wrap_signature(property: impl TemplateProperty<Output = Signature> + 'a) -> Self::Property;
fn wrap_email(property: impl TemplateProperty<Output = Email> + 'a) -> Self::Property;
fn wrap_size_hint(property: impl TemplateProperty<Output = SizeHint> + 'a) -> Self::Property;
fn wrap_timestamp(property: impl TemplateProperty<Output = Timestamp> + 'a) -> Self::Property;
fn wrap_timestamp_range( fn wrap_timestamp_range(
property: impl TemplateProperty<Output = TimestampRange> + 'a, property: Box<dyn TemplateProperty<Output = TimestampRange> + 'a>,
) -> Self::Property; ) -> Self::Property;
fn wrap_template(template: Box<dyn Template + 'a>) -> Self::Property; fn wrap_template(template: Box<dyn Template + 'a>) -> Self::Property;
@ -157,10 +163,10 @@ macro_rules! impl_wrap_property_fns {
($a:lifetime, $kind:path, $outer:path, { $( $func:ident($ty:ty) => $var:ident, )+ }) => { ($a:lifetime, $kind:path, $outer:path, { $( $func:ident($ty:ty) => $var:ident, )+ }) => {
$( $(
fn $func( fn $func(
property: impl $crate::templater::TemplateProperty<Output = $ty> + $a, property: Box<dyn $crate::templater::TemplateProperty<Output = $ty> + $a>,
) -> Self::Property { ) -> Self::Property {
use $kind as Kind; // https://github.com/rust-lang/rust/issues/48067 use $kind as Kind; // https://github.com/rust-lang/rust/issues/48067
$outer(Kind::$var(Box::new(property))) $outer(Kind::$var(property))
} }
)+ )+
}; };
@ -692,14 +698,15 @@ fn build_unary_operation<'a, L: TemplateLanguage<'a> + ?Sized>(
match op { match op {
UnaryOp::LogicalNot => { UnaryOp::LogicalNot => {
let arg = expect_boolean_expression(language, diagnostics, build_ctx, arg_node)?; let arg = expect_boolean_expression(language, diagnostics, build_ctx, arg_node)?;
Ok(L::wrap_boolean(arg.map(|v| !v))) Ok(L::wrap_boolean(arg.map(|v| !v).into_dyn()))
} }
UnaryOp::Negate => { UnaryOp::Negate => {
let arg = expect_integer_expression(language, diagnostics, build_ctx, arg_node)?; let arg = expect_integer_expression(language, diagnostics, build_ctx, arg_node)?;
Ok(L::wrap_integer(arg.and_then(|v| { let out = arg.and_then(|v| {
v.checked_neg() v.checked_neg()
.ok_or_else(|| TemplatePropertyError("Attempt to negate with overflow".into())) .ok_or_else(|| TemplatePropertyError("Attempt to negate with overflow".into()))
}))) });
Ok(L::wrap_integer(out.into_dyn()))
} }
} }
} }
@ -718,45 +725,47 @@ fn build_binary_operation<'a, L: TemplateLanguage<'a> + ?Sized>(
let lhs = expect_boolean_expression(language, diagnostics, build_ctx, lhs_node)?; let lhs = expect_boolean_expression(language, diagnostics, build_ctx, lhs_node)?;
let rhs = expect_boolean_expression(language, diagnostics, build_ctx, rhs_node)?; let rhs = expect_boolean_expression(language, diagnostics, build_ctx, rhs_node)?;
let out = lhs.and_then(move |l| Ok(l || rhs.extract()?)); let out = lhs.and_then(move |l| Ok(l || rhs.extract()?));
Ok(L::wrap_boolean(out)) Ok(L::wrap_boolean(out.into_dyn()))
} }
BinaryOp::LogicalAnd => { BinaryOp::LogicalAnd => {
let lhs = expect_boolean_expression(language, diagnostics, build_ctx, lhs_node)?; let lhs = expect_boolean_expression(language, diagnostics, build_ctx, lhs_node)?;
let rhs = expect_boolean_expression(language, diagnostics, build_ctx, rhs_node)?; let rhs = expect_boolean_expression(language, diagnostics, build_ctx, rhs_node)?;
let out = lhs.and_then(move |l| Ok(l && rhs.extract()?)); let out = lhs.and_then(move |l| Ok(l && rhs.extract()?));
Ok(L::wrap_boolean(out)) Ok(L::wrap_boolean(out.into_dyn()))
} }
BinaryOp::Eq | BinaryOp::Ne => { BinaryOp::Eq | BinaryOp::Ne => {
let lhs = build_expression(language, diagnostics, build_ctx, lhs_node)?; let lhs = build_expression(language, diagnostics, build_ctx, lhs_node)?;
let rhs = build_expression(language, diagnostics, build_ctx, rhs_node)?; let rhs = build_expression(language, diagnostics, build_ctx, rhs_node)?;
let lty = lhs.type_name(); let lty = lhs.type_name();
let rty = rhs.type_name(); let rty = rhs.type_name();
let out = lhs.try_into_eq(rhs).ok_or_else(|| { let eq = lhs.try_into_eq(rhs).ok_or_else(|| {
let message = format!("Cannot compare expressions of type `{lty}` and `{rty}`"); let message = format!("Cannot compare expressions of type `{lty}` and `{rty}`");
TemplateParseError::expression(message, span) TemplateParseError::expression(message, span)
})?; })?;
match op { let out = match op {
BinaryOp::Eq => Ok(L::wrap_boolean(out)), BinaryOp::Eq => eq.into_dyn(),
BinaryOp::Ne => Ok(L::wrap_boolean(out.map(|eq| !eq))), BinaryOp::Ne => eq.map(|eq| !eq).into_dyn(),
_ => unreachable!(), _ => unreachable!(),
} };
Ok(L::wrap_boolean(out))
} }
BinaryOp::Ge | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Lt => { BinaryOp::Ge | BinaryOp::Gt | BinaryOp::Le | BinaryOp::Lt => {
let lhs = build_expression(language, diagnostics, build_ctx, lhs_node)?; let lhs = build_expression(language, diagnostics, build_ctx, lhs_node)?;
let rhs = build_expression(language, diagnostics, build_ctx, rhs_node)?; let rhs = build_expression(language, diagnostics, build_ctx, rhs_node)?;
let lty = lhs.type_name(); let lty = lhs.type_name();
let rty = rhs.type_name(); let rty = rhs.type_name();
let out = lhs.try_into_cmp(rhs).ok_or_else(|| { let cmp = lhs.try_into_cmp(rhs).ok_or_else(|| {
let message = format!("Cannot compare expressions of type `{lty}` and `{rty}`"); let message = format!("Cannot compare expressions of type `{lty}` and `{rty}`");
TemplateParseError::expression(message, span) TemplateParseError::expression(message, span)
})?; })?;
match op { let out = match op {
BinaryOp::Ge => Ok(L::wrap_boolean(out.map(|ordering| ordering.is_ge()))), BinaryOp::Ge => cmp.map(|ordering| ordering.is_ge()).into_dyn(),
BinaryOp::Gt => Ok(L::wrap_boolean(out.map(|ordering| ordering.is_gt()))), BinaryOp::Gt => cmp.map(|ordering| ordering.is_gt()).into_dyn(),
BinaryOp::Le => Ok(L::wrap_boolean(out.map(|ordering| ordering.is_le()))), BinaryOp::Le => cmp.map(|ordering| ordering.is_le()).into_dyn(),
BinaryOp::Lt => Ok(L::wrap_boolean(out.map(|ordering| ordering.is_lt()))), BinaryOp::Lt => cmp.map(|ordering| ordering.is_lt()).into_dyn(),
_ => unreachable!(), _ => unreachable!(),
} };
Ok(L::wrap_boolean(out))
} }
} }
} }
@ -771,7 +780,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.and_then(|s| Ok(s.len().try_into()?)); let out_property = self_property.and_then(|s| Ok(s.len().try_into()?));
Ok(L::wrap_integer(out_property)) Ok(L::wrap_integer(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -783,7 +792,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
expect_plain_text_expression(language, diagnostics, build_ctx, needle_node)?; expect_plain_text_expression(language, diagnostics, build_ctx, needle_node)?;
let out_property = (self_property, needle_property) let out_property = (self_property, needle_property)
.map(|(haystack, needle)| haystack.contains(&needle)); .map(|(haystack, needle)| haystack.contains(&needle));
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -794,7 +803,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
expect_plain_text_expression(language, diagnostics, build_ctx, needle_node)?; expect_plain_text_expression(language, diagnostics, build_ctx, needle_node)?;
let out_property = (self_property, needle_property) let out_property = (self_property, needle_property)
.map(|(haystack, needle)| haystack.starts_with(&needle)); .map(|(haystack, needle)| haystack.starts_with(&needle));
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -805,7 +814,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
expect_plain_text_expression(language, diagnostics, build_ctx, needle_node)?; expect_plain_text_expression(language, diagnostics, build_ctx, needle_node)?;
let out_property = (self_property, needle_property) let out_property = (self_property, needle_property)
.map(|(haystack, needle)| haystack.ends_with(&needle)); .map(|(haystack, needle)| haystack.ends_with(&needle));
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -820,7 +829,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
.map(ToOwned::to_owned) .map(ToOwned::to_owned)
.unwrap_or(haystack) .unwrap_or(haystack)
}); });
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -835,7 +844,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
.map(ToOwned::to_owned) .map(ToOwned::to_owned)
.unwrap_or(haystack) .unwrap_or(haystack)
}); });
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -843,7 +852,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|s| s.trim().to_owned()); let out_property = self_property.map(|s| s.trim().to_owned());
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -851,7 +860,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|s| s.trim_start().to_owned()); let out_property = self_property.map(|s| s.trim_start().to_owned());
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -859,7 +868,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|s| s.trim_end().to_owned()); let out_property = self_property.map(|s| s.trim_end().to_owned());
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -877,7 +886,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
s.get(start_idx..end_idx).unwrap_or_default().to_owned() s.get(start_idx..end_idx).unwrap_or_default().to_owned()
}, },
); );
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -886,7 +895,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = let out_property =
self_property.map(|s| s.lines().next().unwrap_or_default().to_string()); self_property.map(|s| s.lines().next().unwrap_or_default().to_string());
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -894,7 +903,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|s| s.lines().map(|l| l.to_owned()).collect()); let out_property = self_property.map(|s| s.lines().map(|l| l.to_owned()).collect());
Ok(L::wrap_string_list(out_property)) Ok(L::wrap_string_list(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -902,7 +911,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|s| s.to_uppercase()); let out_property = self_property.map(|s| s.to_uppercase());
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -910,7 +919,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|s| s.to_lowercase()); let out_property = self_property.map(|s| s.to_lowercase());
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -918,7 +927,7 @@ fn builtin_string_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|s| serde_json::to_string(&s).unwrap()); let out_property = self_property.map(|s| serde_json::to_string(&s).unwrap());
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map map
@ -960,7 +969,7 @@ fn builtin_config_value_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.and_then(extract); let out_property = self_property.and_then(extract);
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -968,7 +977,7 @@ fn builtin_config_value_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.and_then(extract); let out_property = self_property.and_then(extract);
Ok(L::wrap_integer(out_property)) Ok(L::wrap_integer(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -976,7 +985,7 @@ fn builtin_config_value_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.and_then(extract); let out_property = self_property.and_then(extract);
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -984,7 +993,7 @@ fn builtin_config_value_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.and_then(extract); let out_property = self_property.and_then(extract);
Ok(L::wrap_string_list(out_property)) Ok(L::wrap_string_list(out_property.into_dyn()))
}, },
); );
// TODO: add is_<type>() -> Boolean? // TODO: add is_<type>() -> Boolean?
@ -1002,7 +1011,7 @@ fn builtin_signature_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|signature| signature.name); let out_property = self_property.map(|signature| signature.name);
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1010,7 +1019,7 @@ fn builtin_signature_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|signature| signature.email.into()); let out_property = self_property.map(|signature| signature.email.into());
Ok(L::wrap_email(out_property)) Ok(L::wrap_email(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1026,7 +1035,7 @@ fn builtin_signature_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
let (username, _) = text_util::split_email(&signature.email); let (username, _) = text_util::split_email(&signature.email);
username.to_owned() username.to_owned()
}); });
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1034,7 +1043,7 @@ fn builtin_signature_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|signature| signature.timestamp); let out_property = self_property.map(|signature| signature.timestamp);
Ok(L::wrap_timestamp(out_property)) Ok(L::wrap_timestamp(out_property.into_dyn()))
}, },
); );
map map
@ -1053,7 +1062,7 @@ fn builtin_email_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
let (local, _) = text_util::split_email(&email.0); let (local, _) = text_util::split_email(&email.0);
local.to_owned() local.to_owned()
}); });
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1064,7 +1073,7 @@ fn builtin_email_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
let (_, domain) = text_util::split_email(&email.0); let (_, domain) = text_util::split_email(&email.0);
domain.unwrap_or_default().to_owned() domain.unwrap_or_default().to_owned()
}); });
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map map
@ -1080,7 +1089,7 @@ fn builtin_size_hint_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.and_then(|(lower, _)| Ok(i64::try_from(lower)?)); let out_property = self_property.and_then(|(lower, _)| Ok(i64::try_from(lower)?));
Ok(L::wrap_integer(out_property)) Ok(L::wrap_integer(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1089,7 +1098,7 @@ fn builtin_size_hint_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = let out_property =
self_property.and_then(|(_, upper)| Ok(upper.map(i64::try_from).transpose()?)); self_property.and_then(|(_, upper)| Ok(upper.map(i64::try_from).transpose()?));
Ok(L::wrap_integer_opt(out_property)) Ok(L::wrap_integer_opt(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1100,7 +1109,7 @@ fn builtin_size_hint_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
let exact = (Some(lower) == upper).then_some(lower); let exact = (Some(lower) == upper).then_some(lower);
Ok(exact.map(i64::try_from).transpose()?) Ok(exact.map(i64::try_from).transpose()?)
}); });
Ok(L::wrap_integer_opt(out_property)) Ok(L::wrap_integer_opt(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1108,7 +1117,7 @@ fn builtin_size_hint_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|(_, upper)| upper == Some(0)); let out_property = self_property.map(|(_, upper)| upper == Some(0));
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map map
@ -1128,7 +1137,7 @@ fn builtin_timestamp_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
let out_property = self_property.and_then(move |timestamp| { let out_property = self_property.and_then(move |timestamp| {
Ok(time_util::format_duration(&timestamp, &now, &format)?) Ok(time_util::format_duration(&timestamp, &now, &format)?)
}); });
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1147,7 +1156,7 @@ fn builtin_timestamp_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
&timestamp, &format, &timestamp, &format,
)?) )?)
}); });
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1158,7 +1167,7 @@ fn builtin_timestamp_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
timestamp.tz_offset = 0; timestamp.tz_offset = 0;
timestamp timestamp
}); });
Ok(L::wrap_timestamp(out_property)) Ok(L::wrap_timestamp(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1173,7 +1182,7 @@ fn builtin_timestamp_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
timestamp.tz_offset = tz_offset; timestamp.tz_offset = tz_offset;
timestamp timestamp
}); });
Ok(L::wrap_timestamp(out_property)) Ok(L::wrap_timestamp(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1191,7 +1200,7 @@ fn builtin_timestamp_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
}, },
)?; )?;
let out_property = self_property.map(move |timestamp| date_pattern.matches(&timestamp)); let out_property = self_property.map(move |timestamp| date_pattern.matches(&timestamp));
Ok(L::wrap_boolean(out_property)) Ok(L::wrap_boolean(out_property.into_dyn()))
}, },
); );
map.insert("before", map["after"]); map.insert("before", map["after"]);
@ -1208,7 +1217,7 @@ fn builtin_timestamp_range_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|time_range| time_range.start); let out_property = self_property.map(|time_range| time_range.start);
Ok(L::wrap_timestamp(out_property)) Ok(L::wrap_timestamp(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1216,7 +1225,7 @@ fn builtin_timestamp_range_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.map(|time_range| time_range.end); let out_property = self_property.map(|time_range| time_range.end);
Ok(L::wrap_timestamp(out_property)) Ok(L::wrap_timestamp(out_property.into_dyn()))
}, },
); );
map.insert( map.insert(
@ -1224,7 +1233,7 @@ fn builtin_timestamp_range_methods<'a, L: TemplateLanguage<'a> + ?Sized>(
|_language, _diagnostics, _build_ctx, self_property, function| { |_language, _diagnostics, _build_ctx, self_property, function| {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.and_then(|time_range| Ok(time_range.duration()?)); let out_property = self_property.and_then(|time_range| Ok(time_range.duration()?));
Ok(L::wrap_string(out_property)) Ok(L::wrap_string(out_property.into_dyn()))
}, },
); );
map map
@ -1257,9 +1266,8 @@ pub fn build_formattable_list_method<'a, L, O>(
self_property: impl TemplateProperty<Output = Vec<O>> + 'a, self_property: impl TemplateProperty<Output = Vec<O>> + 'a,
function: &FunctionCallNode, function: &FunctionCallNode,
// TODO: Generic L: WrapProperty<O> trait might be needed to support more // TODO: Generic L: WrapProperty<O> trait might be needed to support more
// list operations such as first()/slice(). For .map(), a simple callback // list operations such as first()/slice().
// works. For .filter(), redundant boxing is needed. wrap_item: impl Fn(Box<dyn TemplateProperty<Output = O> + 'a>) -> L::Property,
wrap_item: impl Fn(PropertyPlaceholder<O>) -> L::Property,
wrap_list: impl Fn(Box<dyn TemplateProperty<Output = Vec<O>> + 'a>) -> L::Property, wrap_list: impl Fn(Box<dyn TemplateProperty<Output = Vec<O>> + 'a>) -> L::Property,
) -> TemplateParseResult<L::Property> ) -> TemplateParseResult<L::Property>
where where
@ -1270,7 +1278,7 @@ where
"len" => { "len" => {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.and_then(|items| Ok(items.len().try_into()?)); let out_property = self_property.and_then(|items| Ok(items.len().try_into()?));
L::wrap_integer(out_property) L::wrap_integer(out_property.into_dyn())
} }
"join" => { "join" => {
let [separator_node] = function.expect_exact_arguments()?; let [separator_node] = function.expect_exact_arguments()?;
@ -1310,7 +1318,7 @@ pub fn build_unformattable_list_method<'a, L, O>(
build_ctx: &BuildContext<L::Property>, build_ctx: &BuildContext<L::Property>,
self_property: impl TemplateProperty<Output = Vec<O>> + 'a, self_property: impl TemplateProperty<Output = Vec<O>> + 'a,
function: &FunctionCallNode, function: &FunctionCallNode,
wrap_item: impl Fn(PropertyPlaceholder<O>) -> L::Property, wrap_item: impl Fn(Box<dyn TemplateProperty<Output = O> + 'a>) -> L::Property,
wrap_list: impl Fn(Box<dyn TemplateProperty<Output = Vec<O>> + 'a>) -> L::Property, wrap_list: impl Fn(Box<dyn TemplateProperty<Output = Vec<O>> + 'a>) -> L::Property,
) -> TemplateParseResult<L::Property> ) -> TemplateParseResult<L::Property>
where where
@ -1321,7 +1329,7 @@ where
"len" => { "len" => {
function.expect_no_arguments()?; function.expect_no_arguments()?;
let out_property = self_property.and_then(|items| Ok(items.len().try_into()?)); let out_property = self_property.and_then(|items| Ok(items.len().try_into()?));
L::wrap_integer(out_property) L::wrap_integer(out_property.into_dyn())
} }
// No "join" // No "join"
"filter" => build_filter_operation( "filter" => build_filter_operation(
@ -1356,7 +1364,7 @@ fn build_filter_operation<'a, L, O, P, B>(
build_ctx: &BuildContext<L::Property>, build_ctx: &BuildContext<L::Property>,
self_property: P, self_property: P,
function: &FunctionCallNode, function: &FunctionCallNode,
wrap_item: impl Fn(PropertyPlaceholder<O>) -> L::Property, wrap_item: impl Fn(Box<dyn TemplateProperty<Output = O> + 'a>) -> L::Property,
wrap_list: impl Fn(Box<dyn TemplateProperty<Output = B> + 'a>) -> L::Property, wrap_list: impl Fn(Box<dyn TemplateProperty<Output = B> + 'a>) -> L::Property,
) -> TemplateParseResult<L::Property> ) -> TemplateParseResult<L::Property>
where where
@ -1372,7 +1380,7 @@ where
build_lambda_expression( build_lambda_expression(
build_ctx, build_ctx,
lambda, lambda,
&[&|| wrap_item(item_placeholder.clone())], &[&|| wrap_item(item_placeholder.clone().into_dyn())],
|build_ctx, body| expect_boolean_expression(language, diagnostics, build_ctx, body), |build_ctx, body| expect_boolean_expression(language, diagnostics, build_ctx, body),
) )
})?; })?;
@ -1401,7 +1409,7 @@ fn build_map_operation<'a, L, O, P>(
build_ctx: &BuildContext<L::Property>, build_ctx: &BuildContext<L::Property>,
self_property: P, self_property: P,
function: &FunctionCallNode, function: &FunctionCallNode,
wrap_item: impl Fn(PropertyPlaceholder<O>) -> L::Property, wrap_item: impl Fn(Box<dyn TemplateProperty<Output = O> + 'a>) -> L::Property,
) -> TemplateParseResult<L::Property> ) -> TemplateParseResult<L::Property>
where where
L: TemplateLanguage<'a> + ?Sized, L: TemplateLanguage<'a> + ?Sized,
@ -1415,7 +1423,7 @@ where
build_lambda_expression( build_lambda_expression(
build_ctx, build_ctx,
lambda, lambda,
&[&|| wrap_item(item_placeholder.clone())], &[&|| wrap_item(item_placeholder.clone().into_dyn())],
|build_ctx, body| expect_template_expression(language, diagnostics, build_ctx, body), |build_ctx, body| expect_template_expression(language, diagnostics, build_ctx, body),
) )
})?; })?;
@ -1648,7 +1656,9 @@ fn builtin_functions<'a, L: TemplateLanguage<'a> + ?Sized>() -> TemplateBuildFun
.with_source(err) .with_source(err)
})?; })?;
// .decorated("", "") to trim leading/trailing whitespace // .decorated("", "") to trim leading/trailing whitespace
Ok(L::wrap_config_value(Literal(value.decorated("", "")))) Ok(L::wrap_config_value(
Literal(value.decorated("", "")).into_dyn(),
))
}); });
map map
} }
@ -1740,15 +1750,15 @@ pub fn build_expression<'a, L: TemplateLanguage<'a> + ?Sized>(
} }
} }
ExpressionKind::Boolean(value) => { ExpressionKind::Boolean(value) => {
let property = L::wrap_boolean(Literal(*value)); let property = L::wrap_boolean(Literal(*value).into_dyn());
Ok(Expression::unlabeled(property)) Ok(Expression::unlabeled(property))
} }
ExpressionKind::Integer(value) => { ExpressionKind::Integer(value) => {
let property = L::wrap_integer(Literal(*value)); let property = L::wrap_integer(Literal(*value).into_dyn());
Ok(Expression::unlabeled(property)) Ok(Expression::unlabeled(property))
} }
ExpressionKind::String(value) => { ExpressionKind::String(value) => {
let property = L::wrap_string(Literal(value.clone())); let property = L::wrap_string(Literal(value.clone()).into_dyn());
Ok(Expression::unlabeled(property)) Ok(Expression::unlabeled(property))
} }
ExpressionKind::Unary(op, arg_node) => { ExpressionKind::Unary(op, arg_node) => {
@ -1817,12 +1827,12 @@ pub fn build<'a, C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized>(
node: &ExpressionNode, node: &ExpressionNode,
// TODO: Generic L: WrapProperty<C> trait might be better. See the // TODO: Generic L: WrapProperty<C> trait might be better. See the
// comment in build_formattable_list_method(). // comment in build_formattable_list_method().
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property, wrap_self: impl Fn(Box<dyn TemplateProperty<Output = C> + 'a>) -> L::Property,
) -> TemplateParseResult<TemplateRenderer<'a, C>> { ) -> TemplateParseResult<TemplateRenderer<'a, C>> {
let self_placeholder = PropertyPlaceholder::new(); let self_placeholder = PropertyPlaceholder::new();
let build_ctx = BuildContext { let build_ctx = BuildContext {
local_variables: HashMap::new(), local_variables: HashMap::new(),
self_variable: &|| wrap_self(self_placeholder.clone()), self_variable: &|| wrap_self(self_placeholder.clone().into_dyn()),
}; };
let template = expect_template_expression(language, diagnostics, &build_ctx, node)?; let template = expect_template_expression(language, diagnostics, &build_ctx, node)?;
Ok(TemplateRenderer::new(template, self_placeholder)) Ok(TemplateRenderer::new(template, self_placeholder))
@ -1834,7 +1844,7 @@ pub fn parse<'a, C: Clone + 'a, L: TemplateLanguage<'a> + ?Sized>(
diagnostics: &mut TemplateDiagnostics, diagnostics: &mut TemplateDiagnostics,
template_text: &str, template_text: &str,
aliases_map: &TemplateAliasesMap, aliases_map: &TemplateAliasesMap,
wrap_self: impl Fn(PropertyPlaceholder<C>) -> L::Property, wrap_self: impl Fn(Box<dyn TemplateProperty<Output = C> + 'a>) -> L::Property,
) -> TemplateParseResult<TemplateRenderer<'a, C>> { ) -> TemplateParseResult<TemplateRenderer<'a, C>> {
let node = template_parser::parse(template_text, aliases_map)?; let node = template_parser::parse(template_text, aliases_map)?;
build(language, diagnostics, &node, wrap_self) build(language, diagnostics, &node, wrap_self)
@ -2046,11 +2056,14 @@ mod tests {
} }
} }
// TODO: O doesn't have to be captured, but "currently, all type parameters fn literal<'a, O: Clone + 'a>(value: O) -> Box<dyn TemplateProperty<Output = O> + 'a> {
// are required to be mentioned in the precise captures list" as of rustc Literal(value).into_dyn()
// 1.85.0. }
fn new_error_property<O>(message: &str) -> impl TemplateProperty<Output = O> + use<'_, O> {
Literal(()).and_then(|()| Err(TemplatePropertyError(message.into()))) fn new_error_property<O>(message: &str) -> Box<dyn TemplateProperty<Output = O> + '_> {
Literal(())
.and_then(|()| Err(TemplatePropertyError(message.into())))
.into_dyn()
} }
fn new_signature(name: &str, email: &str) -> Signature { fn new_signature(name: &str, email: &str) -> Signature {
@ -2071,9 +2084,9 @@ mod tests {
#[test] #[test]
fn test_parsed_tree() { fn test_parsed_tree() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("divergent", || L::wrap_boolean(Literal(false))); env.add_keyword("divergent", || L::wrap_boolean(literal(false)));
env.add_keyword("empty", || L::wrap_boolean(Literal(true))); env.add_keyword("empty", || L::wrap_boolean(literal(true)));
env.add_keyword("hello", || L::wrap_string(Literal("Hello".to_owned()))); env.add_keyword("hello", || L::wrap_string(literal("Hello".to_owned())));
// Empty // Empty
insta::assert_snapshot!(env.render_ok(r#" "#), @""); insta::assert_snapshot!(env.render_ok(r#" "#), @"");
@ -2100,8 +2113,8 @@ mod tests {
#[test] #[test]
fn test_parse_error() { fn test_parse_error() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("description", || L::wrap_string(Literal("".to_owned()))); env.add_keyword("description", || L::wrap_string(literal("".to_owned())));
env.add_keyword("empty", || L::wrap_boolean(Literal(true))); env.add_keyword("empty", || L::wrap_boolean(literal(true)));
insta::assert_snapshot!(env.parse_err(r#"description ()"#), @r" insta::assert_snapshot!(env.parse_err(r#"description ()"#), @r"
--> 1:13 --> 1:13
@ -2345,7 +2358,7 @@ mod tests {
#[test] #[test]
fn test_self_keyword() { fn test_self_keyword() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("say_hello", || L::wrap_string(Literal("Hello".to_owned()))); env.add_keyword("say_hello", || L::wrap_string(literal("Hello".to_owned())));
insta::assert_snapshot!(env.render_ok(r#"self.say_hello()"#), @"Hello"); insta::assert_snapshot!(env.render_ok(r#"self.say_hello()"#), @"Hello");
insta::assert_snapshot!(env.parse_err(r#"self"#), @r" insta::assert_snapshot!(env.parse_err(r#"self"#), @r"
@ -2366,9 +2379,9 @@ mod tests {
insta::assert_snapshot!(env.render_ok(r#"if("a", true, false)"#), @"true"); insta::assert_snapshot!(env.render_ok(r#"if("a", true, false)"#), @"true");
env.add_keyword("sl0", || { env.add_keyword("sl0", || {
L::wrap_string_list(Literal::<Vec<String>>(vec![])) L::wrap_string_list(literal::<Vec<String>>(vec![]))
}); });
env.add_keyword("sl1", || L::wrap_string_list(Literal(vec!["".to_owned()]))); env.add_keyword("sl1", || L::wrap_string_list(literal(vec!["".to_owned()])));
insta::assert_snapshot!(env.render_ok(r#"if(sl0, true, false)"#), @"false"); insta::assert_snapshot!(env.render_ok(r#"if(sl0, true, false)"#), @"false");
insta::assert_snapshot!(env.render_ok(r#"if(sl1, true, false)"#), @"true"); insta::assert_snapshot!(env.render_ok(r#"if(sl1, true, false)"#), @"true");
@ -2383,8 +2396,8 @@ mod tests {
"); ");
// Optional integer can be converted to boolean, and Some(0) is truthy. // Optional integer can be converted to boolean, and Some(0) is truthy.
env.add_keyword("none_i64", || L::wrap_integer_opt(Literal(None))); env.add_keyword("none_i64", || L::wrap_integer_opt(literal(None)));
env.add_keyword("some_i64", || L::wrap_integer_opt(Literal(Some(0)))); env.add_keyword("some_i64", || L::wrap_integer_opt(literal(Some(0))));
insta::assert_snapshot!(env.render_ok(r#"if(none_i64, true, false)"#), @"false"); insta::assert_snapshot!(env.render_ok(r#"if(none_i64, true, false)"#), @"false");
insta::assert_snapshot!(env.render_ok(r#"if(some_i64, true, false)"#), @"true"); insta::assert_snapshot!(env.render_ok(r#"if(some_i64, true, false)"#), @"true");
@ -2406,10 +2419,10 @@ mod tests {
"); ");
env.add_keyword("empty_email", || { env.add_keyword("empty_email", || {
L::wrap_email(Literal(Email("".to_owned()))) L::wrap_email(literal(Email("".to_owned())))
}); });
env.add_keyword("nonempty_email", || { env.add_keyword("nonempty_email", || {
L::wrap_email(Literal(Email("local@domain".to_owned()))) L::wrap_email(literal(Email("local@domain".to_owned())))
}); });
insta::assert_snapshot!(env.render_ok(r#"if(empty_email, true, false)"#), @"false"); insta::assert_snapshot!(env.render_ok(r#"if(empty_email, true, false)"#), @"false");
insta::assert_snapshot!(env.render_ok(r#"if(nonempty_email, true, false)"#), @"true"); insta::assert_snapshot!(env.render_ok(r#"if(nonempty_email, true, false)"#), @"true");
@ -2418,9 +2431,9 @@ mod tests {
#[test] #[test]
fn test_arithmetic_operation() { fn test_arithmetic_operation() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("none_i64", || L::wrap_integer_opt(Literal(None))); env.add_keyword("none_i64", || L::wrap_integer_opt(literal(None)));
env.add_keyword("some_i64", || L::wrap_integer_opt(Literal(Some(1)))); env.add_keyword("some_i64", || L::wrap_integer_opt(literal(Some(1))));
env.add_keyword("i64_min", || L::wrap_integer(Literal(i64::MIN))); env.add_keyword("i64_min", || L::wrap_integer(literal(i64::MIN)));
insta::assert_snapshot!(env.render_ok(r#"-1"#), @"-1"); insta::assert_snapshot!(env.render_ok(r#"-1"#), @"-1");
insta::assert_snapshot!(env.render_ok(r#"--2"#), @"2"); insta::assert_snapshot!(env.render_ok(r#"--2"#), @"2");
@ -2455,10 +2468,10 @@ mod tests {
fn test_logical_operation() { fn test_logical_operation() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("email1", || { env.add_keyword("email1", || {
L::wrap_email(Literal(Email("local-1@domain".to_owned()))) L::wrap_email(literal(Email("local-1@domain".to_owned())))
}); });
env.add_keyword("email2", || { env.add_keyword("email2", || {
L::wrap_email(Literal(Email("local-2@domain".to_owned()))) L::wrap_email(literal(Email("local-2@domain".to_owned())))
}); });
insta::assert_snapshot!(env.render_ok(r#"!false"#), @"true"); insta::assert_snapshot!(env.render_ok(r#"!false"#), @"true");
@ -2497,8 +2510,8 @@ mod tests {
#[test] #[test]
fn test_list_method() { fn test_list_method() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("empty", || L::wrap_boolean(Literal(true))); env.add_keyword("empty", || L::wrap_boolean(literal(true)));
env.add_keyword("sep", || L::wrap_string(Literal("sep".to_owned()))); env.add_keyword("sep", || L::wrap_string(literal("sep".to_owned())));
insta::assert_snapshot!(env.render_ok(r#""".lines().len()"#), @"0"); insta::assert_snapshot!(env.render_ok(r#""".lines().len()"#), @"0");
insta::assert_snapshot!(env.render_ok(r#""a\nb\nc".lines().len()"#), @"3"); insta::assert_snapshot!(env.render_ok(r#""a\nb\nc".lines().len()"#), @"3");
@ -2614,7 +2627,7 @@ mod tests {
fn test_string_method() { fn test_string_method() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("description", || { env.add_keyword("description", || {
L::wrap_string(Literal("description 1".to_owned())) L::wrap_string(literal("description 1".to_owned()))
}); });
env.add_keyword("bad_string", || L::wrap_string(new_error_property("Bad"))); env.add_keyword("bad_string", || L::wrap_string(new_error_property("Bad")));
@ -2711,16 +2724,16 @@ mod tests {
fn test_config_value_method() { fn test_config_value_method() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("boolean", || { env.add_keyword("boolean", || {
L::wrap_config_value(Literal(ConfigValue::from(true))) L::wrap_config_value(literal(ConfigValue::from(true)))
}); });
env.add_keyword("integer", || { env.add_keyword("integer", || {
L::wrap_config_value(Literal(ConfigValue::from(42))) L::wrap_config_value(literal(ConfigValue::from(42)))
}); });
env.add_keyword("string", || { env.add_keyword("string", || {
L::wrap_config_value(Literal(ConfigValue::from("foo"))) L::wrap_config_value(literal(ConfigValue::from("foo")))
}); });
env.add_keyword("string_list", || { env.add_keyword("string_list", || {
L::wrap_config_value(Literal(ConfigValue::from_iter(["foo", "bar"]))) L::wrap_config_value(literal(ConfigValue::from_iter(["foo", "bar"])))
}); });
insta::assert_snapshot!(env.render_ok("boolean"), @"true"); insta::assert_snapshot!(env.render_ok("boolean"), @"true");
@ -2752,7 +2765,7 @@ mod tests {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("author", || { env.add_keyword("author", || {
L::wrap_signature(Literal(new_signature("Test User", "test.user@example.com"))) L::wrap_signature(literal(new_signature("Test User", "test.user@example.com")))
}); });
insta::assert_snapshot!(env.render_ok(r#"author"#), @"Test User <test.user@example.com>"); insta::assert_snapshot!(env.render_ok(r#"author"#), @"Test User <test.user@example.com>");
insta::assert_snapshot!(env.render_ok(r#"author.name()"#), @"Test User"); insta::assert_snapshot!(env.render_ok(r#"author.name()"#), @"Test User");
@ -2760,7 +2773,7 @@ mod tests {
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user"); insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user");
env.add_keyword("author", || { env.add_keyword("author", || {
L::wrap_signature(Literal(new_signature( L::wrap_signature(literal(new_signature(
"Another Test User", "Another Test User",
"test.user@example.com", "test.user@example.com",
))) )))
@ -2771,7 +2784,7 @@ mod tests {
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user"); insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user");
env.add_keyword("author", || { env.add_keyword("author", || {
L::wrap_signature(Literal(new_signature( L::wrap_signature(literal(new_signature(
"Test User", "Test User",
"test.user@invalid@example.com", "test.user@invalid@example.com",
))) )))
@ -2782,14 +2795,14 @@ mod tests {
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user"); insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user");
env.add_keyword("author", || { env.add_keyword("author", || {
L::wrap_signature(Literal(new_signature("Test User", "test.user"))) L::wrap_signature(literal(new_signature("Test User", "test.user")))
}); });
insta::assert_snapshot!(env.render_ok(r#"author"#), @"Test User <test.user>"); insta::assert_snapshot!(env.render_ok(r#"author"#), @"Test User <test.user>");
insta::assert_snapshot!(env.render_ok(r#"author.email()"#), @"test.user"); insta::assert_snapshot!(env.render_ok(r#"author.email()"#), @"test.user");
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user"); insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user");
env.add_keyword("author", || { env.add_keyword("author", || {
L::wrap_signature(Literal(new_signature( L::wrap_signature(literal(new_signature(
"Test User", "Test User",
"test.user+tag@example.com", "test.user+tag@example.com",
))) )))
@ -2799,14 +2812,14 @@ mod tests {
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user+tag"); insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user+tag");
env.add_keyword("author", || { env.add_keyword("author", || {
L::wrap_signature(Literal(new_signature("Test User", "x@y"))) L::wrap_signature(literal(new_signature("Test User", "x@y")))
}); });
insta::assert_snapshot!(env.render_ok(r#"author"#), @"Test User <x@y>"); insta::assert_snapshot!(env.render_ok(r#"author"#), @"Test User <x@y>");
insta::assert_snapshot!(env.render_ok(r#"author.email()"#), @"x@y"); insta::assert_snapshot!(env.render_ok(r#"author.email()"#), @"x@y");
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"x"); insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"x");
env.add_keyword("author", || { env.add_keyword("author", || {
L::wrap_signature(Literal(new_signature("", "test.user@example.com"))) L::wrap_signature(literal(new_signature("", "test.user@example.com")))
}); });
insta::assert_snapshot!(env.render_ok(r#"author"#), @"<test.user@example.com>"); insta::assert_snapshot!(env.render_ok(r#"author"#), @"<test.user@example.com>");
insta::assert_snapshot!(env.render_ok(r#"author.name()"#), @""); insta::assert_snapshot!(env.render_ok(r#"author.name()"#), @"");
@ -2814,7 +2827,7 @@ mod tests {
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user"); insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"test.user");
env.add_keyword("author", || { env.add_keyword("author", || {
L::wrap_signature(Literal(new_signature("Test User", ""))) L::wrap_signature(literal(new_signature("Test User", "")))
}); });
insta::assert_snapshot!(env.render_ok(r#"author"#), @"Test User"); insta::assert_snapshot!(env.render_ok(r#"author"#), @"Test User");
insta::assert_snapshot!(env.render_ok(r#"author.name()"#), @"Test User"); insta::assert_snapshot!(env.render_ok(r#"author.name()"#), @"Test User");
@ -2822,7 +2835,7 @@ mod tests {
insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @""); insta::assert_snapshot!(env.render_ok(r#"author.username()"#), @"");
env.add_keyword("author", || { env.add_keyword("author", || {
L::wrap_signature(Literal(new_signature("", ""))) L::wrap_signature(literal(new_signature("", "")))
}); });
insta::assert_snapshot!(env.render_ok(r#"author"#), @""); insta::assert_snapshot!(env.render_ok(r#"author"#), @"");
insta::assert_snapshot!(env.render_ok(r#"author.name()"#), @""); insta::assert_snapshot!(env.render_ok(r#"author.name()"#), @"");
@ -2834,19 +2847,19 @@ mod tests {
fn test_size_hint_method() { fn test_size_hint_method() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("unbounded", || L::wrap_size_hint(Literal((5, None)))); env.add_keyword("unbounded", || L::wrap_size_hint(literal((5, None))));
insta::assert_snapshot!(env.render_ok(r#"unbounded.lower()"#), @"5"); insta::assert_snapshot!(env.render_ok(r#"unbounded.lower()"#), @"5");
insta::assert_snapshot!(env.render_ok(r#"unbounded.upper()"#), @""); insta::assert_snapshot!(env.render_ok(r#"unbounded.upper()"#), @"");
insta::assert_snapshot!(env.render_ok(r#"unbounded.exact()"#), @""); insta::assert_snapshot!(env.render_ok(r#"unbounded.exact()"#), @"");
insta::assert_snapshot!(env.render_ok(r#"unbounded.zero()"#), @"false"); insta::assert_snapshot!(env.render_ok(r#"unbounded.zero()"#), @"false");
env.add_keyword("bounded", || L::wrap_size_hint(Literal((0, Some(10))))); env.add_keyword("bounded", || L::wrap_size_hint(literal((0, Some(10)))));
insta::assert_snapshot!(env.render_ok(r#"bounded.lower()"#), @"0"); insta::assert_snapshot!(env.render_ok(r#"bounded.lower()"#), @"0");
insta::assert_snapshot!(env.render_ok(r#"bounded.upper()"#), @"10"); insta::assert_snapshot!(env.render_ok(r#"bounded.upper()"#), @"10");
insta::assert_snapshot!(env.render_ok(r#"bounded.exact()"#), @""); insta::assert_snapshot!(env.render_ok(r#"bounded.exact()"#), @"");
insta::assert_snapshot!(env.render_ok(r#"bounded.zero()"#), @"false"); insta::assert_snapshot!(env.render_ok(r#"bounded.zero()"#), @"false");
env.add_keyword("zero", || L::wrap_size_hint(Literal((0, Some(0))))); env.add_keyword("zero", || L::wrap_size_hint(literal((0, Some(0)))));
insta::assert_snapshot!(env.render_ok(r#"zero.lower()"#), @"0"); insta::assert_snapshot!(env.render_ok(r#"zero.lower()"#), @"0");
insta::assert_snapshot!(env.render_ok(r#"zero.upper()"#), @"0"); insta::assert_snapshot!(env.render_ok(r#"zero.upper()"#), @"0");
insta::assert_snapshot!(env.render_ok(r#"zero.exact()"#), @"0"); insta::assert_snapshot!(env.render_ok(r#"zero.exact()"#), @"0");
@ -2856,7 +2869,7 @@ mod tests {
#[test] #[test]
fn test_timestamp_method() { fn test_timestamp_method() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("t0", || L::wrap_timestamp(Literal(new_timestamp(0, 0)))); env.add_keyword("t0", || L::wrap_timestamp(literal(new_timestamp(0, 0))));
insta::assert_snapshot!( insta::assert_snapshot!(
env.render_ok(r#"t0.format("%Y%m%d %H:%M:%S")"#), env.render_ok(r#"t0.format("%Y%m%d %H:%M:%S")"#),
@ -3107,7 +3120,7 @@ mod tests {
#[test] #[test]
fn test_label_function() { fn test_label_function() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("empty", || L::wrap_boolean(Literal(true))); env.add_keyword("empty", || L::wrap_boolean(literal(true)));
env.add_color("error", crossterm::style::Color::DarkRed); env.add_color("error", crossterm::style::Color::DarkRed);
env.add_color("warning", crossterm::style::Color::DarkYellow); env.add_color("warning", crossterm::style::Color::DarkYellow);
@ -3182,9 +3195,9 @@ mod tests {
fn test_coalesce_function() { fn test_coalesce_function() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("bad_string", || L::wrap_string(new_error_property("Bad"))); env.add_keyword("bad_string", || L::wrap_string(new_error_property("Bad")));
env.add_keyword("empty_string", || L::wrap_string(Literal("".to_owned()))); env.add_keyword("empty_string", || L::wrap_string(literal("".to_owned())));
env.add_keyword("non_empty_string", || { env.add_keyword("non_empty_string", || {
L::wrap_string(Literal("a".to_owned())) L::wrap_string(literal("a".to_owned()))
}); });
insta::assert_snapshot!(env.render_ok(r#"coalesce()"#), @""); insta::assert_snapshot!(env.render_ok(r#"coalesce()"#), @"");
@ -3205,8 +3218,8 @@ mod tests {
#[test] #[test]
fn test_concat_function() { fn test_concat_function() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("empty", || L::wrap_boolean(Literal(true))); env.add_keyword("empty", || L::wrap_boolean(literal(true)));
env.add_keyword("hidden", || L::wrap_boolean(Literal(false))); env.add_keyword("hidden", || L::wrap_boolean(literal(false)));
env.add_color("empty", crossterm::style::Color::DarkGreen); env.add_color("empty", crossterm::style::Color::DarkGreen);
env.add_color("error", crossterm::style::Color::DarkRed); env.add_color("error", crossterm::style::Color::DarkRed);
env.add_color("warning", crossterm::style::Color::DarkYellow); env.add_color("warning", crossterm::style::Color::DarkYellow);
@ -3223,9 +3236,9 @@ mod tests {
#[test] #[test]
fn test_separate_function() { fn test_separate_function() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("description", || L::wrap_string(Literal("".to_owned()))); env.add_keyword("description", || L::wrap_string(literal("".to_owned())));
env.add_keyword("empty", || L::wrap_boolean(Literal(true))); env.add_keyword("empty", || L::wrap_boolean(literal(true)));
env.add_keyword("hidden", || L::wrap_boolean(Literal(false))); env.add_keyword("hidden", || L::wrap_boolean(literal(false)));
env.add_color("empty", crossterm::style::Color::DarkGreen); env.add_color("empty", crossterm::style::Color::DarkGreen);
env.add_color("error", crossterm::style::Color::DarkRed); env.add_color("error", crossterm::style::Color::DarkRed);
env.add_color("warning", crossterm::style::Color::DarkYellow); env.add_color("warning", crossterm::style::Color::DarkYellow);
@ -3279,10 +3292,10 @@ mod tests {
#[test] #[test]
fn test_surround_function() { fn test_surround_function() {
let mut env = TestTemplateEnv::new(); let mut env = TestTemplateEnv::new();
env.add_keyword("lt", || L::wrap_string(Literal("<".to_owned()))); env.add_keyword("lt", || L::wrap_string(literal("<".to_owned())));
env.add_keyword("gt", || L::wrap_string(Literal(">".to_owned()))); env.add_keyword("gt", || L::wrap_string(literal(">".to_owned())));
env.add_keyword("content", || L::wrap_string(Literal("content".to_owned()))); env.add_keyword("content", || L::wrap_string(literal("content".to_owned())));
env.add_keyword("empty_content", || L::wrap_string(Literal("".to_owned()))); env.add_keyword("empty_content", || L::wrap_string(literal("".to_owned())));
env.add_color("error", crossterm::style::Color::DarkRed); env.add_color("error", crossterm::style::Color::DarkRed);
env.add_color("paren", crossterm::style::Color::Cyan); env.add_color("paren", crossterm::style::Color::Cyan);