diff --git a/Cargo.lock b/Cargo.lock index 32ab2279..a2d3b514 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "abigen" version = "0.1.0" @@ -831,6 +833,7 @@ dependencies = [ "log", "maplit", "move-executor", + "move-resource-viewer", "once_cell", "rand", "regex", @@ -1625,7 +1628,7 @@ dependencies = [ "log", "lsp-types", "move-compat", - "move-core-types 0.1.0 (git+https://github.com/dfinance/libra.git)", + "move-core-types 0.1.0 (git+https://github.com/dfinance/libra.git?branch=master)", "rand", "regex", "rust-base58", @@ -1666,7 +1669,7 @@ name = "libra-canonical-serialization" version = "0.1.0" source = "git+https://github.com/dfinance/libra.git?branch=master#c46b32d73087ed2afc6c1ac3e78535cac37e3fa9" dependencies = [ - "libra-workspace-hack 0.1.0 (git+https://github.com/dfinance/libra.git)", + "libra-workspace-hack 0.1.0 (git+https://github.com/dfinance/libra.git?branch=master)", "serde", "thiserror", ] @@ -1719,10 +1722,10 @@ dependencies = [ "ed25519-dalek 1.0.0-pre.4", "hex", "hkdf", - "libra-canonical-serialization 0.1.0 (git+https://github.com/dfinance/libra.git)", - "libra-crypto-derive 0.1.0 (git+https://github.com/dfinance/libra.git)", - "libra-nibble 0.1.0 (git+https://github.com/dfinance/libra.git)", - "libra-workspace-hack 0.1.0 (git+https://github.com/dfinance/libra.git)", + "libra-canonical-serialization 0.1.0 (git+https://github.com/dfinance/libra.git?branch=master)", + "libra-crypto-derive 0.1.0 (git+https://github.com/dfinance/libra.git?branch=master)", + "libra-nibble 0.1.0 (git+https://github.com/dfinance/libra.git?branch=master)", + "libra-workspace-hack 0.1.0 (git+https://github.com/dfinance/libra.git?branch=master)", "mirai-annotations", "once_cell", "rand", @@ -1774,7 +1777,7 @@ name = "libra-crypto-derive" version = "0.1.0" source = "git+https://github.com/dfinance/libra.git?branch=master#c46b32d73087ed2afc6c1ac3e78535cac37e3fa9" dependencies = [ - "libra-workspace-hack 0.1.0 (git+https://github.com/dfinance/libra.git)", + "libra-workspace-hack 0.1.0 (git+https://github.com/dfinance/libra.git?branch=master)", "proc-macro2", "quote", "syn", @@ -1911,7 +1914,7 @@ name = "libra-nibble" version = "0.1.0" source = "git+https://github.com/dfinance/libra.git?branch=master#c46b32d73087ed2afc6c1ac3e78535cac37e3fa9" dependencies = [ - "libra-workspace-hack 0.1.0 (git+https://github.com/dfinance/libra.git)", + "libra-workspace-hack 0.1.0 (git+https://github.com/dfinance/libra.git?branch=master)", "serde", ] @@ -2349,10 +2352,10 @@ source = "git+https://github.com/dfinance/libra.git?branch=master#c46b32d73087ed dependencies = [ "anyhow", "hex", - "libra-canonical-serialization 0.1.0 (git+https://github.com/dfinance/libra.git)", - "libra-crypto 0.1.0 (git+https://github.com/dfinance/libra.git)", - "libra-crypto-derive 0.1.0 (git+https://github.com/dfinance/libra.git)", - "libra-workspace-hack 0.1.0 (git+https://github.com/dfinance/libra.git)", + "libra-canonical-serialization 0.1.0 (git+https://github.com/dfinance/libra.git?branch=master)", + "libra-crypto 0.1.0 (git+https://github.com/dfinance/libra.git?branch=master)", + "libra-crypto-derive 0.1.0 (git+https://github.com/dfinance/libra.git?branch=master)", + "libra-workspace-hack 0.1.0 (git+https://github.com/dfinance/libra.git?branch=master)", "mirai-annotations", "once_cell", "rand", diff --git a/README.md b/README.md index d4b519a2..77810afc 100644 --- a/README.md +++ b/README.md @@ -53,9 +53,98 @@ Build project: ```shell script dove build ``` - See `./target/` folder to get scripts/modules binaries. + +Create transactions: + +Command `ct` allows you to create transactions for `polkadot` chain with move vm palette. + +`ct` takes script identifier, type parameters, and arguments and creates a transaction file as an artifact of work. + +Example: +```shell script +dove ct 'store_u64(60)' +``` +This command searches for the script by name 'store_u64' in the script directory. Then it compiles it and creates a transaction file. + +This command will fail if: + +- There is no script with the name given name 'store_u64'. +- There is more than one script with the name 'store_64'. +- The passed parameters or type parameters do not match the script parameters. +- There are syntax errors in the script. + +Type parameters: + +You can use type parameters like in the move language. +Example: +```shell script +dove ct 'create_account<0x01::Dfinance::USD, 0x01::Dfinance::BTC>()' +``` +You allow can use ss58 address format: +```shell script +dove ct 'create_account<1exaAg2VJRQbyUBAeXcktChCAqjVP9TUxF3zo23R2T6EGdE::Dfinance::USD>()' +``` + +Types: + +numbers (u8, u64, u128): 10, 1024. + +bool: true, false. + +address: 1exaAg2VJRQbyUBAeXcktChCAqjVP9TUxF3zo23R2T6EGdE, 0x1CF326C5AAA5AF9F0E2791E66310FE8F044FAADAF12567EAA0976959D1F7731F + +vector
: [1exaAg2VJRQbyUBAeXcktChCAqjVP9TUxF3zo23R2T6EGdE, 0x1CF326C5AAA5AF9F0E2791E66310FE8F044FAADAF12567EAA0976959D1F7731F, 0x01] + +vector: [10, 30, 1024] + +vector: [true, false] + +You can define or override script names by using '--name' or '-n' parameter. + +Example: +Override script name: +```shell script +dove ct 'store_u64(60)' -n store_u126 +``` +Define script name: +```shell script +dove ct -n store_u126 +``` + +File name: +You can define the file name by using '--file' or '-f' parameter. +With this option 'ct' searches in a specified file. It may be useful when there is more than one script with the same name in different files. +Or the specified file has one script. +```shell script +dove ct 'store_u64(60)' -n store_u126 -f script.move +``` +```shell script +dove ct -n store_u126 -f script +``` + +Type parameters: + +You can define or override script type parameters by using '--type' or '-t' parameter. +```shell script +dove ct 'store_u64()' -t 0x01::Dfinance::USD u8 +``` +```shell script +dove ct -n store_u64 -t 0x01::Dfinance::USD u8 +``` + + +arguments: + +You can define or override script arguments by using '--args' or '-a' parameter. +```shell script +dove ct 'store_u64()' -a [10, 1024] 10 0x01 +``` +```shell script +dove ct -n store_u64 -a [10, 1024] 10 0x01 +``` + ## Resource Viewer See [documentation](/resource-viewer/README.md). diff --git a/dove/Cargo.toml b/dove/Cargo.toml index 07d29e6c..bc1f86a8 100644 --- a/dove/Cargo.toml +++ b/dove/Cargo.toml @@ -31,6 +31,7 @@ reqwest = { version = "0.10.4", features = ["blocking", "json"] } itertools = "0.9.0" lang = { path = "../lang" } move-executor = { path = "../executor" } +move-resource-viewer = { path = "../resource-viewer", default-features = false } git-hash = { path = "../common/git-hash" } maplit = "1.0.2" serde_json = "1.0.52" @@ -44,14 +45,17 @@ libra_address = [ "libra/libra_address", "lang/libra_address", "move-executor/libra_address", + "move-resource-viewer/libra_address", ] dfinance_address = [ "libra/dfinance_address", "lang/dfinance_address", "move-executor/dfinance_address", + "move-resource-viewer/dfinance_address", ] ps_address = [ "libra/ps_address", "lang/ps_address", "move-executor/ps_address", + "move-resource-viewer/ps_address" ] \ No newline at end of file diff --git a/dove/src/bin/dove.rs b/dove/src/bin/dove.rs index 0cbb5c03..2c825c64 100644 --- a/dove/src/bin/dove.rs +++ b/dove/src/bin/dove.rs @@ -14,6 +14,7 @@ use dove::cmd::fetch::Fetch; use dove::cmd::build::Build; use dove::cmd::test::Test; use dove::cmd::run::Run; +use dove::cmd::ct::CreateTransactionCmd; #[derive(StructOpt, Debug)] #[structopt(name = "Move compiler.", version = git_hash::crate_version_with_git_hash_short!())] @@ -58,6 +59,11 @@ enum Opt { #[structopt(flatten)] cmd: Run, }, + #[structopt(about = "Create transaction")] + Ct { + #[structopt(flatten)] + cmd: CreateTransactionCmd, + }, } fn main() { @@ -73,6 +79,7 @@ fn main() { Opt::Build { cmd } => cmd.execute(), Opt::Test { cmd } => cmd.execute(), Opt::Run { cmd } => cmd.execute(), + Opt::Ct { cmd } => cmd.execute(), }); } diff --git a/dove/src/cmd/build.rs b/dove/src/cmd/build.rs index 7b69fd75..5a036283 100644 --- a/dove/src/cmd/build.rs +++ b/dove/src/cmd/build.rs @@ -2,11 +2,10 @@ use crate::cmd::{Cmd, load_dependencies}; use crate::context::Context; use anyhow::Error; use structopt::StructOpt; -use crate::index::Index; use lang::compiler::file::load_move_files; use lang::builder::{Artifacts, MoveBuilder}; use termcolor::{StandardStream, ColorChoice}; -use std::path::PathBuf; +use std::path::Path; use std::fs::File; use std::io::Write; use std::fs; @@ -24,17 +23,12 @@ pub struct Build {} impl Cmd for Build { fn apply(self, ctx: Context) -> Result<(), Error> { - let dirs: Vec<_> = [ + let dirs = ctx.paths_for(&[ &ctx.manifest.layout.script_dir, &ctx.manifest.layout.module_dir, - ] - .iter() - .map(|d| ctx.path_for(&d)) - .filter(|p| p.exists()) - .collect(); + ]); - let mut index = Index::load(&ctx)?; - index.build()?; + let mut index = ctx.build_index()?; let dep_set = index.make_dependency_set(&dirs)?; let dep_list = load_dependencies(dep_set)?; @@ -68,7 +62,7 @@ pub fn verify_and_store( .into_iter() .partition(|u| matches!(u, CompiledUnit::Module { .. })); - fn store(units: Vec, base_dir: &PathBuf) -> Result<(), Error> { + fn store(units: Vec, base_dir: &Path) -> Result<(), Error> { for (idx, unit) in units.into_iter().enumerate() { let mut path = base_dir.join(format!("{}_{}", idx, unit.name())); path.set_extension("mv"); diff --git a/dove/src/cmd/ct.rs b/dove/src/cmd/ct.rs new file mode 100644 index 00000000..a4636971 --- /dev/null +++ b/dove/src/cmd/ct.rs @@ -0,0 +1,708 @@ +use crate::cmd::{Cmd, load_dependencies}; +use crate::context::Context; +use anyhow::Error; +use structopt::StructOpt; +use lang::compiler::file::{MoveFile, find_move_files, load_move_files}; +use lang::meta_extractor::{ScriptMetadata, Meta}; +use lang::builder::{Artifacts, MoveBuilder}; +use termcolor::{StandardStream, ColorChoice}; +use libra::move_core_types::language_storage::TypeTag; +use serde::{Serialize, Deserialize}; +use libra::account::AccountAddress; +use libra::move_lang::parser::lexer::{Lexer, Tok}; +use libra::move_lang::parser::syntax::parse_type; +use libra::{ + prelude::CompiledUnit, + move_lang::{compiled_unit, errors::output_errors}, +}; +use move_resource_viewer::tte::unwrap_spanned_ty; +use std::fmt::Debug; +use std::str::FromStr; +use lang::compiler::ss58::{ss58_to_libra, replace_ss58_addresses}; +use std::fs; + +/// Create transaction. +#[derive(StructOpt, Debug)] +pub struct CreateTransactionCmd { + #[structopt(help = "Script call declaration.\ + Example: 'create_balance<0x01::Dfinance::USD>([10,10], true, 68656c6c6f776f726c64, 100)'")] + call: Option, + #[structopt(help = "Script name.", long = "name", short = "n")] + script_name: Option, + #[structopt(help = "Script file name.", long = "file", short = "f")] + file_name: Option, + #[structopt( + help = r#"Script type parametrs, e.g. 0x1::Dfinance::USD"#, + name = "Script type parameters.", + long = "type", + short = "t" + )] + type_parameters: Option>, + #[structopt( + help = r#"Script arguments, e.g. 10 20 30"#, + name = "Script arguments.", + long = "args", + short = "a" + )] + args: Option>, +} + +impl Cmd for CreateTransactionCmd { + fn apply(self, ctx: Context) -> Result<(), Error> { + let builder = TransactionBuilder::new(self, &ctx)?; + let (script_name, transaction) = builder.build()?; + store_transaction(&ctx, &script_name, transaction) + } +} + +struct TransactionBuilder<'a> { + script_file_name: Option, + script_name: Option, + type_parameters: Vec, + args: Vec, + dove_ctx: &'a Context, +} + +impl<'a> TransactionBuilder<'a> { + pub fn new(cmd: CreateTransactionCmd, ctx: &'a Context) -> Result { + let (mut script_name, mut type_parameters, mut args) = if let Some(call) = cmd.call { + let (script_name, type_parameters, args) = Self::parse_call(&call)?; + (Some(script_name), type_parameters, args) + } else { + (None, vec![], vec![]) + }; + + if let Some(cmd_script_name) = cmd.script_name { + script_name = Some(cmd_script_name); + } + + if let Some(cmd_type_parameters) = cmd.type_parameters { + type_parameters = cmd_type_parameters + .iter() + .map(|tp| replace_ss58_addresses(tp, &mut Default::default())) + .map(|tp| parse_type_params(&mut Lexer::new(&tp, "tp", Default::default()))) + .collect::>()?; + } + + if let Some(cmd_args) = cmd.args { + args = cmd_args + .iter() + .map(|arg| replace_ss58_addresses(arg, &mut Default::default())) + .collect(); + } + + Ok(TransactionBuilder { + script_file_name: cmd.file_name, + script_name, + type_parameters, + args, + dove_ctx: ctx, + }) + } + + pub fn parse_call(call: &str) -> Result<(String, Vec, Vec), Error> { + let call = replace_ss58_addresses(call, &mut Default::default()); + + let map_err = |err| Error::msg(format!("{:?}", err)); + let mut lexer = Lexer::new(&call, "call", Default::default()); + lexer.advance().map_err(map_err)?; + if lexer.peek() != Tok::IdentifierValue { + return Err(anyhow!("Invalid call script format.\ + Expected function identifier. Use pattern \ + 'script_name(comma separated parameters WITHOUT signers)'")); + } + + let script_name = lexer.content().to_owned(); + + lexer.advance().map_err(map_err)?; + + let type_parameters = if lexer.peek() == Tok::Less { + let mut type_parameter = vec![]; + + lexer.advance().map_err(map_err)?; + while lexer.peek() != Tok::Greater { + if lexer.peek() == Tok::EOF { + return Err(anyhow!("Invalid call script format.\ + Invalid type parameters format.. Use pattern \ + 'script_name(comma separated parameters WITHOUT signers)'")); + } + + if lexer.peek() == Tok::Comma { + lexer.advance().map_err(map_err)?; + continue; + } + + type_parameter.push(parse_type_params(&mut lexer)?); + } + lexer.advance().map_err(map_err)?; + type_parameter + } else { + vec![] + }; + + if lexer.peek() != Tok::LParen { + return Err(anyhow!("Invalid call script format.\ + Invalid script arguments format.. Left paren '(' is expected. Use pattern \ + 'script_name(comma separated parameters WITHOUT signers)'")); + } + + let mut arguments = vec![]; + + lexer.advance().map_err(map_err)?; + while lexer.peek() != Tok::RParen { + if lexer.peek() == Tok::EOF { + return Err(anyhow!("Invalid call script format.\ + Invalid arguments format.. Use pattern \ + 'script_name(comma separated parameters WITHOUT signers)'")); + } + + if lexer.peek() == Tok::Comma { + lexer.advance().map_err(map_err)?; + continue; + } + + if lexer.peek() == Tok::LBracket { + let mut token = String::new(); + token.push_str(lexer.content()); + lexer.advance().map_err(map_err)?; + while lexer.peek() != Tok::RBracket { + token.push_str(lexer.content()); + lexer.advance().map_err(map_err)?; + } + token.push_str(lexer.content()); + arguments.push(token); + } else { + let mut token = String::new(); + token.push_str(lexer.content()); + lexer.advance().map_err(map_err)?; + while lexer.peek() != Tok::Comma && lexer.peek() != Tok::RParen { + token.push_str(lexer.content()); + lexer.advance().map_err(map_err)?; + } + arguments.push(token); + if lexer.peek() == Tok::RParen { + break; + } + } + lexer.advance().map_err(map_err)?; + } + + Ok((script_name, type_parameters, arguments)) + } + + fn lookup_script_by_file_name(&self, fname: &str) -> Result<(MoveFile, Meta), Error> { + let script_path = self + .dove_ctx + .path_for(&self.dove_ctx.manifest.layout.script_dir); + let file_path = if !fname.ends_with("move") { + let mut path = script_path.join(fname); + path.set_extension("move"); + path + } else { + script_path.join(fname) + }; + if !file_path.exists() { + return Err(anyhow!("File [{}] not found", fname)); + } + + let script = MoveFile::load(&file_path)?; + let mut scripts = ScriptMetadata::extract(self.dove_ctx.dialect.as_ref(), &script)?; + if scripts.is_empty() { + return Err(anyhow!("Script not found in file '{}'", fname)); + } + + let meta = if scripts.len() > 1 { + let mut scripts = scripts + .into_iter() + .filter(|sc| { + if let Some(script_name) = &self.script_name { + &sc.name == script_name + } else { + false + } + }) + .collect::>(); + if scripts.len() > 1 { + return Err(anyhow!( + "There are several scripts with the name '{:?}' in file '{}'", + self.script_name, + fname + )); + } else { + scripts.remove(0) + } + } else { + scripts.remove(0) + }; + + Ok((script, meta)) + } + + fn lookup_script_by_name(&self, name: &str) -> Result<(MoveFile, Meta), Error> { + let script_path = self + .dove_ctx + .path_for(&self.dove_ctx.manifest.layout.script_dir); + let mut files = find_move_files(&script_path)? + .iter() + .map(MoveFile::load) + .filter_map(|mf| match mf { + Ok(mf) => { + if mf.content().contains(name) { + Some(mf) + } else { + None + } + } + Err(err) => { + warn!("{:?}", err); + None + } + }) + .map(|mf| { + ScriptMetadata::extract(self.dove_ctx.dialect.as_ref(), &mf) + .map(|meta| (mf, meta)) + }) + .filter_map(|script| match script { + Ok((mf, meta)) => Some((mf, meta)), + Err(err) => { + warn!("{:?}", err); + None + } + }) + .filter(|(_, meta)| meta.iter().any(|meta| *name == meta.name)) + .collect::>(); + + if files.is_empty() { + return Err(anyhow!("Script not found.")); + } + + if files.len() > 1 { + let name_list = files + .iter() + .map(|(mf, _)| mf.name()) + .collect::>() + .join(", "); + return Err(anyhow!( + "There are several scripts with the name '{:?}' in files ['{}'].", + name, + name_list + )); + } + + let (file, mut meta) = files.remove(0); + if meta.is_empty() { + return Err(anyhow!("Script not found.")); + } + + if meta.len() > 1 { + return Err(anyhow!( + "There are several scripts with the name '{:?}' in file '{}'.", + name, + file.name() + )); + } + Ok((file, meta.remove(0))) + } + + fn lookup_script(&self) -> Result<(MoveFile, Meta), Error> { + if let Some(file_name) = &self.script_file_name { + return self.lookup_script_by_file_name(file_name); + } + + if let Some(name) = &self.script_name { + return self.lookup_script_by_name(name); + } + + let script_path = self + .dove_ctx + .path_for(&self.dove_ctx.manifest.layout.script_dir); + let files = find_move_files(&script_path)?; + if files.len() == 1 { + let mf = MoveFile::load(&files[0])?; + let mut meta = ScriptMetadata::extract(self.dove_ctx.dialect.as_ref(), &mf)?; + if meta.is_empty() { + return Err(anyhow!("Script not found.")); + } + if meta.len() > 1 { + return Err(anyhow!("Failed to determine script. There are several scripts. Use '--name' to determine the script.")); + } + Ok((mf, meta.remove(0))) + } else { + Err(anyhow!("Failed to determine script. There are several scripts. Use '--name' or '--file' to determine the script.")) + } + } + + fn build_script(&self, script: MoveFile) -> Result, Error> { + let mut index = self.dove_ctx.build_index()?; + + let module_dir = self + .dove_ctx + .path_for(&self.dove_ctx.manifest.layout.module_dir) + .to_str() + .map(|path| path.to_owned()) + .ok_or_else(|| anyhow!("Failed to convert module dir path"))?; + + let dep_set = index.make_dependency_set(&[module_dir.as_str(), script.name()])?; + let mut dep_list = load_dependencies(dep_set)?; + dep_list.extend(load_move_files(&[module_dir])?); + + let sender = self.dove_ctx.account_address()?; + let Artifacts { files, prog } = + MoveBuilder::new(self.dove_ctx.dialect.as_ref(), Some(sender).as_ref()) + .build(&[script], &dep_list); + + match prog { + Err(errors) => { + let mut writer = StandardStream::stderr(ColorChoice::Auto); + output_errors(&mut writer, files, errors); + Err(anyhow!( + "could not compile:{}", + self.dove_ctx.project_name() + )) + } + Ok(compiled_units) => { + let (compiled_units, ice_errors) = compiled_unit::verify_units(compiled_units); + + if !ice_errors.is_empty() { + let mut writer = StandardStream::stderr(ColorChoice::Auto); + output_errors(&mut writer, files, ice_errors); + Err(anyhow!("could not verify:{}", self.dove_ctx.project_name())) + } else { + Ok(compiled_units) + } + } + } + } + + fn prepare_arguments( + &self, + args_type: &[(String, String)], + ) -> Result<(usize, usize, Vec), Error> { + let total_args = args_type.len(); + + fn parse_err(name: &str, tp: &str, index: usize, value: &str, err: D) -> Error { + anyhow!( + "Parameter '{}' has {} type. Failed to parse {} [{}]. Error:'{:?}'", + name, + tp, + value, + index, + err + ) + } + + args_type.iter().try_fold( + (0, 0, Vec::new()), + |(signers, args_index, mut values), (name, tp)| match tp.as_str() { + "&signer" => Ok((signers + 1, args_index, values)), + "bool" => { + let arg = self.argument(args_index, total_args)?; + values.push(ScriptArg::Bool( + arg.parse() + .map_err(|err| parse_err(name, tp, args_index, arg, err))?, + )); + Ok((signers, args_index + 1, values)) + } + "u8" => { + let arg = self.argument(args_index, total_args)?; + values.push(ScriptArg::U8( + arg.parse() + .map_err(|err| parse_err(name, tp, args_index, arg, err))?, + )); + Ok((signers, args_index + 1, values)) + } + "u64" => { + let arg = self.argument(args_index, total_args)?; + values.push(ScriptArg::U64( + arg.parse() + .map_err(|err| parse_err(name, tp, args_index, arg, err))?, + )); + Ok((signers, args_index + 1, values)) + } + "u128" => { + let arg = self.argument(args_index, total_args)?; + values.push(ScriptArg::U128( + arg.parse() + .map_err(|err| parse_err(name, tp, args_index, arg, err))?, + )); + Ok((signers, args_index + 1, values)) + } + "address" => { + let arg = self.argument(args_index, total_args)?; + values.push(ScriptArg::Address(Address::from_str(arg)?.addr)); + Ok((signers, args_index + 1, values)) + } + "vector" => { + let arg = self.argument(args_index, total_args)?; + let buffer = if arg.contains('[') { + parse_vec(arg, "u8")? + } else { + hex::decode(arg)? + }; + values.push(ScriptArg::VectorU8(buffer)); + Ok((signers, args_index + 1, values)) + } + "vector" => { + let arg = self.argument(args_index, total_args)?; + values.push(ScriptArg::VectorU64(parse_vec(arg, "u64")?)); + Ok((signers, args_index + 1, values)) + } + "vector" => { + let arg = self.argument(args_index, total_args)?; + values.push(ScriptArg::VectorU128(parse_vec(arg, "u128")?)); + Ok((signers, args_index + 1, values)) + } + "vector
" => { + let arg = self.argument(args_index, total_args)?; + let address = parse_vec::
(arg, "vector
")? + .iter() + .map(|addr| addr.addr) + .collect(); + values.push(ScriptArg::VectorAddress(address)); + Ok((signers, args_index + 1, values)) + } + &_ => Err(anyhow!("Unexpected script parameter: {}", tp)), + }, + ) + } + + fn argument(&self, index: usize, total_expected: usize) -> Result<&String, Error> { + self.args + .get(index) + .ok_or_else(|| anyhow!("{} arguments are expected.", total_expected)) + } + + pub fn build(self) -> Result<(String, Transaction), Error> { + let (script, meta) = self.lookup_script()?; + let units = self.build_script(script)?; + + let unit = units + .into_iter() + .find(|unit| { + let is_module = match &unit { + CompiledUnit::Module { .. } => false, + CompiledUnit::Script { .. } => true, + }; + is_module && unit.name() == meta.name + }) + .map(|unit| unit.serialize()) + .ok_or_else(|| anyhow!("Script '{}' not found", meta.name))?; + + if meta.type_parameters.len() != self.type_parameters.len() { + return Err(anyhow!( + "Script '{}' takes {} type parameters, {} passed", + meta.name, + meta.type_parameters.len(), + self.type_parameters.len() + )); + } + + let (signers, args_count, args) = self.prepare_arguments(&meta.parameters)?; + + if self.args.len() != args_count { + return Err(anyhow!( + "Script '{}' takes {} parameters, {} passed", + meta.name, + args_count, + self.args.len() + )); + } + + Ok(( + meta.name, + Transaction::new(signers as u8, unit, args, self.type_parameters), + )) + } +} + +/// Script argument type. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, PartialOrd)] +pub enum ScriptArg { + /// u8 + U8(u8), + /// u64 + U64(u64), + /// u128 + U128(u128), + /// bool + Bool(bool), + /// address + Address(AccountAddress), + /// vector + VectorU8(Vec), + /// vector + VectorU64(Vec), + /// vector + VectorU128(Vec), + /// vector + VectorBool(Vec), + /// vector
+ VectorAddress(Vec), +} + +/// Transaction model. +#[derive(Serialize, Deserialize, Debug)] +pub struct Transaction { + signers_count: u8, + code: Vec, + args: Vec, + type_args: Vec, +} + +impl Transaction { + /// Create a new transaction. + pub fn new( + signers_count: u8, + code: Vec, + args: Vec, + type_args: Vec, + ) -> Transaction { + Transaction { + signers_count, + code, + args, + type_args, + } + } +} + +fn parse_type_params(lexer: &mut Lexer) -> Result { + let ty = parse_type(lexer).map_err(|err| Error::msg(format!("{:?}", err)))?; + unwrap_spanned_ty(ty) +} + +fn parse_vec(tkn: &str, tp_name: &str) -> Result, Error> +where + E: FromStr, +{ + let map_err = |err| Error::msg(format!("{:?}", err)); + + let mut lexer = Lexer::new(tkn, "vec", Default::default()); + lexer.advance().map_err(map_err)?; + + if lexer.peek() != Tok::LBracket { + return Err(anyhow!("Vector in format [n1, n2, ..., nn] is expected.")); + } + lexer.advance().map_err(map_err)?; + + let mut elements = vec![]; + while lexer.peek() != Tok::RBracket { + match lexer.peek() { + Tok::Comma => { + lexer.advance().map_err(map_err)?; + continue; + } + Tok::EOF => { + return Err(anyhow!("unexpected end of vector.")); + } + _ => { + elements.push(E::from_str(lexer.content()).map_err(|_| { + anyhow!( + "Failed to parse vector element. {} type is expected. Actual:'{}'", + tp_name, + lexer.content() + ) + })?); + lexer.advance().map_err(map_err)?; + } + } + } + Ok(elements) +} + +fn store_transaction(ctx: &Context, name: &str, tx: Transaction) -> Result<(), Error> { + let tx_dir = ctx.path_for(&ctx.manifest.layout.transaction_output); + if !tx_dir.exists() { + fs::create_dir_all(&tx_dir)?; + } + + let mut tx_file = tx_dir.join(name); + tx_file.set_extension("mvt"); + + if tx_file.exists() { + fs::remove_file(&tx_file)?; + } + println!("Store transaction:{:?}", tx_file); + Ok(fs::write(&tx_file, libra::lcs::to_bytes(&tx)?)?) +} + +struct Address { + addr: AccountAddress, +} + +impl FromStr for Address { + type Err = Error; + + fn from_str(addr: &str) -> Result { + let addr = match ss58_to_libra(addr) { + Ok(addr) => AccountAddress::from_hex_literal(&addr)?, + Err(_) => AccountAddress::from_hex_literal(&addr)?, + }; + Ok(Address { addr }) + } +} + +#[cfg(test)] +mod test { + use crate::cmd::ct::TransactionBuilder; + use libra::move_core_types::language_storage::{TypeTag, StructTag}; + use libra::move_core_types::language_storage::CORE_CODE_ADDRESS; + use libra::move_core_types::identifier::Identifier; + + #[test] + fn test_parse_call() { + let (name, tp, args) = TransactionBuilder::parse_call("create_account>(10, 68656c6c6f, [10, 23], true, 1exaAg2VJRQbyUBAeXcktChCAqjVP9TUxF3zo23R2T6EGdE)").unwrap(); + assert_eq!(name, "create_account"); + assert_eq!( + tp, + vec![ + TypeTag::U8, + TypeTag::Struct(StructTag { + address: CORE_CODE_ADDRESS, + module: Identifier::new("Dfinance").unwrap(), + name: Identifier::new("USD").unwrap(), + type_params: vec![TypeTag::U8], + }) + ] + ); + assert_eq!( + args, + vec![ + "10".to_owned(), + "68656c6c6f".to_owned(), + "[10,23]".to_owned(), + "true".to_owned(), + "0x1CF326C5AAA5AF9F0E2791E66310FE8F044FAADAF12567EAA0976959D1F7731F".to_owned() + ] + ); + + let (name, tp, args) = TransactionBuilder::parse_call( + "create_account<0x01::Dfinance::USD>([true, false], [0x01, 0x02])", + ) + .unwrap(); + assert_eq!(name, "create_account"); + assert_eq!( + tp, + vec![TypeTag::Struct(StructTag { + address: CORE_CODE_ADDRESS, + module: Identifier::new("Dfinance").unwrap(), + name: Identifier::new("USD").unwrap(), + type_params: vec![], + })] + ); + assert_eq!( + args, + vec!["[true,false]".to_owned(), "[0x01,0x02]".to_owned()] + ); + + let (name, tp, args) = TransactionBuilder::parse_call("create_account()").unwrap(); + assert_eq!(name, "create_account"); + assert_eq!(tp, Vec::::new()); + assert_eq!(args, Vec::::new()); + + let (name, tp, args) = TransactionBuilder::parse_call("create_account<>()").unwrap(); + assert_eq!(name, "create_account"); + assert_eq!(tp, Vec::::new()); + assert_eq!(args, Vec::::new()); + } +} diff --git a/dove/src/cmd/fetch.rs b/dove/src/cmd/fetch.rs index ccc2aaf3..43ca4448 100644 --- a/dove/src/cmd/fetch.rs +++ b/dove/src/cmd/fetch.rs @@ -2,7 +2,6 @@ use crate::cmd::Cmd; use crate::context::Context; use anyhow::Error; use structopt::StructOpt; -use crate::index::Index; /// Fetch dependencies. #[derive(StructOpt, Debug)] @@ -10,8 +9,7 @@ pub struct Fetch {} impl Cmd for Fetch { fn apply(self, ctx: Context) -> Result<(), Error> { - let mut index = Index::load(&ctx)?; - index.build()?; + ctx.build_index()?; Ok(()) } } diff --git a/dove/src/cmd/metadata.rs b/dove/src/cmd/metadata.rs index 04463db9..06e8321c 100644 --- a/dove/src/cmd/metadata.rs +++ b/dove/src/cmd/metadata.rs @@ -57,18 +57,18 @@ pub struct PackageJson { pub local_dependencies: Vec, } -impl Into for DoveToml { - fn into(self) -> DoveJson { +impl From for DoveJson { + fn from(toml: DoveToml) -> Self { DoveJson { - package: self.package.into(), - layout: self.layout, + package: toml.package.into(), + layout: toml.layout, } } } -impl Into for Package { - fn into(self) -> PackageJson { - let (locals, git) = if let Some(dependencies) = self.dependencies { +impl From for PackageJson { + fn from(pac: Package) -> Self { + let (locals, git) = if let Some(dependencies) = pac.dependencies { dependencies.deps.into_iter().fold( (Vec::new(), Vec::new()), |(mut locals, mut gits), elt| { @@ -84,10 +84,10 @@ impl Into for Package { }; PackageJson { - name: self.name.unwrap_or_default(), - account_address: self.account_address, - authors: self.authors, - blockchain_api: self.blockchain_api, + name: pac.name.unwrap_or_default(), + account_address: pac.account_address, + authors: pac.authors, + blockchain_api: pac.blockchain_api, git_dependencies: git, local_dependencies: locals, } diff --git a/dove/src/cmd/mod.rs b/dove/src/cmd/mod.rs index d4a4f27f..daba4110 100644 --- a/dove/src/cmd/mod.rs +++ b/dove/src/cmd/mod.rs @@ -8,6 +8,8 @@ use std::rc::Rc; pub mod build; /// Project dependencies loader. pub mod clean; +/// Create transaction. +pub mod ct; /// Dependencies fetcher. pub mod fetch; /// Project initializer. diff --git a/dove/src/cmd/run.rs b/dove/src/cmd/run.rs index 0a00547e..08b0663d 100644 --- a/dove/src/cmd/run.rs +++ b/dove/src/cmd/run.rs @@ -3,7 +3,6 @@ use crate::context::Context; use anyhow::Error; use structopt::StructOpt; use lang::compiler::file::{MoveFile, load_move_files}; -use crate::index::Index; use move_executor::executor::{Executor, render_execution_result}; /// Run script. @@ -29,8 +28,7 @@ impl Cmd for Run { } let module_dir = ctx.path_for(&ctx.manifest.layout.module_dir); - let mut index = Index::load(&ctx)?; - index.build()?; + let mut index = ctx.build_index()?; let dep_set = index.make_dependency_set(&[&script, &module_dir])?; let mut dep_list = load_dependencies(dep_set)?; diff --git a/dove/src/cmd/test.rs b/dove/src/cmd/test.rs index d15b91a5..8f295741 100644 --- a/dove/src/cmd/test.rs +++ b/dove/src/cmd/test.rs @@ -2,7 +2,6 @@ use crate::cmd::{Cmd, load_dependencies}; use crate::context::Context; use anyhow::Error; use structopt::StructOpt; -use crate::index::Index; use lang::compiler::file::load_move_files; use move_executor::executor::{Executor, render_test_result}; @@ -24,19 +23,14 @@ impl Cmd for Test { return Ok(()); } - let mut dirs: Vec<_> = [ + let mut dirs = ctx.paths_for(&[ &ctx.manifest.layout.script_dir, &ctx.manifest.layout.module_dir, - ] - .iter() - .map(|d| ctx.path_for(&d)) - .filter(|p| p.exists()) - .collect(); + ]); dirs.push(tests_dir.clone()); - let mut index = Index::load(&ctx)?; - index.build()?; + let mut index = ctx.build_index()?; let dep_set = index.make_dependency_set(&dirs)?; let mut dep_list = load_dependencies(dep_set)?; diff --git a/dove/src/context.rs b/dove/src/context.rs index 9c6f89db..87988e3e 100644 --- a/dove/src/context.rs +++ b/dove/src/context.rs @@ -1,10 +1,11 @@ use std::path::{PathBuf, Path}; use crate::manifest::{DoveToml, MANIFEST, read_manifest, default_dialect}; use std::str::FromStr; -use anyhow::{Result, anyhow}; +use anyhow::{Result, anyhow, Error}; use std::env; use lang::compiler::dialects::{Dialect, DialectName}; use lang::compiler::address::ProvidedAccountAddress; +use crate::index::Index; /// Project context. pub struct Context { @@ -22,6 +23,22 @@ impl Context { self.project_dir.join(path) } + /// Create absolute paths in project. + pub fn paths_for>(&self, paths: &[P]) -> Vec { + paths + .iter() + .map(|d| self.path_for(&d)) + .filter(|p| p.exists()) + .collect() + } + + /// Build project index. + pub fn build_index(&self) -> Result { + let mut index = Index::load(self)?; + index.build()?; + Ok(index) + } + /// Returns project name or default name `project` if the name is not defined. pub fn project_name(&self) -> String { self.manifest.package.name.clone().unwrap_or_else(|| { diff --git a/dove/src/index/mod.rs b/dove/src/index/mod.rs index 3f71632c..7ffe0095 100644 --- a/dove/src/index/mod.rs +++ b/dove/src/index/mod.rs @@ -63,10 +63,10 @@ impl<'a> Index<'a> { if !self.dep_names.contains(&name) { if name.starts_with(git::PREFIX) { let git = GitIndex::new(self.ctx, &path); - self.store_meta(git.meta()?, SourceType::Git, name.clone())?; + self.store_meta(git.meta()?, SourceType::Git, name.clone()); } else if name.starts_with(chain::PREFIX) { let chain = ChainIndex::new(self.ctx, &path); - self.store_meta(chain.meta()?, SourceType::Chain, name.clone())?; + self.store_meta(chain.meta()?, SourceType::Chain, name.clone()); chain.meta()?; } new_deps.insert(name.clone()); @@ -184,7 +184,7 @@ impl<'a> Index<'a> { } } - self.store_meta(files_meta, SourceType::Chain, name)?; + self.store_meta(files_meta, SourceType::Chain, name); if !resolve(self, import, deps)? { return Err(anyhow!("Failed to resolve dependency:{:?}", import)); @@ -220,7 +220,7 @@ impl<'a> Index<'a> { self.ctx.dialect.as_ref(), )?; - self.store_meta(vec![meta], SourceType::Local, dep_name.clone())?; + self.store_meta(vec![meta], SourceType::Local, dep_name.clone()); } Ok(()) } @@ -257,12 +257,7 @@ impl<'a> Index<'a> { Ok(()) } - fn store_meta( - &mut self, - f_meta: Vec, - src_type: SourceType, - dep_name: Rc, - ) -> Result<(), Error> { + fn store_meta(&mut self, f_meta: Vec, src_type: SourceType, dep_name: Rc) { for file in f_meta { for unit in file.meta { let name = Rc::new(unit.module_id); @@ -283,7 +278,6 @@ impl<'a> Index<'a> { ); } } - Ok(()) } } diff --git a/dove/src/index/resolver/chain/loader.rs b/dove/src/index/resolver/chain/loader.rs index 4647a204..e1b76950 100644 --- a/dove/src/index/resolver/chain/loader.rs +++ b/dove/src/index/resolver/chain/loader.rs @@ -105,7 +105,7 @@ where /// Tries to load the module from the local cache. /// Then tries to load the module from the external module source if the module doesn't exist in cache. pub fn get(&self, module_id: &ModuleId) -> Result> { - let name = self.make_local_name(&module_id)?; + let name = self.make_local_name(&module_id); if let Some(cache_path) = &self.cache_path { let local_path = cache_path.join(name); @@ -128,12 +128,12 @@ where } } - fn make_local_name(&self, module_id: &ModuleId) -> Result { + fn make_local_name(&self, module_id: &ModuleId) -> String { let mut digest = Sha3::v256(); digest.update(module_id.name().as_bytes()); digest.update(module_id.address().as_ref()); let mut output = [0; 32]; digest.finalize(&mut output); - Ok(hex::encode(&output)) + hex::encode(&output) } } diff --git a/dove/src/lib.rs b/dove/src/lib.rs index 4c0128ff..66116090 100644 --- a/dove/src/lib.rs +++ b/dove/src/lib.rs @@ -4,6 +4,7 @@ #[macro_use] extern crate anyhow; +#[macro_use] extern crate log; /// Dove commands handler. diff --git a/dove/src/manifest.rs b/dove/src/manifest.rs index b4abd676..1ccfcc53 100644 --- a/dove/src/manifest.rs +++ b/dove/src/manifest.rs @@ -55,6 +55,7 @@ impl Default for Package { } } +#[allow(clippy::unnecessary_wraps)] fn dialect() -> Option { Some(default_dialect()) } @@ -79,6 +80,10 @@ fn script_output() -> String { "target/scripts".to_owned() } +fn transaction_output() -> String { + "target/transactions".to_owned() +} + fn target_deps() -> String { "target/.external".to_owned() } @@ -91,6 +96,7 @@ fn index() -> String { ".Dove.man".to_owned() } +#[allow(clippy::unnecessary_wraps)] fn code_code_address() -> Option { Some(format!("0x{}", CORE_CODE_ADDRESS)) } @@ -118,6 +124,10 @@ pub struct Layout { #[serde(default = "script_output")] pub script_output: String, + /// Directory with transactions. + #[serde(default = "transaction_output")] + pub transaction_output: String, + /// Directory with external dependencies. #[serde(default = "target_deps")] pub target_deps: String, @@ -138,6 +148,7 @@ impl Default for Layout { tests_dir: tests_dir(), module_output: module_output(), script_output: script_output(), + transaction_output: transaction_output(), target_deps: target_deps(), target: target(), index: index(), diff --git a/lang/disassembler/src/code/exp/mod.rs b/lang/disassembler/src/code/exp/mod.rs index 6240fb55..bd7c0a0d 100644 --- a/lang/disassembler/src/code/exp/mod.rs +++ b/lang/disassembler/src/code/exp/mod.rs @@ -298,7 +298,7 @@ where let sorted_index_list = range_list .into_iter() .map(|p| p.source_range()) - .filter_map(|p| p) + .flatten() .flat_map(|p| vec![p.0, p.1]) .sorted() .collect::>(); diff --git a/lang/src/lib.rs b/lang/src/lib.rs index a35cdf67..785ea38d 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -8,3 +8,4 @@ pub extern crate disassembler; pub mod builder; pub mod checker; pub mod compiler; +pub mod meta_extractor; diff --git a/lang/src/meta_extractor.rs b/lang/src/meta_extractor.rs new file mode 100644 index 00000000..a7476091 --- /dev/null +++ b/lang/src/meta_extractor.rs @@ -0,0 +1,144 @@ +use crate::compiler::{CompileFlow, Step, compile}; +use anyhow::Error; +use crate::compiler::parser::{ParserArtifact, ParsingMeta}; +use libra::module::{CompiledUnit, Definition}; +use libra::move_lang::errors::Errors; +use crate::compiler::error::CompilerError; +use crate::compiler::dialects::Dialect; +use crate::compiler::file::MoveFile; +use libra::move_lang::parser::ast::{Script, Type, Type_, ModuleAccess_}; + +pub struct ScriptMetadata; + +impl ScriptMetadata { + pub fn extract(dialect: &dyn Dialect, script: &MoveFile) -> Result, Error> { + compile(dialect, &[script.to_owned()], &[], None, ScriptMetadata) + } +} + +impl CompileFlow, Error>> for ScriptMetadata { + fn after_parsing( + &mut self, + parser_artifact: ParserArtifact, + ) -> Step, Error>, ParserArtifact> { + let result = parser_artifact.result; + let source_map = parser_artifact.meta.source_map; + let offsets_map = parser_artifact.meta.offsets_map; + Step::Stop( + result + .map_err(|err| { + CompilerError { + source_map, + errors: offsets_map.transform(err), + } + .into() + }) + .map(|prog| { + prog.source_definitions + .into_iter() + .filter_map(|def| { + if let Definition::Script(script) = def { + Some(make_script_meta(script)) + } else { + None + } + }) + .collect::>() + }), + ) + } + + fn after_translate( + &mut self, + _: ParsingMeta, + _: Result, Errors>, + ) -> Result, Error> { + Ok(vec![]) + } +} + +fn make_script_meta(script: Script) -> Meta { + let func = script.function; + let type_parameters = func + .signature + .type_parameters + .into_iter() + .map(|tp| tp.0.value) + .collect(); + let parameters = func + .signature + .parameters + .into_iter() + .map(|(var, tp)| (var.0.value, extract_type_name(tp))) + .collect(); + Meta { + name: func.name.0.value, + type_parameters, + parameters, + } +} + +fn extract_type_name(tp: Type) -> String { + match tp.value { + Type_::Apply(name, types) => { + let mut tp = match name.value { + ModuleAccess_::Name(name) => name.value, + ModuleAccess_::ModuleAccess(module, name) => { + format!("{}::{}", module.0.value, name.value) + } + ModuleAccess_::QualifiedModuleAccess(module, name) => { + let module = module.0.value; + format!("{}::{}::{}", module.address, module.name.0, name.value) + } + }; + if !types.is_empty() { + tp.push('<'); + tp.push_str( + &types + .into_iter() + .map(extract_type_name) + .collect::>() + .join(", "), + ); + tp.push('>'); + } + tp + } + Type_::Ref(is_mut, tp) => { + if is_mut { + format!("&mut {}", extract_type_name(*tp)) + } else { + format!("&{}", extract_type_name(*tp)) + } + } + Type_::Fun(types, tp) => { + format!( + "({}):{}", + types + .into_iter() + .map(extract_type_name) + .collect::>() + .join(", "), + extract_type_name(*tp) + ) + } + Type_::Unit => "()".to_owned(), + Type_::Multiple(types) => { + format!( + "({})", + types + .into_iter() + .map(extract_type_name) + .collect::>() + .join(", ") + ) + } + } +} + +#[derive(Debug)] +pub struct Meta { + pub name: String, + pub type_parameters: Vec, + pub parameters: Vec<(String, String)>, +} diff --git a/language_server/src/main_loop.rs b/language_server/src/main_loop.rs index 07151df7..c19196ba 100644 --- a/language_server/src/main_loop.rs +++ b/language_server/src/main_loop.rs @@ -178,15 +178,7 @@ pub fn loop_turn( } Event::Lsp(message) => { match message { - Message::Request(req) => { - on_request( - global_state, - pool, - resp_events_sender, - &connection.sender, - req, - )?; - } + Message::Request(_) => {} Message::Notification(not) => { on_notification(&connection.sender, fs_events_sender, loop_state, not)?; } @@ -235,22 +227,6 @@ pub fn loop_turn( Ok(()) } -#[allow(unused)] -fn on_request( - global_state: &mut GlobalState, - pool: &ThreadPool, - task_sender: &Sender, - msg_sender: &Sender, - req: Request, -) -> Result<()> { - // let mut pool_dispatcher = - // PoolDispatcher::new(req, pool, global_state, msg_sender, task_sender); - // pool_dispatcher - // .on::(handlers::handle_completion)? - // .finish(); - Ok(()) -} - fn diagnostic_as_string(d: &Diagnostic) -> String { format!( "({}, {}), ({}, {}): {}", diff --git a/resource-viewer/src/lib.rs b/resource-viewer/src/lib.rs new file mode 100644 index 00000000..4b15559d --- /dev/null +++ b/resource-viewer/src/lib.rs @@ -0,0 +1,5 @@ +#[macro_use] +extern crate anyhow; + +pub mod ser; +pub mod tte; diff --git a/resource-viewer/src/main.rs b/resource-viewer/src/main.rs index 199931d4..1b27a6ae 100644 --- a/resource-viewer/src/main.rs +++ b/resource-viewer/src/main.rs @@ -7,8 +7,6 @@ #[macro_use] extern crate log; -#[macro_use] -extern crate anyhow; use std::path::{Path, PathBuf}; use anyhow::{Result, Error, anyhow}; @@ -18,9 +16,7 @@ use libra::prelude::*; use lang::compiler::bech32::{bech32_into_libra, HRP}; use dnclient::blocking as net; use libra::rv; - -mod ser; -mod tte; +use move_resource_viewer::{tte, ser}; const VERSION: &str = git_hash::crate_version_with_git_hash_short!(); const JSON_SCHEMA_STDOUT: &str = "-"; diff --git a/resource-viewer/src/ser.rs b/resource-viewer/src/ser.rs index 088e3383..d1fc8c87 100644 --- a/resource-viewer/src/ser.rs +++ b/resource-viewer/src/ser.rs @@ -1,3 +1,5 @@ +#![allow(clippy::field_reassign_with_default)] + use libra::rv; use libra::prelude::*; use libra::move_core_types::language_storage::StructTag; diff --git a/resource-viewer/src/tte.rs b/resource-viewer/src/tte.rs index 79b8b192..af698c93 100644 --- a/resource-viewer/src/tte.rs +++ b/resource-viewer/src/tte.rs @@ -28,9 +28,9 @@ impl FromStr for TypeTagQuery { } } -impl Into<(TypeTag, Option)> for TypeTagQuery { - fn into(self) -> (TypeTag, Option) { - (self.tt, self.i) +impl From for (TypeTag, Option) { + fn from(query: TypeTagQuery) -> Self { + (query.tt, query.i) } } @@ -40,7 +40,7 @@ impl TypeTagQuery { } } -fn unwrap_spanned_ty(ty: Type) -> Result { +pub fn unwrap_spanned_ty(ty: Type) -> Result { fn unwrap_spanned_ty_(ty: Type, this: Option) -> Result { let st = match ty.value { Type_::Apply(ma, mut ty_params) => {