use std::cmp::Ordering;
use std::fmt;
use ra_ap_syntax::{ast, AstToken, TextRange};
use super::meta::{MetaName, MetaNameValue, MetaOption, MetaValue};
use crate::Version;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InkArg {
kind: InkArgKind,
meta: MetaNameValue,
}
impl From<MetaNameValue> for InkArg {
fn from(meta: MetaNameValue) -> Self {
Self {
kind: if let MetaOption::Ok(name) = meta.name() {
InkArgKind::from(name.text())
} else {
InkArgKind::Unknown
},
meta,
}
}
}
impl InkArg {
pub fn kind(&self) -> &InkArgKind {
&self.kind
}
pub fn meta(&self) -> &MetaNameValue {
&self.meta
}
pub fn text_range(&self) -> TextRange {
self.meta.text_range()
}
pub fn name(&self) -> Option<&MetaName> {
self.meta.name().result().ok()
}
pub fn value(&self) -> Option<&MetaValue> {
self.meta.value().result().ok()
}
pub fn nested(&self) -> Option<InkArg> {
self.meta.nested().map(|meta| InkArg::from(meta.to_owned()))
}
pub fn parent_attr(&self) -> Option<ast::Attr> {
self.meta.parent_attr()
}
}
impl fmt::Display for InkArg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.meta.fmt(f)
}
}
impl Ord for InkArg {
fn cmp(&self, other: &Self) -> Ordering {
Ord::cmp(self.kind(), other.kind())
}
}
impl PartialOrd for InkArg {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum InkArgKind {
Abi,
AdditionalContracts,
Anonymous,
Backend,
Constructor,
Decode,
Default,
Derive,
Encode,
Env,
Environment,
Event,
Extension,
Function,
HandleStatus,
Impl,
KeepAttr,
Message,
Name,
Namespace,
Node,
Packed,
Payable,
RuntimeOnly,
Sandbox,
Selector,
SignatureTopic,
Storage,
Topic,
TypeInfo,
Url,
Unknown,
}
impl From<&str> for InkArgKind {
fn from(arg_name: &str) -> Self {
match arg_name {
"abi" => InkArgKind::Abi,
"additional_contracts" => InkArgKind::AdditionalContracts,
"anonymous" => InkArgKind::Anonymous,
"backend" => InkArgKind::Backend,
"constructor" => InkArgKind::Constructor,
"Decode" => InkArgKind::Decode,
"default" => InkArgKind::Default,
"derive" => InkArgKind::Derive,
"Encode" => InkArgKind::Encode,
"env" => InkArgKind::Env,
"environment" => InkArgKind::Environment,
"event" => InkArgKind::Event,
"extension" => InkArgKind::Extension,
"function" => InkArgKind::Function,
"handle_status" => InkArgKind::HandleStatus,
"impl" => InkArgKind::Impl,
"keep_attr" => InkArgKind::KeepAttr,
"message" => InkArgKind::Message,
"name" => InkArgKind::Name,
"namespace" => InkArgKind::Namespace,
"node" => InkArgKind::Node,
"packed" => InkArgKind::Packed,
"payable" => InkArgKind::Payable,
"runtime_only" => InkArgKind::RuntimeOnly,
"sandbox" => InkArgKind::Sandbox,
"selector" => InkArgKind::Selector,
"signature_topic" => InkArgKind::SignatureTopic,
"storage" => InkArgKind::Storage,
"topic" => InkArgKind::Topic,
"TypeInfo" => InkArgKind::TypeInfo,
"url" => InkArgKind::Url,
_ => InkArgKind::Unknown,
}
}
}
impl fmt::Display for InkArgKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
InkArgKind::Abi => "abi",
InkArgKind::AdditionalContracts => "additional_contracts",
InkArgKind::Anonymous => "anonymous",
InkArgKind::Backend => "backend",
InkArgKind::Constructor => "constructor",
InkArgKind::Decode => "Decode",
InkArgKind::Default => "default",
InkArgKind::Derive => "derive",
InkArgKind::Encode => "Encode",
InkArgKind::Env => "env",
InkArgKind::Environment => "environment",
InkArgKind::Event => "event",
InkArgKind::Extension => "extension",
InkArgKind::Function => "function",
InkArgKind::HandleStatus => "handle_status",
InkArgKind::Impl => "impl",
InkArgKind::KeepAttr => "keep_attr",
InkArgKind::Message => "message",
InkArgKind::Name => "name",
InkArgKind::Namespace => "namespace",
InkArgKind::Node => "node",
InkArgKind::Packed => "packed",
InkArgKind::Payable => "payable",
InkArgKind::RuntimeOnly => "runtime_only",
InkArgKind::Sandbox => "sandbox",
InkArgKind::Selector => "selector",
InkArgKind::SignatureTopic => "signature_topic",
InkArgKind::Storage => "storage",
InkArgKind::Topic => "topic",
InkArgKind::TypeInfo => "TypeInfo",
InkArgKind::Url => "url",
InkArgKind::Unknown => "unknown",
}
)
}
}
fn ink_arg_kind_sort_order(arg_kind: InkArgKind) -> u8 {
match arg_kind {
InkArgKind::Constructor
| InkArgKind::Event
| InkArgKind::Extension | InkArgKind::Function | InkArgKind::Impl
| InkArgKind::Message
| InkArgKind::Storage
| InkArgKind::Topic => 0,
InkArgKind::Abi
| InkArgKind::AdditionalContracts
| InkArgKind::Anonymous
| InkArgKind::Backend
| InkArgKind::Decode
| InkArgKind::Default
| InkArgKind::Derive
| InkArgKind::Encode
| InkArgKind::Env
| InkArgKind::Environment
| InkArgKind::HandleStatus
| InkArgKind::KeepAttr
| InkArgKind::Name
| InkArgKind::Namespace
| InkArgKind::Node
| InkArgKind::Packed
| InkArgKind::Payable
| InkArgKind::RuntimeOnly
| InkArgKind::Sandbox
| InkArgKind::Selector
| InkArgKind::SignatureTopic
| InkArgKind::TypeInfo
| InkArgKind::Url => 1,
InkArgKind::Unknown => 10,
}
}
impl InkArgKind {
pub fn is_entity_type(&self) -> bool {
ink_arg_kind_sort_order(*self) == 0
}
pub fn is_complementary(&self) -> bool {
ink_arg_kind_sort_order(*self) == 1
}
pub fn detail(&self, version: Version) -> &str {
const CHAIN_EXT_DEPRECATION_NOTICE: &str = "ink! chain extensions are deprecated. See https://github.com/use-ink/ink/pull/2621 for details.";
match self {
InkArgKind::Abi if version.is_gte_v6() => "Specifies the ABI (Application Binary Interface) of the target.",
InkArgKind::AdditionalContracts if version.is_legacy() => "Tells the ink! e2e test runner which additional contracts to build before executing the test.",
InkArgKind::AdditionalContracts => "ink! attribute argument `additional_contracts` is deprecated. See https://github.com/paritytech/ink/pull/2098 for details.",
InkArgKind::Anonymous => "Tells the ink! codegen to treat the ink! event as anonymous which omits the event signature as topic upon emitting.",
InkArgKind::Backend if version.is_gte_v5() => "Tells the ink! e2e test runner which type of architecture to use to execute the test.",
InkArgKind::Constructor => "Flags a function for the ink! storage `struct` as a constructor making it available to the API for instantiating the contract.",
InkArgKind::Decode if version.is_gte_v5() => "Derives an implementation of the `ink::scale::Decode` trait.",
InkArgKind::Default => "Tells UI to treat the ink! message or ink! constructor as the default choice in selection widgets (e.g dropdowns).",
InkArgKind::Derive => "A configuration parameter used to enable/disable auto deriving of all required storage traits.",
InkArgKind::Encode if version.is_gte_v5() => "Derives an implementation of the `ink::scale::Encode` trait.",
InkArgKind::Env => "Tells the ink! code generator which environment to use for the ink! smart contract.",
InkArgKind::Environment => "Tells the ink! e2e test runner which environment to use to execute the test.",
InkArgKind::Event => "Defines an ink! event.",
InkArgKind::Extension if version.is_legacy() => "Determines the unique function ID of the chain extension function.",
InkArgKind::Extension if version.is_v5() => "Determines the unique ID of the chain extension.",
InkArgKind::Extension => CHAIN_EXT_DEPRECATION_NOTICE,
InkArgKind::Function if version.is_v5() => "Determines the unique function ID of the chain extension function.",
InkArgKind::Function if version.is_gte_v6() => CHAIN_EXT_DEPRECATION_NOTICE,
InkArgKind::HandleStatus if version.is_lte_v5() => "Assumes that the returned status code of the chain extension function always indicates success and therefore always loads and decodes the output buffer of the call.",
InkArgKind::HandleStatus => CHAIN_EXT_DEPRECATION_NOTICE,
InkArgKind::Impl => "Tells the ink! codegen that some implementation block shall be granted access to ink! internals even without it containing any ink! messages or ink! constructors.",
InkArgKind::KeepAttr => "Tells the ink! code generator which attributes should be passed to call builders.",
InkArgKind::Message => "Flags a method for the ink! storage `struct` as a message making it available to the API for calling the contract.",
InkArgKind::Name if version.is_gte_v6() => "Specifies a name/identifier override that is used in place of the item's name/identifier for:\n\
- Selector computation for ink! messages and ink! constructors\
- Signature topic computation for ink! events\
- Contract metadata generation for the ink! messages, ink! constructors or ink! events",
InkArgKind::Namespace => "Changes the resulting selectors of all the ink! messages and ink! constructors within the trait implementation.",
InkArgKind::Node if version.is_gte_v5() => "Tells the ink! e2e test runner to use the standard approach of running dedicated single-node blockchain in a background process to execute the test.",
InkArgKind::Packed if version.is_gte_v6() => r#"A configuration flag for storage items that enables "packed" layout."#,
InkArgKind::Payable => "Allows receiving value as part of the call of the ink! message.",
InkArgKind::RuntimeOnly if version.is_v5_0() => "Tells the ink! e2e test runner to use the lightweight approach of skipping the node layer by running a runtime emulator within `TestExternalities` (using drink! library) in the same process as the test.",
InkArgKind::RuntimeOnly if version.is_gte_v5_1() => "Tells the ink! e2e test runner to use the lightweight approach of skipping the node layer by running a runtime emulator within `TestExternalities` in the same process as the test.",
InkArgKind::Sandbox if version.is_gte_v5() => "Tells the ink! e2e test runner which runtime emulator to use when executing the test.",
InkArgKind::Selector => "The `u32` variant specifies a concrete dispatch selector for the flagged entity, \
which allows a contract author to precisely control the selectors of their APIs making it possible to rename their API without breakage.\n\n\
While the `_` variant specifies a fallback message that is invoked if no other ink! message matches a selector.",
InkArgKind::SignatureTopic if version.is_gte_v5() => "Specifies custom signature topic of the event that allows to use manually specified shared event definition.",
InkArgKind::Storage => "Defines the ink! storage `struct`.",
InkArgKind::Topic => "Tells the ink! codegen to provide a topic hash for the given field.",
InkArgKind::TypeInfo if version.is_gte_v5() => "Derives an implementation of the `ink::scale_info::TypeInfo` trait.",
InkArgKind::Url => "Tells the ink! e2e test runner which node url to connect to before executing the test.",
_ => "",
}
}
}
impl Ord for InkArgKind {
fn cmp(&self, other: &Self) -> Ordering {
Ord::cmp(
&ink_arg_kind_sort_order(*self),
&ink_arg_kind_sort_order(*other),
)
}
}
impl PartialOrd for InkArgKind {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum InkArgValueKind {
None,
U16,
U32,
U32OrWildcard,
U32OrWildcardOrComplement,
String(InkArgValueStringKind),
Bool,
Path(InkArgValuePathKind),
Arg(InkArgKind, bool),
Choice(InkArgKind, InkArgKind, bool),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum InkArgValueStringKind {
Abi,
CommaList,
Default,
Hex,
Identifier,
IdentifierLike,
SpaceList,
Url,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum InkArgValuePathKind {
Default,
Environment,
Sandbox,
}
impl From<InkArgKind> for InkArgValueKind {
fn from(arg_kind: InkArgKind) -> Self {
match arg_kind {
InkArgKind::Abi => InkArgValueKind::String(InkArgValueStringKind::Abi),
InkArgKind::AdditionalContracts => {
InkArgValueKind::String(InkArgValueStringKind::SpaceList)
}
InkArgKind::Backend => {
InkArgValueKind::Choice(InkArgKind::Node, InkArgKind::RuntimeOnly, true)
}
InkArgKind::Env | InkArgKind::Environment => {
InkArgValueKind::Path(InkArgValuePathKind::Environment)
}
InkArgKind::Extension => InkArgValueKind::U32,
InkArgKind::Function => InkArgValueKind::U16,
InkArgKind::HandleStatus | InkArgKind::Derive => InkArgValueKind::Bool,
InkArgKind::KeepAttr => InkArgValueKind::String(InkArgValueStringKind::CommaList),
InkArgKind::Name => InkArgValueKind::String(InkArgValueStringKind::IdentifierLike),
InkArgKind::Namespace => InkArgValueKind::String(InkArgValueStringKind::Identifier),
InkArgKind::Node => InkArgValueKind::Arg(InkArgKind::Url, false),
InkArgKind::RuntimeOnly => InkArgValueKind::Arg(InkArgKind::Sandbox, false),
InkArgKind::Sandbox => InkArgValueKind::Path(InkArgValuePathKind::Sandbox),
InkArgKind::Selector => InkArgValueKind::U32OrWildcard,
InkArgKind::SignatureTopic => InkArgValueKind::String(InkArgValueStringKind::Hex),
InkArgKind::Url => InkArgValueKind::String(InkArgValueStringKind::Url),
_ => InkArgValueKind::None,
}
}
}
impl InkArgValueKind {
pub fn from_v5(arg_kind: InkArgKind, is_constructor: Option<bool>) -> Self {
match arg_kind {
InkArgKind::Extension => InkArgValueKind::U16,
InkArgKind::Selector if is_constructor == Some(true) => InkArgValueKind::U32OrWildcard,
InkArgKind::Selector => InkArgValueKind::U32OrWildcardOrComplement,
_ => InkArgValueKind::from(arg_kind),
}
}
}
impl fmt::Display for InkArgValueKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
InkArgValueKind::U16 => "u16".to_owned(),
InkArgValueKind::U32 => "u32".to_owned(),
InkArgValueKind::U32OrWildcard => "u32 | _".to_owned(),
InkArgValueKind::U32OrWildcardOrComplement => "u32 | _ | @".to_owned(),
InkArgValueKind::String(_) => "&str".to_owned(),
InkArgValueKind::Bool => "bool".to_owned(),
InkArgValueKind::Path(path_kind) => match path_kind {
InkArgValuePathKind::Environment => "impl Environment".to_owned(),
InkArgValuePathKind::Sandbox => "impl drink::Sandbox".to_owned(),
_ => "Path".to_owned(),
},
InkArgValueKind::Arg(kind, required) if *required => kind.to_string(),
InkArgValueKind::Choice(kind_1, kind_2, required) if *required =>
format!("{kind_1} | {kind_2}"),
_ => "".to_owned(),
}
)
}
}
impl InkArgValueKind {
pub fn detail(&self) -> String {
match self {
InkArgValueKind::Path(InkArgValuePathKind::Environment) => {
"A `ink::env::Environment` implementation.".to_owned()
}
InkArgValueKind::Path(InkArgValuePathKind::Sandbox) => {
"A `drink::Sandbox` implementation.".to_owned()
}
InkArgValueKind::String(InkArgValueStringKind::CommaList) => {
"A comma separated list.".to_owned()
}
InkArgValueKind::String(InkArgValueStringKind::Hex) => {
"A 32 byte hex string.".to_owned()
}
InkArgValueKind::String(InkArgValueStringKind::Abi) => {
r#"The ABI (Application Binary Interface), either "ink" or "sol"."#.to_owned()
}
InkArgValueKind::String(InkArgValueStringKind::Identifier) => {
"A valid Rust identifier.".to_owned()
}
InkArgValueKind::String(InkArgValueStringKind::IdentifierLike) => {
"An \"identifier-like\" string i.e. \
must begin with an alphabetic character, underscore or dollar sign, \
and only contain alphanumeric characters, underscores and dollar signs."
.to_owned()
}
InkArgValueKind::String(InkArgValueStringKind::SpaceList) => {
"A space separated list.".to_owned()
}
InkArgValueKind::String(InkArgValueStringKind::Url) => "A URL.".to_owned(),
InkArgValueKind::Arg(kind, required) if *required => kind.to_string(),
InkArgValueKind::Choice(kind_1, kind_2, required) if *required => {
format!("{kind_1} | {kind_2}")
}
_ => "".to_owned(),
}
}
}