Unnamed repository; edit this file 'description' to name the repository.
Merge #10304
10304: internal: Generate ast nodes for each ast trait r=Veykril a=Veykril Generate a `DynTrait` node per ast trait that implements the trait itself as well as conversions via the `AstNode` trait. This is a trick already employed in `hir_def::attr` with `AttrsOwner` where it was manually implemented for. This basically gives us stack trait objects for these(only useful for `hir_def::attr` currently) as well as simple conversions of `SyntaxNode` to a trait, in cases where only a result of a function call of such a trait is of interest. It doesn't have many uses yet but as its autogenerated it doesn't add any maintenance costs. bors r+ Co-authored-by: Lukas Wirth <[email protected]>
bors[bot] 2021-09-21
parent 168b00f · parent 83e97ad · commit 254022c
-rw-r--r--crates/hir/src/semantics.rs17
-rw-r--r--crates/hir_def/src/attr.rs36
-rw-r--r--crates/syntax/src/ast.rs4
-rw-r--r--crates/syntax/src/ast/generated/nodes.rs253
-rw-r--r--crates/syntax/src/ast/node_ext.rs41
-rw-r--r--crates/syntax/src/tests/sourcegen_ast.rs55
6 files changed, 331 insertions, 75 deletions
diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs
index 9821d9d4fa..51befc698c 100644
--- a/crates/hir/src/semantics.rs
+++ b/crates/hir/src/semantics.rs
@@ -481,7 +481,7 @@ impl<'db> SemanticsImpl<'db> {
)
}
- // This might not be the correct way to due this, but it works for now
+ // This might not be the correct way to do this, but it works for now
fn descend_node_into_attributes<N: AstNode>(&self, node: N) -> SmallVec<[N; 1]> {
let mut res = smallvec![];
let tokens = (|| {
@@ -682,20 +682,7 @@ impl<'db> SemanticsImpl<'db> {
fn resolve_lifetime_param(&self, lifetime: &ast::Lifetime) -> Option<LifetimeParam> {
let text = lifetime.text();
let lifetime_param = lifetime.syntax().ancestors().find_map(|syn| {
- let gpl = match_ast! {
- match syn {
- ast::Fn(it) => it.generic_param_list()?,
- ast::TypeAlias(it) => it.generic_param_list()?,
- ast::Struct(it) => it.generic_param_list()?,
- ast::Enum(it) => it.generic_param_list()?,
- ast::Union(it) => it.generic_param_list()?,
- ast::Trait(it) => it.generic_param_list()?,
- ast::Impl(it) => it.generic_param_list()?,
- ast::WherePred(it) => it.generic_param_list()?,
- ast::ForType(it) => it.generic_param_list()?,
- _ => return None,
- }
- };
+ let gpl = ast::DynGenericParamsOwner::cast(syn)?.generic_param_list()?;
gpl.lifetime_params()
.find(|tp| tp.lifetime().as_ref().map(|lt| lt.text()).as_ref() == Some(&text))
})?;
diff --git a/crates/hir_def/src/attr.rs b/crates/hir_def/src/attr.rs
index f8cc940486..0470b9510f 100644
--- a/crates/hir_def/src/attr.rs
+++ b/crates/hir_def/src/attr.rs
@@ -411,47 +411,47 @@ impl AttrsWithOwner {
let file_id = id.parent.file_id(db);
let root = db.parse_or_expand(file_id).unwrap();
let owner = match &map[id.local_id] {
- Either::Left(it) => ast::AttrsOwnerNode::new(it.to_node(&root)),
- Either::Right(it) => ast::AttrsOwnerNode::new(it.to_node(&root)),
+ Either::Left(it) => ast::DynAttrsOwner::new(it.to_node(&root)),
+ Either::Right(it) => ast::DynAttrsOwner::new(it.to_node(&root)),
};
InFile::new(file_id, owner)
}
AttrDefId::AdtId(adt) => match adt {
- AdtId::StructId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
- AdtId::UnionId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
- AdtId::EnumId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
+ AdtId::StructId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
+ AdtId::UnionId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
+ AdtId::EnumId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
},
- AttrDefId::FunctionId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
+ AttrDefId::FunctionId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
AttrDefId::EnumVariantId(id) => {
let map = db.variants_attrs_source_map(id.parent);
let file_id = id.parent.lookup(db).id.file_id();
let root = db.parse_or_expand(file_id).unwrap();
- InFile::new(file_id, ast::AttrsOwnerNode::new(map[id.local_id].to_node(&root)))
+ InFile::new(file_id, ast::DynAttrsOwner::new(map[id.local_id].to_node(&root)))
}
- AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
- AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
- AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
- AttrDefId::TypeAliasId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
+ AttrDefId::StaticId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
+ AttrDefId::ConstId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
+ AttrDefId::TraitId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
+ AttrDefId::TypeAliasId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
AttrDefId::MacroDefId(id) => id.ast_id().either(
- |it| it.with_value(ast::AttrsOwnerNode::new(it.to_node(db.upcast()))),
- |it| it.with_value(ast::AttrsOwnerNode::new(it.to_node(db.upcast()))),
+ |it| it.with_value(ast::DynAttrsOwner::new(it.to_node(db.upcast()))),
+ |it| it.with_value(ast::DynAttrsOwner::new(it.to_node(db.upcast()))),
),
- AttrDefId::ImplId(id) => id.lookup(db).source(db).map(ast::AttrsOwnerNode::new),
+ AttrDefId::ImplId(id) => id.lookup(db).source(db).map(ast::DynAttrsOwner::new),
AttrDefId::GenericParamId(id) => match id {
GenericParamId::TypeParamId(id) => {
id.parent.child_source(db).map(|source| match &source[id.local_id] {
- Either::Left(id) => ast::AttrsOwnerNode::new(id.clone()),
- Either::Right(id) => ast::AttrsOwnerNode::new(id.clone()),
+ Either::Left(id) => ast::DynAttrsOwner::new(id.clone()),
+ Either::Right(id) => ast::DynAttrsOwner::new(id.clone()),
})
}
GenericParamId::LifetimeParamId(id) => id
.parent
.child_source(db)
- .map(|source| ast::AttrsOwnerNode::new(source[id.local_id].clone())),
+ .map(|source| ast::DynAttrsOwner::new(source[id.local_id].clone())),
GenericParamId::ConstParamId(id) => id
.parent
.child_source(db)
- .map(|source| ast::AttrsOwnerNode::new(source[id.local_id].clone())),
+ .map(|source| ast::DynAttrsOwner::new(source[id.local_id].clone())),
},
};
diff --git a/crates/syntax/src/ast.rs b/crates/syntax/src/ast.rs
index e26c5b7ad9..7c9200f568 100644
--- a/crates/syntax/src/ast.rs
+++ b/crates/syntax/src/ast.rs
@@ -21,8 +21,8 @@ pub use self::{
expr_ext::{ArrayExprKind, Effect, ElseBranch, LiteralKind},
generated::{nodes::*, tokens::*},
node_ext::{
- AttrKind, AttrsOwnerNode, FieldKind, Macro, NameLike, NameOrNameRef, PathSegmentKind,
- SelfParamKind, SlicePatComponents, StructKind, TypeBoundKind, VisibilityKind,
+ AttrKind, FieldKind, Macro, NameLike, NameOrNameRef, PathSegmentKind, SelfParamKind,
+ SlicePatComponents, StructKind, TypeBoundKind, VisibilityKind,
},
operators::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, UnaryOp},
token_ext::{
diff --git a/crates/syntax/src/ast/generated/nodes.rs b/crates/syntax/src/ast/generated/nodes.rs
index 205d01e974..547e754636 100644
--- a/crates/syntax/src/ast/generated/nodes.rs
+++ b/crates/syntax/src/ast/generated/nodes.rs
@@ -1445,6 +1445,46 @@ pub enum GenericParam {
TypeParam(TypeParam),
}
impl ast::AttrsOwner for GenericParam {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynArgListOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::ArgListOwner for DynArgListOwner {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynAttrsOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::AttrsOwner for DynAttrsOwner {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynGenericParamsOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::GenericParamsOwner for DynGenericParamsOwner {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynLoopBodyOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::LoopBodyOwner for DynLoopBodyOwner {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynModuleItemOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::ModuleItemOwner for DynModuleItemOwner {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynNameOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::NameOwner for DynNameOwner {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynTypeBoundsOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::TypeBoundsOwner for DynTypeBoundsOwner {}
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct DynVisibilityOwner {
+ pub(crate) syntax: SyntaxNode,
+}
+impl ast::VisibilityOwner for DynVisibilityOwner {}
impl AstNode for Name {
fn can_cast(kind: SyntaxKind) -> bool { kind == NAME }
fn cast(syntax: SyntaxNode) -> Option<Self> {
@@ -3564,6 +3604,219 @@ impl AstNode for GenericParam {
}
}
}
+impl DynArgListOwner {
+ #[inline]
+ pub fn new<T: ast::ArgListOwner>(node: T) -> DynArgListOwner {
+ DynArgListOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynArgListOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ CALL_EXPR | METHOD_CALL_EXPR => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynArgListOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl DynAttrsOwner {
+ #[inline]
+ pub fn new<T: ast::AttrsOwner>(node: T) -> DynAttrsOwner {
+ DynAttrsOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynAttrsOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ MACRO_CALL
+ | SOURCE_FILE
+ | CONST
+ | ENUM
+ | EXTERN_BLOCK
+ | EXTERN_CRATE
+ | FN
+ | IMPL
+ | MACRO_RULES
+ | MACRO_DEF
+ | MODULE
+ | STATIC
+ | STRUCT
+ | TRAIT
+ | TYPE_ALIAS
+ | UNION
+ | USE
+ | ITEM_LIST
+ | BLOCK_EXPR
+ | SELF_PARAM
+ | PARAM
+ | RECORD_FIELD
+ | TUPLE_FIELD
+ | VARIANT
+ | ASSOC_ITEM_LIST
+ | EXTERN_ITEM_LIST
+ | CONST_PARAM
+ | LIFETIME_PARAM
+ | TYPE_PARAM
+ | EXPR_STMT
+ | LET_STMT
+ | ARRAY_EXPR
+ | AWAIT_EXPR
+ | BIN_EXPR
+ | BOX_EXPR
+ | BREAK_EXPR
+ | CALL_EXPR
+ | CAST_EXPR
+ | CLOSURE_EXPR
+ | CONTINUE_EXPR
+ | EFFECT_EXPR
+ | FIELD_EXPR
+ | FOR_EXPR
+ | IF_EXPR
+ | INDEX_EXPR
+ | LITERAL
+ | LOOP_EXPR
+ | MATCH_EXPR
+ | METHOD_CALL_EXPR
+ | PAREN_EXPR
+ | PATH_EXPR
+ | PREFIX_EXPR
+ | RANGE_EXPR
+ | REF_EXPR
+ | RETURN_EXPR
+ | TRY_EXPR
+ | TUPLE_EXPR
+ | WHILE_EXPR
+ | YIELD_EXPR
+ | RECORD_EXPR_FIELD_LIST
+ | RECORD_EXPR_FIELD
+ | MATCH_ARM_LIST
+ | MATCH_ARM
+ | IDENT_PAT
+ | RECORD_PAT_FIELD => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynAttrsOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl DynGenericParamsOwner {
+ #[inline]
+ pub fn new<T: ast::GenericParamsOwner>(node: T) -> DynGenericParamsOwner {
+ DynGenericParamsOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynGenericParamsOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ ENUM | FN | IMPL | STRUCT | TRAIT | TYPE_ALIAS | UNION => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynGenericParamsOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl DynLoopBodyOwner {
+ #[inline]
+ pub fn new<T: ast::LoopBodyOwner>(node: T) -> DynLoopBodyOwner {
+ DynLoopBodyOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynLoopBodyOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ FOR_EXPR | LOOP_EXPR | WHILE_EXPR => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynLoopBodyOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl DynModuleItemOwner {
+ #[inline]
+ pub fn new<T: ast::ModuleItemOwner>(node: T) -> DynModuleItemOwner {
+ DynModuleItemOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynModuleItemOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ MACRO_ITEMS | SOURCE_FILE | ITEM_LIST => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynModuleItemOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl DynNameOwner {
+ #[inline]
+ pub fn new<T: ast::NameOwner>(node: T) -> DynNameOwner {
+ DynNameOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynNameOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ CONST | ENUM | FN | MACRO_RULES | MACRO_DEF | MODULE | STATIC | STRUCT | TRAIT
+ | TYPE_ALIAS | UNION | RENAME | SELF_PARAM | RECORD_FIELD | VARIANT | CONST_PARAM
+ | TYPE_PARAM | IDENT_PAT => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynNameOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl DynTypeBoundsOwner {
+ #[inline]
+ pub fn new<T: ast::TypeBoundsOwner>(node: T) -> DynTypeBoundsOwner {
+ DynTypeBoundsOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynTypeBoundsOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ ASSOC_TYPE_ARG | TRAIT | TYPE_ALIAS | LIFETIME_PARAM | TYPE_PARAM | WHERE_PRED => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynTypeBoundsOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
+impl DynVisibilityOwner {
+ #[inline]
+ pub fn new<T: ast::VisibilityOwner>(node: T) -> DynVisibilityOwner {
+ DynVisibilityOwner { syntax: node.syntax().clone() }
+ }
+}
+impl AstNode for DynVisibilityOwner {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ CONST | ENUM | EXTERN_CRATE | FN | IMPL | MACRO_RULES | MACRO_DEF | MODULE | STATIC
+ | STRUCT | TRAIT | TYPE_ALIAS | UNION | USE | RECORD_FIELD | TUPLE_FIELD | VARIANT => {
+ true
+ }
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| DynVisibilityOwner { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode { &self.syntax }
+}
impl std::fmt::Display for GenericArg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self.syntax(), f)
diff --git a/crates/syntax/src/ast/node_ext.rs b/crates/syntax/src/ast/node_ext.rs
index 99ef5c264f..930e5ce74f 100644
--- a/crates/syntax/src/ast/node_ext.rs
+++ b/crates/syntax/src/ast/node_ext.rs
@@ -167,36 +167,6 @@ impl NameOwner for Macro {
impl AttrsOwner for Macro {}
-/// Basically an owned `dyn AttrsOwner` without extra boxing.
-pub struct AttrsOwnerNode {
- node: SyntaxNode,
-}
-
-impl AttrsOwnerNode {
- pub fn new<N: AttrsOwner>(node: N) -> Self {
- AttrsOwnerNode { node: node.syntax().clone() }
- }
-}
-
-impl AttrsOwner for AttrsOwnerNode {}
-impl AstNode for AttrsOwnerNode {
- fn can_cast(_: SyntaxKind) -> bool
- where
- Self: Sized,
- {
- false
- }
- fn cast(_: SyntaxNode) -> Option<Self>
- where
- Self: Sized,
- {
- None
- }
- fn syntax(&self) -> &SyntaxNode {
- &self.node
- }
-}
-
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AttrKind {
Inner,
@@ -602,16 +572,7 @@ impl ast::Variant {
impl ast::Item {
pub fn generic_param_list(&self) -> Option<ast::GenericParamList> {
- match self {
- ast::Item::Enum(it) => it.generic_param_list(),
- ast::Item::Fn(it) => it.generic_param_list(),
- ast::Item::Impl(it) => it.generic_param_list(),
- ast::Item::Struct(it) => it.generic_param_list(),
- ast::Item::Trait(it) => it.generic_param_list(),
- ast::Item::TypeAlias(it) => it.generic_param_list(),
- ast::Item::Union(it) => it.generic_param_list(),
- _ => None,
- }
+ ast::DynGenericParamsOwner::cast(self.syntax().clone())?.generic_param_list()
}
}
diff --git a/crates/syntax/src/tests/sourcegen_ast.rs b/crates/syntax/src/tests/sourcegen_ast.rs
index a6624a8ab1..1a0377830b 100644
--- a/crates/syntax/src/tests/sourcegen_ast.rs
+++ b/crates/syntax/src/tests/sourcegen_ast.rs
@@ -8,6 +8,7 @@ use std::{
fmt::Write,
};
+use itertools::Itertools;
use proc_macro2::{Punct, Spacing};
use quote::{format_ident, quote};
use ungrammar::{rust_grammar, Grammar, Rule};
@@ -208,6 +209,58 @@ fn generate_nodes(kinds: KindsSrc<'_>, grammar: &AstSrc) -> String {
})
.unzip();
+ let (dyn_node_defs, dyn_node_boilerplate_impls): (Vec<_>, Vec<_>) = grammar
+ .nodes
+ .iter()
+ .flat_map(|node| node.traits.iter().map(move |t| (t, node)))
+ .into_group_map()
+ .into_iter()
+ .sorted_by_key(|(k, _)| k.clone())
+ .map(|(trait_name, nodes)| {
+ let name = format_ident!("Dyn{}", trait_name);
+ let trait_name = format_ident!("{}", trait_name);
+ let kinds: Vec<_> = nodes
+ .iter()
+ .map(|name| format_ident!("{}", to_upper_snake_case(&name.name.to_string())))
+ .collect();
+
+ (
+ quote! {
+ #[pretty_doc_comment_placeholder_workaround]
+ #[derive(Debug, Clone, PartialEq, Eq, Hash)]
+ pub struct #name {
+ pub(crate) syntax: SyntaxNode,
+ }
+ impl ast::#trait_name for #name {}
+ },
+ quote! {
+ impl #name {
+ #[inline]
+ pub fn new<T: ast::#trait_name>(node: T) -> #name {
+ #name {
+ syntax: node.syntax().clone()
+ }
+ }
+ }
+ impl AstNode for #name {
+ fn can_cast(kind: SyntaxKind) -> bool {
+ match kind {
+ #(#kinds)|* => true,
+ _ => false,
+ }
+ }
+ fn cast(syntax: SyntaxNode) -> Option<Self> {
+ Self::can_cast(syntax.kind()).then(|| #name { syntax })
+ }
+ fn syntax(&self) -> &SyntaxNode {
+ &self.syntax
+ }
+ }
+ },
+ )
+ })
+ .unzip();
+
let enum_names = grammar.enums.iter().map(|it| &it.name);
let node_names = grammar.nodes.iter().map(|it| &it.name);
@@ -244,8 +297,10 @@ fn generate_nodes(kinds: KindsSrc<'_>, grammar: &AstSrc) -> String {
#(#node_defs)*
#(#enum_defs)*
+ #(#dyn_node_defs)*
#(#node_boilerplate_impls)*
#(#enum_boilerplate_impls)*
+ #(#dyn_node_boilerplate_impls)*
#(#display_impls)*
};