use itertools::Itertools;
use ra_ap_syntax::{ast, AstNode, SyntaxKind, SyntaxNode};
use super::ast_ext;
use crate::iter::IterSuccessors;
use crate::traits::{HasInkImplParent, InkEntity};
use crate::{Constructor, InkArg, InkArgKind, InkAttribute, InkAttributeKind, InkImpl, Message};
pub fn attrs(node: &SyntaxNode) -> impl Iterator<Item = ast::Attr> {
node.children().filter_map(ast::Attr::cast)
}
pub fn ink_attrs(node: &SyntaxNode) -> impl Iterator<Item = InkAttribute> {
attrs(node).filter_map(InkAttribute::cast)
}
pub fn ink_attrs_descendants(node: &SyntaxNode) -> impl Iterator<Item = InkAttribute> {
node.children()
.filter(|child| child.kind() != SyntaxKind::ATTR)
.flat_map(|child| {
child
.descendants()
.filter_map(|node| ast::Attr::cast(node).and_then(InkAttribute::cast))
})
}
pub fn ink_attrs_closest_descendants(node: &SyntaxNode) -> impl Iterator<Item = InkAttribute> {
node.children().flat_map(|child| {
if ink_attrs(&child).next().is_some() {
Box::new(ink_attrs(&child)) as Box<dyn Iterator<Item = InkAttribute>>
} else {
Box::new(ink_attrs_closest_descendants(&child))
}
})
}
pub fn ink_attrs_in_scope(node: &SyntaxNode) -> impl Iterator<Item = InkAttribute> {
ink_attrs(node).chain(
ink_attrs_descendants(node),
)
}
pub fn ink_attrs_ancestors(node: &SyntaxNode) -> impl Iterator<Item = InkAttribute> + '_ {
node.ancestors()
.filter(move |ancestor| {
ancestor != node
&& (node.kind() != SyntaxKind::ATTR
|| node.parent().as_ref().map(SyntaxNode::text_range)
!= Some(ancestor.text_range()))
})
.flat_map(|ancestor| ink_attrs(&ancestor))
}
pub fn ink_attrs_closest_ancestors(node: &SyntaxNode) -> impl Iterator<Item = InkAttribute> {
IterSuccessors::new(
if node.kind() == SyntaxKind::ATTR {
node.parent().as_ref().and_then(SyntaxNode::parent)
} else {
node.parent()
},
|source| {
source.as_ref().map(|current_node| {
if ink_attrs(current_node).next().is_some() {
(Some(ink_attrs(current_node)), None)
} else {
(None, current_node.parent())
}
})
},
)
}
pub fn ink_args(node: &SyntaxNode) -> impl Iterator<Item = InkArg> {
ink_attrs(node).flat_map(|attr| attr.args().to_owned())
}
pub fn ink_args_by_kind(node: &SyntaxNode, kind: InkArgKind) -> impl Iterator<Item = InkArg> {
ink_attrs(node)
.filter_map(move |attr| attr.args().iter().find(|arg| *arg.kind() == kind).cloned())
}
pub fn ink_arg_by_kind(node: &SyntaxNode, kind: InkArgKind) -> Option<InkArg> {
ink_attrs(node).find_map(|attr| attr.args().iter().find(|arg| *arg.kind() == kind).cloned())
}
pub fn ink_attr_to_entity<T>(attr: InkAttribute) -> Option<T>
where
T: InkEntity,
{
T::cast(attr.syntax().clone())
}
pub fn ink_descendants<T>(node: &SyntaxNode) -> impl Iterator<Item = T>
where
T: InkEntity,
{
ink_attrs_descendants(node).filter_map(ink_attr_to_entity)
}
pub fn ink_closest_descendants<T>(node: &SyntaxNode) -> impl Iterator<Item = T>
where
T: InkEntity,
{
ink_attrs_closest_descendants(node).filter_map(ink_attr_to_entity)
}
pub fn ink_parent<T>(node: &SyntaxNode) -> Option<T>
where
T: InkEntity,
{
ast_ext::parent_ast_item(node)
.and_then(|parent| ink_attrs(parent.syntax()).find_map(ink_attr_to_entity))
}
pub fn ink_ancestors<'a, T>(node: &'a SyntaxNode) -> impl Iterator<Item = T> + 'a
where
T: InkEntity + 'a,
{
ink_attrs_ancestors(node).filter_map(ink_attr_to_entity)
}
pub fn ink_closest_ancestors<T>(node: &SyntaxNode) -> impl Iterator<Item = T>
where
T: InkEntity,
{
ink_attrs_closest_ancestors(node).filter_map(ink_attr_to_entity)
}
pub fn ink_callable_closest_descendants<T>(node: &SyntaxNode) -> impl Iterator<Item = T>
where
T: HasInkImplParent,
{
ink_peekable_quasi_closest_descendants(node, is_possible_callable_ancestor)
}
pub fn ink_impl_closest_descendants(node: &SyntaxNode) -> impl Iterator<Item = InkImpl> {
node.children()
.filter_map(ast::Impl::cast)
.map(|item| item.syntax().clone())
.chain(ink_attrs_closest_descendants(node).filter_map(|attr| {
if is_possible_callable_ancestor(&attr) {
ast_ext::parent_ast_item(attr.syntax()).map(|item| item.syntax().clone())
} else if Constructor::can_cast(attr.syntax()) {
Constructor::cast(attr.syntax().clone())
.expect("Should be able to cast")
.parent_impl_item()
.map(|item| item.syntax().clone())
} else if Message::can_cast(attr.syntax()) {
Message::cast(attr.syntax().clone())
.expect("Should be able to cast")
.parent_impl_item()
.map(|item| item.syntax().clone())
} else {
None
}
}))
.filter_map(InkImpl::cast)
.unique_by(|item| item.syntax().clone())
}
fn is_possible_callable_ancestor(attr: &InkAttribute) -> bool {
*attr.kind() == InkAttributeKind::Arg(InkArgKind::Impl)
|| ((*attr.kind() == InkAttributeKind::Arg(InkArgKind::Namespace)
|| attr.kind().is_unknown())
&& ast_ext::parent_ast_item(attr.syntax())
.is_some_and(|item| matches!(item, ast::Item::Impl(_))))
}
pub fn ink_peekable_quasi_closest_descendants<T, F>(
node: &SyntaxNode,
is_peekable_ancestor: F,
) -> impl Iterator<Item = T>
where
T: InkEntity,
F: Fn(&InkAttribute) -> bool,
{
ink_attrs_closest_descendants(node)
.flat_map(move |attr| {
if T::can_cast(attr.syntax()) {
return vec![T::cast(attr.syntax().clone()).expect("Should be able to cast")];
}
if is_peekable_ancestor(&attr) {
if let Some(item) = attr.syntax().parent() {
return ink_attrs_closest_descendants(&item)
.filter_map(ink_attr_to_entity)
.collect();
}
}
Vec::new()
})
.unique_by(|item| item.syntax().clone())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::*;
use test_utils::quote_as_str;
#[test]
fn ink_attrs_works() {
for (node, n_attrs) in [
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
mod my_contract {}
})
.syntax(),
0,
),
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
#[ink::contract]
mod my_contract {}
})
.syntax(),
1,
),
(
parse_first_ast_node_of_type::<ast::Fn>(quote_as_str! {
#[ink(message)]
#[ink(payable, default, selector=1)]
pub fn my_message(&self) {}
})
.syntax(),
2,
),
] {
assert_eq!(ink_attrs(node).count(), n_attrs);
}
}
#[test]
fn ink_attrs_descendants_works() {
for (node, n_attrs) in [
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
mod my_contract {}
})
.syntax(),
0,
),
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
#[ink::contract]
mod my_contract {}
})
.syntax(),
0, ),
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
#[ink::contract]
mod my_contract {
#[ink(event)]
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
impl MyContract {
#[ink(message)]
#[ink(payable, default, selector=1)]
pub fn my_message(&self) {}
}
}
})
.syntax(),
4, ),
] {
assert_eq!(ink_attrs_descendants(node).count(), n_attrs);
}
}
#[test]
fn ink_attrs_closest_descendants_works() {
for (node, n_attrs) in [
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
mod my_contract {}
})
.syntax(),
0,
),
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
#[ink::contract]
mod my_contract {}
})
.syntax(),
0, ),
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
#[ink::contract]
mod my_contract {
#[ink(event)]
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
impl MyContract {
#[ink(message)]
pub fn my_message(&self) {}
}
}
})
.syntax(),
2, ),
] {
assert_eq!(ink_attrs_closest_descendants(node).count(), n_attrs);
}
}
#[test]
fn ink_attrs_in_scope_works() {
for (node, n_attrs) in [
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
mod my_contract {}
})
.syntax(),
0,
),
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
#[ink::contract]
mod my_contract {}
})
.syntax(),
1, ),
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
#[ink::contract]
mod my_contract {
#[ink(event)]
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
impl MyContract {
#[ink(message)]
pub fn my_message(&self) {}
}
}
})
.syntax(),
4, ),
] {
assert_eq!(ink_attrs_in_scope(node).count(), n_attrs);
}
}
#[test]
fn ink_attrs_ancestors_works() {
for (node, n_attrs) in [
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
mod my_contract {}
})
.syntax(),
0,
),
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
#[ink::contract]
mod my_contract {}
})
.syntax(),
0, ),
(
parse_first_ast_node_of_type::<ast::RecordField>(quote_as_str! {
#[ink::contract]
mod my_contract {
#[ink(event)]
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
}
})
.syntax(),
2, ),
] {
assert_eq!(ink_attrs_ancestors(node).count(), n_attrs);
}
}
#[test]
fn ink_attrs_closest_ancestors_works() {
for (node, n_attrs) in [
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
mod my_contract {}
})
.syntax(),
0,
),
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
#[ink::contract]
mod my_contract {}
})
.syntax(),
0, ),
(
parse_first_ast_node_of_type::<ast::RecordField>(quote_as_str! {
#[ink::contract]
mod my_contract {
#[ink(event)]
pub struct MyEvent {
#[ink(topic)]
field_1: i32,
field_2: bool,
}
}
})
.syntax(),
1, ),
] {
assert_eq!(ink_attrs_closest_ancestors(node).count(), n_attrs);
}
}
#[test]
fn ink_args_works() {
for (node, n_args) in [
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
mod my_contract {}
})
.syntax(),
0,
),
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
#[ink::contract]
mod my_contract {}
})
.syntax(),
0, ),
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
#[ink::contract(env=my::env::Types, keep_attr="foo,bar")]
mod my_contract {}
})
.syntax(),
2, ),
(
parse_first_ast_node_of_type::<ast::Fn>(quote_as_str! {
#[ink(message)]
#[ink(payable, default, selector=1)]
pub fn my_message(&self) {}
})
.syntax(),
4, ),
] {
assert_eq!(ink_args(node).count(), n_args);
}
}
#[test]
fn ink_arg_by_kind_works() {
for (node, arg_kind, is_expected) in [
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
mod my_contract {}
})
.syntax(),
InkArgKind::Env,
false,
),
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
#[ink::contract]
mod my_contract {}
})
.syntax(),
InkArgKind::Env,
false,
),
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
#[ink::contract(env=my::env::Types, keep_attr="foo,bar")]
mod my_contract {}
})
.syntax(),
InkArgKind::Env,
true,
),
(
parse_first_ast_node_of_type::<ast::Module>(quote_as_str! {
#[ink::contract(env=my::env::Types, keep_attr="foo,bar")]
mod my_contract {}
})
.syntax(),
InkArgKind::Namespace,
false,
),
(
parse_first_ast_node_of_type::<ast::Fn>(quote_as_str! {
#[ink(message)]
#[ink(payable, default, selector=1)]
pub fn my_message(&self) {}
})
.syntax(),
InkArgKind::Message,
true,
),
(
parse_first_ast_node_of_type::<ast::Fn>(quote_as_str! {
#[ink(message)]
#[ink(payable, default, selector=1)]
pub fn my_message(&self) {}
})
.syntax(),
InkArgKind::Selector,
true,
),
(
parse_first_ast_node_of_type::<ast::Fn>(quote_as_str! {
#[ink(message)]
#[ink(payable, default, selector=1)]
pub fn my_message(&self) {}
})
.syntax(),
InkArgKind::Constructor,
false,
),
] {
assert_eq!(ink_arg_by_kind(node, arg_kind).is_some(), is_expected);
}
}
#[test]
fn ink_callable_closest_descendants_works() {
for (code, n_constructors, n_messages) in [
(
quote_as_str! {
#[ink::contract]
mod my_contract {}
},
0,
0,
),
(
quote_as_str! {
#[ink::contract]
mod my_contract {
impl MyContract {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
}
}
},
1,
0,
),
(
quote_as_str! {
#[ink::contract]
mod my_contract {
impl MyContract {
#[ink(message)]
pub fn my_message(&self) {}
}
}
},
0,
1,
),
(
quote_as_str! {
#[ink::contract]
mod my_contract {
impl MyContract {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message)]
pub fn my_message(&self) {}
}
impl Mytrait for MyContract {
#[ink(constructor)]
pub fn my_constructor2() -> Self {}
#[ink(message)]
pub fn my_message2(&self) {}
}
}
},
2,
2,
),
(
quote_as_str! {
#[ink::contract]
mod my_contract {
#[ink(namespace="my_namespace")]
impl MyContract {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message)]
pub fn my_message(&self) {}
}
}
},
1,
1,
),
(
quote_as_str! {
#[ink::contract]
mod my_contract {
#[ink(impl)]
impl MyContract {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message)]
pub fn my_message(&self) {}
}
}
},
1,
1,
),
(
quote_as_str! {
#[ink::contract]
mod my_contract {
#[ink(impl, namespace="my_namespace")]
impl MyContract {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message)]
pub fn my_message(&self) {}
}
}
},
1,
1,
),
(
quote_as_str! {
#[ink::contract]
mod my_contract {
#[ink::test]
fn it_works() {
impl MyContract {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message)]
pub fn my_message(&self) {}
}
}
}
},
0,
0,
),
] {
let module: ast::Module = parse_first_ast_node_of_type(code);
assert_eq!(
ink_callable_closest_descendants::<Constructor>(module.syntax()).count(),
n_constructors,
"constructor: {code}"
);
assert_eq!(
ink_callable_closest_descendants::<Message>(module.syntax()).count(),
n_messages,
"message: {code}"
);
}
}
#[test]
fn ink_impl_closest_descendants_works() {
for (code, n_impls) in [
(
quote_as_str! {
#[ink::contract]
mod my_contract {}
},
0,
),
(
quote_as_str! {
#[ink::contract]
mod my_contract {
impl MyContract {
}
}
},
0, ),
(
quote_as_str! {
#[ink::contract]
mod my_contract {
impl MyContract {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
}
}
},
1,
),
(
quote_as_str! {
#[ink::contract]
mod my_contract {
impl MyContract {
#[ink(message)]
pub fn my_message(&self) {}
}
}
},
1,
),
(
quote_as_str! {
#[ink::contract]
mod my_contract {
impl MyContract {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message)]
pub fn my_message(&self) {}
}
impl Mytrait for MyContract {
#[ink(constructor)]
pub fn my_constructor2() -> Self {}
#[ink(message)]
pub fn my_message2(&self) {}
}
}
},
2,
),
(
quote_as_str! {
#[ink::contract]
mod my_contract {
#[ink(namespace="my_namespace")]
impl MyContract {
#[ink(constructor)]
pub fn my_constructor() -> Self {}
#[ink(message)]
pub fn my_message(&self) {}
}
}
},
1,
),
(
quote_as_str! {
#[ink::contract]
mod my_contract {
#[ink(impl)]
impl MyContract {
}
}
},
1, ),
(
quote_as_str! {
#[ink::contract]
mod my_contract {
#[ink(impl, namespace="my_namespace")]
impl MyContract {
}
}
},
1, ),
] {
let module: ast::Module = parse_first_ast_node_of_type(code);
assert_eq!(
ink_impl_closest_descendants(module.syntax()).count(),
n_impls,
"impls: {code}"
);
}
}
}