diff --git a/Cargo.lock b/Cargo.lock index a3cc61c3..0f186124 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2594,9 +2594,9 @@ dependencies = [ [[package]] name = "include_dir" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31a924bd335356c7622dff9ee33d06920afcf7f762a1a991236645e08c8a484b" +checksum = "24b56e147e6187d61e9d0f039f10e070d0c0a887e24fe0bb9ca3f29bfde62cab" dependencies = [ "glob", "include_dir_impl", @@ -2605,9 +2605,9 @@ dependencies = [ [[package]] name = "include_dir_impl" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afae3917f781921d7c7813992ccadff7e816f7e6ecb4b70a9ec3e740d51da3d6" +checksum = "0a0c890c85da4bab7bce4204c707396bbd3c6c8a681716a51c8814cfc2b682df" dependencies = [ "anyhow", "proc-macro-hack", diff --git a/dove/src/context.rs b/dove/src/context.rs index 4184f321..f75a2f72 100644 --- a/dove/src/context.rs +++ b/dove/src/context.rs @@ -10,6 +10,7 @@ use crate::index::Index; use crate::manifest::{default_dialect, DoveToml, MANIFEST, read_manifest}; use diem_crypto::hash::CryptoHash; use crate::index::interface::{InterfaceBuilder, Interface}; +use crate::metadata::MapDependencies; /// Project context. pub struct Context { @@ -63,6 +64,7 @@ impl Context { new_index.store(&index_path)?; new_index.remove_unused(old_index.diff(&new_index))?; new_index.remove_unnecessary_elements_in_dependencies(); + MapDependencies::create_and_save(self)?; new_index }; let builder = InterfaceBuilder::new(self, &index); @@ -102,18 +104,19 @@ impl Context { /// Returns interface files dir. pub fn interface_files_dir(&self) -> PathBuf { - self.path_for(&self.manifest.layout.artifacts) + self.path_for(&self.manifest.layout.system_folder) .join("interface_files_dir") } /// Returns directory for dependency bytecode. pub fn deps_mv_dir(&self) -> PathBuf { - self.path_for(&self.manifest.layout.artifacts).join("depmv") + self.path_for(&self.manifest.layout.system_folder) + .join("depmv") } /// Interface files lock. pub fn interface_files_lock(&self) -> PathBuf { - self.path_for(&self.manifest.layout.artifacts) + self.path_for(&self.manifest.layout.system_folder) .join("interface.lock") } } diff --git a/dove/src/lib.rs b/dove/src/lib.rs index 252242dc..0667d69b 100644 --- a/dove/src/lib.rs +++ b/dove/src/lib.rs @@ -36,6 +36,8 @@ pub mod executor; pub mod index; /// Dove configuration. pub mod manifest; +/// Metadata from project +pub mod metadata; /// StdOut stream pub mod stdout; #[doc(hidden)] diff --git a/dove/src/manifest.rs b/dove/src/manifest.rs index 47b0ec04..7e0a13e9 100644 --- a/dove/src/manifest.rs +++ b/dove/src/manifest.rs @@ -129,7 +129,7 @@ fn artifacts() -> String { } fn index() -> String { - format!("{}{}{}", artifacts(), MS, ".DoveIndex.toml") + format!("{}{}{}", system_folder(), MS, ".DoveIndex.toml") } fn storage_dir() -> String { @@ -144,6 +144,14 @@ fn code_code_address() -> String { "0x1".to_string() } +fn system_folder() -> String { + format!("{}{}{}", artifacts(), MS, ".system") +} + +fn project_map() -> String { + format!("{}{}{}", system_folder(), MS, "project.map") +} + /// Project layout. #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] pub struct Layout { @@ -203,6 +211,14 @@ pub struct Layout { /// Path to pover settings #[serde(default = "prover_toml")] pub prover_toml: String, + + /// Path t + #[serde(default = "system_folder")] + pub system_folder: String, + + /// Path to project map + #[serde(default = "project_map")] + pub project_map: String, } impl Layout { @@ -225,6 +241,8 @@ impl Layout { storage_dir: ctx.str_path_for(&self.storage_dir)?, exe_build_dir: ctx.str_path_for(&self.exe_build_dir)?, prover_toml: ctx.str_path_for(&self.prover_toml)?, + system_folder: ctx.str_path_for(&self.system_folder)?, + project_map: ctx.str_path_for(&self.project_map)?, }) } } @@ -248,6 +266,8 @@ impl Default for Layout { storage_dir: storage_dir(), exe_build_dir: exe_build_dir(), prover_toml: prover_toml(), + system_folder: system_folder(), + project_map: project_map(), } } } diff --git a/dove/src/metadata.rs b/dove/src/metadata.rs new file mode 100644 index 00000000..2be69ed2 --- /dev/null +++ b/dove/src/metadata.rs @@ -0,0 +1,148 @@ +use std::fs; +use std::fs::{create_dir_all, read_to_string}; +use anyhow::Error; +use serde::{Deserialize, Serialize}; +use lang::compiler::metadata::{Unit, parse, FuncMeta, ModuleMeta}; +use lang::compiler::file::is_move_file; +use crate::context::Context; +use std::path::PathBuf; +use crate::manifest::DoveToml; + +/// All modules and scripts from dependencies +pub(crate) fn get_all_dependencies(ctx: &Context) -> Result, Error> { + let external_folder = ctx.path_for(&ctx.manifest.layout.deps); + if !external_folder.exists() { + return Ok(Vec::new()); + } + + let sender = ctx.account_address_str()?; + let dialect = ctx.dialect.as_ref(); + + let externals = external_folder + .read_dir()? + .filter_map(|path| path.ok()) + .map(|path| path.path()) + .filter(|path| { + path.is_dir() + && path + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default() + .starts_with("git_") + }) + .map(test_folder) + .map(|(project_path, test_path)| { + move_files_without_tests(project_path, test_path.as_ref()) + }) + .flatten() + .collect::>(); + + let meta = externals + .iter() + .filter_map(|path| parse(&path.to_string_lossy(), dialect, &sender).ok()) + .flatten() + .collect(); + + Ok(meta) +} + +fn test_folder(path: PathBuf) -> (PathBuf, Option) { + let dove_path = path.join("Dove.toml"); + if !dove_path.exists() { + return (path, None); + } + + let test = read_to_string(dove_path) + .ok() + .and_then(|str| toml::from_str::(&str).ok()) + .map(|dovetoml| path.join(dovetoml.layout.tests_dir)); + + (path, test) +} +fn move_files_without_tests( + project_folder: PathBuf, + test_folder: Option<&PathBuf>, +) -> Vec { + project_folder + .read_dir() + .ok() + .map(|read| { + read.into_iter() + .filter_map(|path| path.ok()) + .map(|path| path.path()) + .filter(|path| { + !test_folder + .map(|test| path.starts_with(test)) + .unwrap_or(false) + }) + .filter_map(|path| { + if path.is_dir() { + Some(move_files_without_tests(path, test_folder)) + } else if path.is_file() && is_move_file(&path) { + Some(vec![path]) + } else { + None + } + }) + .flatten() + .collect::>() + }) + .unwrap_or_default() +} + +/// All scripts and modules from dependencies +#[derive(Debug, Serialize, Deserialize)] +pub struct MapDependencies { + /// All scripts from dependencies + pub scripts: Vec, + /// All modules from dependencies + pub modules: Vec, +} +impl MapDependencies { + /// Create a dependencies map and save it to disk + pub fn create_and_save(ctx: &Context) -> Result<(), Error> { + let fpath = ctx.path_for(&ctx.manifest.layout.project_map); + if !fpath.exists() { + let parent = fpath.parent().map_or_else( + || anyhow::bail!("The path to the dependencies map is set incorrectly"), + Ok, + )?; + if !parent.exists() { + create_dir_all(parent)?; + } + } + + let map = Self::create(ctx)?; + let bmap = bcs::to_bytes(&map)?; + fs::write(&fpath, bmap).map_err(|err| anyhow!("{}", err.to_string())) + } + + /// Get all scripts and modules from dependencies + pub fn create(ctx: &Context) -> Result { + let deps = get_all_dependencies(ctx)?; + + let (scripts, modules) = deps.into_iter().fold( + (Vec::new(), Vec::new()), + |(mut scripts, mut modules), unit| { + match unit { + Unit::Module(module) => modules.push(module), + Unit::Script(script) => scripts.push(script), + } + (scripts, modules) + }, + ); + + Ok(MapDependencies { scripts, modules }) + } + + /// Download a dependencies map from disk + pub fn load(ctx: &Context) -> Result { + let fpath = ctx.path_for(&ctx.manifest.layout.project_map); + if !fpath.exists() { + anyhow::bail!("The project map file was not found. Build a project."); + } + + let bmap = fs::read(&fpath)?; + bcs::from_bytes::(&bmap).map_err(|err| anyhow!("{:?}", err)) + } +} diff --git a/dove/src/tx/fn_call.rs b/dove/src/tx/fn_call.rs index fdfe72df..6ce3903d 100644 --- a/dove/src/tx/fn_call.rs +++ b/dove/src/tx/fn_call.rs @@ -58,8 +58,12 @@ pub(crate) fn make_script_call( let (path, meta) = select_function(scripts, addr, &type_tag, &args, &cfg)?; - let (signers, args) = - prepare_function_signature(&meta.parameters, &args, !cfg.deny_signers_definition, addr)?; + let (signers, args) = prepare_function_signature( + &meta.value.parameters, + &args, + !cfg.deny_signers_definition, + addr, + )?; let (signers, mut tx) = match signers { Signers::Explicit(signers) => ( @@ -141,8 +145,12 @@ pub(crate) fn make_function_call( let addr = ctx.account_address()?; let (_, meta) = select_function(functions, addr, &type_tag, &args, &cfg)?; - let (signers, args) = - prepare_function_signature(&meta.parameters, &args, !cfg.deny_signers_definition, addr)?; + let (signers, args) = prepare_function_signature( + &meta.value.parameters, + &args, + !cfg.deny_signers_definition, + addr, + )?; let tx_name = format!("{}_{}", module, func); let (signers, tx) = match signers { @@ -191,10 +199,10 @@ fn select_function( } else if func.len() > 1 { let mut func = func .into_iter() - .filter(|(_, f)| f.type_parameters.len() == type_tag.len()) + .filter(|(_, f)| f.value.type_parameters.len() == type_tag.len()) .filter(|(_, f)| { prepare_function_signature( - &f.parameters, + &f.value.parameters, args, !cfg.deny_signers_definition, addr, diff --git a/dove/src/tx/resolver.rs b/dove/src/tx/resolver.rs index e11b0024..4fdbd3ef 100644 --- a/dove/src/tx/resolver.rs +++ b/dove/src/tx/resolver.rs @@ -1,12 +1,12 @@ +use std::fs; +use std::path::PathBuf; +use anyhow::Error; +use regex::Regex; use move_core_types::account_address::AccountAddress; use move_core_types::identifier::Identifier; -use crate::context::Context; -use anyhow::Error; use lang::compiler::file::find_move_files; -use std::fs; -use regex::Regex; -use lang::compiler::metadata::{module_meta, FuncMeta, script_meta}; -use std::path::PathBuf; +use lang::compiler::metadata::{module_meta, script_meta, FuncMeta}; +use crate::context::Context; pub(crate) fn find_module_function( ctx: &Context, @@ -41,12 +41,12 @@ pub(crate) fn find_module_function( }) .flat_map(|(p, m)| { m.into_iter() - .filter(|m| m.address == *address && &m.name == m_name) - .flat_map(|m| m.funs) - .filter(|f| &f.name == f_name) + .filter(|m| m.value.address == *address && &m.value.name == m_name) + .flat_map(|m| m.value.funs) + .filter(|f| &f.value.name == f_name) .filter(|f| { if script_only { - f.visibility.is_script() + f.value.visibility.is_script() } else { false } @@ -80,7 +80,7 @@ pub(crate) fn find_script( }) .flat_map(|(p, m)| { m.into_iter() - .filter(|m| &m.name == name) + .filter(|m| &m.value.name == name) .map(|m| (p.to_owned(), m)) .collect::>() }) diff --git a/lang/src/compiler/file.rs b/lang/src/compiler/file.rs index eb9ef55d..2394a728 100644 --- a/lang/src/compiler/file.rs +++ b/lang/src/compiler/file.rs @@ -34,7 +34,7 @@ impl<'a, P: AsRef> Iterator for Files<'a, P> { loop { if let Some(entry) = self.next_entry() { if let Ok(entry) = entry { - if is_move_file(&entry) { + if is_move_file(entry.path()) { return Some(entry.into_path()); } } @@ -44,19 +44,15 @@ impl<'a, P: AsRef> Iterator for Files<'a, P> { } } } - -fn is_move_file(entry: &DirEntry) -> bool { - entry.file_type().is_file() +/// Check whether the file is a move +pub fn is_move_file(entry: &Path) -> bool { + entry.is_file() && !entry .file_name() - .to_str() + .and_then(|name| name.to_str()) .map(|name| name.starts_with('.')) .unwrap_or(true) - && entry - .path() - .extension() - .map(|ext| ext.eq("move")) - .unwrap_or(false) + && entry.extension().map(|ext| ext.eq("move")).unwrap_or(false) } pub fn find_move_files

(paths: &[P]) -> Files

@@ -78,7 +74,7 @@ where .into_iter() .filter_map(|entry| match entry { Ok(entry) => { - if is_move_file(&entry) { + if is_move_file(entry.path()) { let path = entry.into_path(); if filter(&path) { Some(Ok(path)) diff --git a/lang/src/compiler/metadata.rs b/lang/src/compiler/metadata.rs deleted file mode 100644 index a16f503c..00000000 --- a/lang/src/compiler/metadata.rs +++ /dev/null @@ -1,451 +0,0 @@ -use std::collections::HashMap; - -use anyhow::Error; -use move_core_types::account_address::AccountAddress; -use move_lang::{leak_str, parse_file}; -use move_lang::errors::{FilesSourceText, output_errors}; -use move_lang::parser::ast::{ - Definition, Script, Type, Type_, NameAccessChain_, LeadingNameAccess_, ModuleDefinition, - ModuleMember, Visibility as AstVisibility, -}; - -use crate::compiler::dialects::Dialect; -use crate::compiler::preprocessor::BuilderPreprocessor; -use codespan_reporting::term::termcolor::{StandardStream, ColorChoice}; -use move_core_types::identifier::Identifier; - -#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)] -pub struct FuncMeta { - pub name: Identifier, - pub visibility: Visibility, - pub type_parameters: Vec, - pub parameters: Vec<(String, String)>, -} - -pub fn script_meta( - script_path: &str, - dialect: &dyn Dialect, - sender: &str, -) -> Result, Error> { - Ok(parse(script_path, dialect, sender)? - .into_iter() - .filter_map(|def| { - if let Definition::Script(script) = def { - make_script_meta(script).ok() - } else { - None - } - }) - .collect::>()) -} - -#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)] -pub enum Visibility { - Public, - Script, - Friend, - Internal, -} - -impl Visibility { - pub fn is_script(&self) -> bool { - matches!(self, Visibility::Script) - } -} - -#[derive(Debug, Ord, PartialOrd, Eq, PartialEq)] -pub struct ModuleMeta { - pub address: AccountAddress, - pub name: Identifier, - pub funs: Vec, -} - -pub fn module_meta( - module_path: &str, - dialect: &dyn Dialect, - sender: &str, -) -> Result, Error> { - let mut modules = Vec::new(); - - for def in parse(module_path, dialect, sender)? { - match def { - Definition::Module(module) => { - modules.push(parse_module_definition(module, None)?); - } - Definition::Address(def) => { - let addr = match def.addr.value { - LeadingNameAccess_::AnonymousAddress(addr) => { - Some(AccountAddress::new(addr.into_bytes())) - } - LeadingNameAccess_::Name(_) => def - .addr_value - .map(|addr| AccountAddress::new(addr.value.into_bytes())), - }; - for def in def.modules { - modules.push(parse_module_definition(def, addr)?); - } - } - Definition::Script(_) => { - // no-op - } - } - } - Ok(modules) -} - -fn parse( - script_path: &str, - dialect: &dyn Dialect, - sender: &str, -) -> Result, Error> { - let mut preprocessor = BuilderPreprocessor::new(dialect, sender); - let mut files: FilesSourceText = HashMap::new(); - let (defs, _, errors) = parse_file(&mut files, leak_str(script_path), &mut preprocessor)?; - if errors.is_empty() { - Ok(defs) - } else { - let errors = preprocessor.transform(errors); - let mut writer = StandardStream::stderr(ColorChoice::Auto); - output_errors(&mut writer, files, errors); - Err(anyhow!("Could not compile scripts '{}'.", script_path)) - } -} - -fn make_script_meta(script: Script) -> Result { - 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(); - Ok(FuncMeta { - name: Identifier::new(func.name.0.value)?, - visibility: Visibility::Script, - type_parameters, - parameters, - }) -} - -fn extract_type_name(tp: Type) -> String { - match tp.value { - Type_::Apply(name, types) => { - let mut tp = match name.value { - NameAccessChain_::One(name) => name.value, - NameAccessChain_::Two(access, name) => { - format!("{}::{}", access.value, name.value) - } - NameAccessChain_::Three(access, name) => { - let (address, m_name) = access.value; - format!("{}::{}::{}", address, m_name, 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(", ") - ) - } - } -} - -fn parse_module_definition( - module: ModuleDefinition, - adds: Option, -) -> Result { - let ModuleDefinition { - address, - name, - members, - .. - } = module; - - let address = address.and_then(|addr| { - match addr.value { - LeadingNameAccess_::AnonymousAddress(addr) => Some(AccountAddress::new(addr.into_bytes())), - LeadingNameAccess_::Name(_) => adds, - } - }).or(adds) - .ok_or_else(|| anyhow!("Failed to parse module definition. The module {} does not contain an address definition.", name.0.value))?; - - let funs = members - .into_iter() - .filter_map(|member| match member { - ModuleMember::Function(func) => { - 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(); - - let visibility = match func.visibility { - AstVisibility::Public(_) => Visibility::Public, - AstVisibility::Script(_) => Visibility::Script, - AstVisibility::Friend(_) => Visibility::Friend, - AstVisibility::Internal => Visibility::Internal, - }; - - Some(FuncMeta { - name: Identifier::new(func.name.0.value).expect("Valid identifier"), - visibility, - type_parameters, - parameters, - }) - } - _ => None, - }) - .collect(); - - Ok(ModuleMeta { - address, - name: Identifier::new(name.0.value)?, - funs, - }) -} - -#[cfg(test)] -mod metadata_tests { - use crate::compiler::metadata::{module_meta, ModuleMeta, FuncMeta, Visibility, script_meta}; - use crate::compiler::dialects::DialectName; - use tempfile::NamedTempFile; - use std::io::Write; - use move_core_types::language_storage::CORE_CODE_ADDRESS; - use move_core_types::identifier::Identifier; - use move_core_types::account_address::AccountAddress; - - #[test] - fn test_module_meta() { - let source = r" -address 0x1 { -module Empty {} - -module FuncsVisability { - fun f1() {} - - public fun f2() {} - - public(script) fun f3() {} - - public(friend) fun f4() {} - native fun f5(); - native public fun f6(); -} -} - -module 0x2::FuncsTp { - public(script) fun f1() {} - public(script) fun f2() {} -} - -module 0x3::FuncsArgs { - public(script) fun f1() {} - public(script) fun f2(_d: signer, d: u8) {} -} - "; - let mut module = NamedTempFile::new().unwrap(); - module.write_all(source.as_bytes()).unwrap(); - - let dialect = DialectName::Pont.get_dialect(); - - let defs = module_meta( - module.path().to_string_lossy().as_ref(), - dialect.as_ref(), - "0x1", - ) - .unwrap(); - - assert_eq!( - defs, - vec![ - ModuleMeta { - address: CORE_CODE_ADDRESS, - name: Identifier::new("Empty").unwrap(), - funs: vec![], - }, - ModuleMeta { - address: CORE_CODE_ADDRESS, - name: Identifier::new("FuncsVisability").unwrap(), - funs: vec![ - FuncMeta { - name: Identifier::new("f1").unwrap(), - visibility: Visibility::Internal, - type_parameters: vec![], - parameters: vec![], - }, - FuncMeta { - name: Identifier::new("f2").unwrap(), - visibility: Visibility::Public, - type_parameters: vec![], - parameters: vec![], - }, - FuncMeta { - name: Identifier::new("f3").unwrap(), - visibility: Visibility::Script, - type_parameters: vec![], - parameters: vec![], - }, - FuncMeta { - name: Identifier::new("f4").unwrap(), - visibility: Visibility::Friend, - type_parameters: vec![], - parameters: vec![], - }, - FuncMeta { - name: Identifier::new("f5").unwrap(), - visibility: Visibility::Internal, - type_parameters: vec![], - parameters: vec![], - }, - FuncMeta { - name: Identifier::new("f6").unwrap(), - visibility: Visibility::Public, - type_parameters: vec![], - parameters: vec![], - }, - ], - }, - ModuleMeta { - address: AccountAddress::from_hex_literal("0x2").unwrap(), - name: Identifier::new("FuncsTp").unwrap(), - funs: vec![ - FuncMeta { - name: Identifier::new("f1").unwrap(), - visibility: Visibility::Script, - type_parameters: vec!["T".to_string(), "D".to_string()], - parameters: vec![], - }, - FuncMeta { - name: Identifier::new("f2").unwrap(), - visibility: Visibility::Script, - type_parameters: vec![], - parameters: vec![], - }, - ], - }, - ModuleMeta { - address: AccountAddress::from_hex_literal("0x3").unwrap(), - name: Identifier::new("FuncsArgs").unwrap(), - funs: vec![ - FuncMeta { - name: Identifier::new("f1").unwrap(), - visibility: Visibility::Script, - type_parameters: vec![], - parameters: vec![], - }, - FuncMeta { - name: Identifier::new("f2").unwrap(), - visibility: Visibility::Script, - type_parameters: vec![], - parameters: vec![ - ("_d".to_string(), "signer".to_string(),), - ("d".to_string(), "u8".to_string(),), - ], - }, - ], - }, - ] - ); - } - - #[test] - fn test_script_meta() { - let source = r" - script { - fun main() { - } - } - script { - fun main_1(_d: signer) { - } - } - script { - fun main_2(_d: signer) { - } - } - "; - - let mut module = NamedTempFile::new().unwrap(); - module.write_all(source.as_bytes()).unwrap(); - - let dialect = DialectName::Pont.get_dialect(); - - let defs = script_meta( - module.path().to_string_lossy().as_ref(), - dialect.as_ref(), - "0x1", - ) - .unwrap(); - assert_eq!( - defs, - vec![ - FuncMeta { - name: Identifier::new("main").unwrap(), - visibility: Visibility::Script, - type_parameters: vec![], - parameters: vec![], - }, - FuncMeta { - name: Identifier::new("main_1").unwrap(), - visibility: Visibility::Script, - type_parameters: vec![], - parameters: vec![("_d".to_string(), "signer".to_string(),)], - }, - FuncMeta { - name: Identifier::new("main_2").unwrap(), - visibility: Visibility::Script, - type_parameters: vec!["T".to_string()], - parameters: vec![("_d".to_string(), "signer".to_string(),)], - }, - ] - ); - } -} diff --git a/lang/src/compiler/metadata/mod.rs b/lang/src/compiler/metadata/mod.rs new file mode 100644 index 00000000..a316a163 --- /dev/null +++ b/lang/src/compiler/metadata/mod.rs @@ -0,0 +1,737 @@ +use std::collections::HashMap; +use serde::{Deserialize, Serialize}; +use anyhow::Error; +use move_core_types::account_address::AccountAddress; +use move_lang::{leak_str, parse_file, MatchedFileCommentMap}; +use move_lang::errors::{FilesSourceText, output_errors}; +use move_lang::parser::ast::{ + Definition, Script, Type, Type_, NameAccessChain_, LeadingNameAccess_, ModuleDefinition, + ModuleMember, Visibility as AstVisibility, StructFields, +}; + +use crate::compiler::dialects::Dialect; +use crate::compiler::preprocessor::BuilderPreprocessor; +use codespan_reporting::term::termcolor::{StandardStream, ColorChoice}; +use move_core_types::identifier::Identifier; +use move_lang::shared::Identifier as Iden; + +pub mod spanned; +use spanned::Spanned; + +#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)] +pub enum Unit { + Module(ModuleMeta), + Script(FuncMeta), +} +impl Unit { + /// Converts from Unit to Option. + pub fn module(&self) -> Option<&ModuleMeta> { + match self { + Unit::Module(module) => Some(module), + _ => None, + } + } + /// Converts from Unit to Option. + pub fn script(&self) -> Option<&FuncMeta> { + match self { + Unit::Script(script) => Some(script), + _ => None, + } + } +} +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)] +pub struct FuncMeta_ { + pub name: Identifier, + pub visibility: Visibility, + pub type_parameters: Vec, + pub parameters: Vec<(String, String)>, + pub doc: Option, +} + +pub type FuncMeta = Spanned; + +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)] +pub struct StructMeta_ { + /// struct Name { ... } + pub name: Identifier, + /// struct Name has ability1, ability2 { ... } + pub abilities: Vec, + /// struct Name { ... } + pub type_parameters: Vec<(String, Vec)>, + /// struct Example { + /// /// doc for field + /// field1: type, + /// field2: u8, + /// field3: u64, + /// ... + /// } + pub fields: Vec, + /// /// Doc text + /// struct Example { + /// ... + /// } + pub doc: Option, +} + +pub type StructMeta = Spanned; + +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)] +pub struct StructMetaField_ { + pub name: Identifier, + pub type_field: String, + pub doc: Option, +} + +pub type StructMetaField = Spanned; + +fn processing_docs(docs: &mut MatchedFileCommentMap) { + docs.iter_mut() + .for_each(|(_, doc)| *doc = doc.trim().to_string()); + docs.retain(|_, doc| !doc.is_empty()); +} + +pub fn script_meta( + script_path: &str, + dialect: &dyn Dialect, + sender: &str, +) -> Result, Error> { + parse(script_path, dialect, sender).map(|list| { + list.iter() + .filter_map(|unit| unit.script()) + .cloned() + .collect::>() + }) +} + +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)] +pub enum Visibility { + Public, + Script, + Friend, + Internal, +} + +impl Visibility { + pub fn is_script(&self) -> bool { + matches!(self, Visibility::Script) + } +} + +#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Serialize, Deserialize)] +pub struct ModuleMeta_ { + pub address: AccountAddress, + pub name: Identifier, + pub funs: Vec, + pub structs: Vec, + pub doc: Option, +} + +pub type ModuleMeta = Spanned; + +pub fn module_meta( + module_path: &str, + dialect: &dyn Dialect, + sender: &str, +) -> Result, Error> { + parse(module_path, dialect, sender).map(|list| { + list.iter() + .filter_map(|unit| unit.module()) + .cloned() + .collect() + }) +} + +/// Get metadata from move file +pub fn parse(script_path: &str, dialect: &dyn Dialect, sender: &str) -> Result, Error> { + let mut preprocessor = BuilderPreprocessor::new(dialect, sender); + let mut files: FilesSourceText = HashMap::new(); + let (defs, mut docs, errors) = + parse_file(&mut files, leak_str(script_path), &mut preprocessor)?; + + if !errors.is_empty() { + let errors = preprocessor.transform(errors); + let mut writer = StandardStream::stderr(ColorChoice::Auto); + output_errors(&mut writer, files, errors); + anyhow::bail!("Could not compile scripts '{}'.", script_path); + } + + let mut list_unit = Vec::new(); + processing_docs(&mut docs); + + for def in defs { + match def { + Definition::Module(module) => { + list_unit.push(Unit::Module(parse_module_definition(module, &docs, None)?)); + } + Definition::Address(def) => { + let addr = match def.addr.value { + LeadingNameAccess_::AnonymousAddress(addr) => { + Some(AccountAddress::new(addr.into_bytes())) + } + LeadingNameAccess_::Name(_) => def + .addr_value + .map(|addr| AccountAddress::new(addr.value.into_bytes())), + }; + for def in def.modules { + list_unit.push(Unit::Module(parse_module_definition(def, &docs, addr)?)); + } + } + Definition::Script(script) => { + list_unit.push(Unit::Script(make_script_meta(script, &docs)?)) + } + } + } + + Ok(list_unit) +} + +fn make_script_meta(script: Script, docs: &MatchedFileCommentMap) -> Result { + 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(); + Ok(Spanned::new( + func.loc.into(), + FuncMeta_ { + name: Identifier::new(func.name.0.value)?, + visibility: Visibility::Script, + type_parameters, + parameters, + doc: docs.get(&func.loc.span.start()).cloned(), + }, + )) +} + +fn extract_type_name(tp: Type) -> String { + match tp.value { + Type_::Apply(name, types) => { + let mut tp = match name.value { + NameAccessChain_::One(name) => name.value, + NameAccessChain_::Two(access, name) => { + format!("{}::{}", access.value, name.value) + } + NameAccessChain_::Three(access, name) => { + let (address, m_name) = access.value; + format!("{}::{}::{}", address, m_name, 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(", ") + ) + } + } +} + +fn parse_module_definition( + module: ModuleDefinition, + docs: &MatchedFileCommentMap, + adds: Option, +) -> Result { + let ModuleDefinition { + address, + name, + members, + .. + } = module; + + let address = address.and_then(|addr| { + match addr.value { + LeadingNameAccess_::AnonymousAddress(addr) => Some(AccountAddress::new(addr.into_bytes())), + LeadingNameAccess_::Name(_) => adds, + } + }).or(adds) + .ok_or_else(|| anyhow!("Failed to parse module definition. The module {} does not contain an address definition.", name.0.value))?; + + let funs = members + .iter() + .filter_map(|member| match member { + ModuleMember::Function(func) => { + let type_parameters = func + .signature + .type_parameters + .iter() + .map(|tp| tp.0.value.to_owned()) + .collect(); + let parameters = func + .signature + .parameters + .iter() + .map(|(var, tp)| (var.0.value.to_owned(), extract_type_name(tp.to_owned()))) + .collect(); + + let visibility = match func.visibility { + AstVisibility::Public(_) => Visibility::Public, + AstVisibility::Script(_) => Visibility::Script, + AstVisibility::Friend(_) => Visibility::Friend, + AstVisibility::Internal => Visibility::Internal, + }; + + Some(Spanned::new( + func.loc.into(), + FuncMeta_ { + name: Identifier::new(func.name.0.value.to_owned()) + .expect("Valid identifier"), + visibility, + type_parameters, + parameters, + doc: docs.get(&func.loc.span.start()).cloned(), + }, + )) + } + _ => None, + }) + .collect(); + + let structs = members + .into_iter() + .filter_map(|member| match member { + ModuleMember::Struct(struc) => { + let abilities = struc + .abilities + .iter() + .map(|ab| ab.value.to_string()) + .collect(); + let fields = match struc.fields { + StructFields::Defined(fields) => fields + .iter() + .map(|(name, tp)| { + Spanned::new( + name.loc().into(), + StructMetaField_ { + name: Identifier::new(name.to_string()) + .expect("Valid identifier"), + type_field: extract_type_name(tp.clone()), + doc: docs.get(&name.loc().span.start()).cloned(), + }, + ) + }) + .collect(), + _ => Vec::new(), + }; + let type_parameters = struc + .type_parameters + .iter() + .map(|(name, ab)| { + let ab = ab.iter().map(|ab| ab.to_string()).collect(); + (name.to_string(), ab) + }) + .collect(); + Some(Spanned::new( + struc.loc.into(), + StructMeta_ { + name: Identifier::new(struc.name.0.value).expect("Valid identifier"), + abilities, + type_parameters, + fields, + doc: docs.get(&struc.loc.span.start()).cloned(), + }, + )) + } + _ => None, + }) + .collect(); + + Ok(Spanned::new( + module.loc.into(), + ModuleMeta_ { + address, + name: Identifier::new(name.0.value)?, + funs, + structs, + doc: docs.get(&module.loc.span.start()).cloned(), + }, + )) +} + +#[cfg(test)] +mod metadata_tests { + use crate::compiler::metadata::{ + module_meta, ModuleMeta_, FuncMeta_, Visibility, script_meta, StructMeta_, + StructMetaField_, StructMetaField, Spanned, + }; + use crate::compiler::dialects::DialectName; + use tempfile::NamedTempFile; + use std::io::Write; + use move_core_types::language_storage::CORE_CODE_ADDRESS; + use move_core_types::identifier::Identifier; + use move_core_types::account_address::AccountAddress; + use codespan::Span; + use crate::compiler::metadata::spanned::Loc; + + fn spanned_wrap(value: T) -> Spanned { + Spanned::new(Loc::new("none".to_string(), Span::new(0, 0)), value) + } + fn create_field(name: &str, tp: &str, doc: Option<&str>) -> StructMetaField { + spanned_wrap(StructMetaField_ { + name: Identifier::new(name).unwrap(), + type_field: tp.to_string(), + doc: doc.map(|d| d.to_string()), + }) + } + + trait ToSpannded { + fn to_spanned(self) -> Spanned + where + Self: Sized, + { + spanned_wrap(self) + } + } + + impl ToSpannded for FuncMeta_ {} + impl ToSpannded for ModuleMeta_ {} + impl ToSpannded for StructMeta_ {} + + #[test] + fn test_module_meta() { + let source = r" + address 0x1 { + module Empty {} + + /// doc for module + module StructsModule{ + struct Empty {} + /// doc for stucture + struct Example { + /// doc for field + field1: u8, + field2: u64, + field3: address, + field4: bool, + field5: Empty + } + struct Example2 has copy, drop { + field1: bool, + field2: T + } + } + module FuncsVisability { + + struct MyStruct { + field1: bool, + } + /// doc for function + fun f1() {} + /* + not doc type comment + */ + public fun f2() {} + // not doc type comment + public(script) fun f3() {} + + public(friend) fun f4() {} + native fun f5(); + native public fun f6(); + } + } + + module 0x2::FuncsTp { + public(script) fun f1() {} + public(script) fun f2() {} + } + + module 0x3::FuncsArgs { + public(script) fun f1() {} + public(script) fun f2(_d: signer, d: u8) {} + }"; + let mut module = NamedTempFile::new().unwrap(); + module.write_all(source.as_bytes()).unwrap(); + + let dialect = DialectName::Pont.get_dialect(); + + let defs = module_meta( + module.path().to_string_lossy().as_ref(), + dialect.as_ref(), + "0x1", + ) + .unwrap(); + + assert_eq!( + defs, + vec![ + ModuleMeta_ { + address: CORE_CODE_ADDRESS, + name: Identifier::new("Empty").unwrap(), + funs: vec![], + structs: vec![], + doc: None + } + .to_spanned(), + ModuleMeta_ { + address: CORE_CODE_ADDRESS, + name: Identifier::new("StructsModule").unwrap(), + funs: vec![], + structs: vec![ + StructMeta_ { + name: Identifier::new("Empty").unwrap(), + abilities: vec![], + type_parameters: vec![], + fields: vec![], + doc: None + } + .to_spanned(), + StructMeta_ { + name: Identifier::new("Example").unwrap(), + abilities: vec![], + type_parameters: vec![], + fields: vec![ + create_field("field1", "u8", Some("doc for field")), + create_field("field2", "u64", None), + create_field("field3", "address", None), + create_field("field4", "bool", None), + create_field("field5", "Empty", None) + ], + doc: Some("doc for stucture".to_string()) + } + .to_spanned(), + StructMeta_ { + name: Identifier::new("Example2").unwrap(), + abilities: vec!["copy".to_string(), "drop".to_string()], + type_parameters: vec![( + "T".to_string(), + vec!["copy".to_string(), "drop".to_string()] + )], + fields: vec![ + create_field("field1", "bool", None), + create_field("field2", "T", None), + ], + doc: None + } + .to_spanned() + ], + doc: Some("doc for module".to_string()) + } + .to_spanned(), + ModuleMeta_ { + address: CORE_CODE_ADDRESS, + name: Identifier::new("FuncsVisability").unwrap(), + funs: vec![ + FuncMeta_ { + name: Identifier::new("f1").unwrap(), + visibility: Visibility::Internal, + type_parameters: vec![], + parameters: vec![], + doc: Some("doc for function".to_string()) + } + .to_spanned(), + FuncMeta_ { + name: Identifier::new("f2").unwrap(), + visibility: Visibility::Public, + type_parameters: vec![], + parameters: vec![], + doc: None + } + .to_spanned(), + FuncMeta_ { + name: Identifier::new("f3").unwrap(), + visibility: Visibility::Script, + type_parameters: vec![], + parameters: vec![], + doc: None + } + .to_spanned(), + FuncMeta_ { + name: Identifier::new("f4").unwrap(), + visibility: Visibility::Friend, + type_parameters: vec![], + parameters: vec![], + doc: None + } + .to_spanned(), + FuncMeta_ { + name: Identifier::new("f5").unwrap(), + visibility: Visibility::Internal, + type_parameters: vec![], + parameters: vec![], + doc: None + } + .to_spanned(), + FuncMeta_ { + name: Identifier::new("f6").unwrap(), + visibility: Visibility::Public, + type_parameters: vec![], + parameters: vec![], + doc: None + } + .to_spanned(), + ], + structs: vec![StructMeta_ { + name: Identifier::new("MyStruct").unwrap(), + abilities: vec![], + type_parameters: vec![], + fields: vec![create_field("field1", "bool", None)], + doc: None + } + .to_spanned()], + doc: None + } + .to_spanned(), + ModuleMeta_ { + address: AccountAddress::from_hex_literal("0x2").unwrap(), + name: Identifier::new("FuncsTp").unwrap(), + funs: vec![ + FuncMeta_ { + name: Identifier::new("f1").unwrap(), + visibility: Visibility::Script, + type_parameters: vec!["T".to_string(), "D".to_string()], + parameters: vec![], + doc: None + } + .to_spanned(), + FuncMeta_ { + name: Identifier::new("f2").unwrap(), + visibility: Visibility::Script, + type_parameters: vec![], + parameters: vec![], + doc: None + } + .to_spanned(), + ], + structs: vec![], + doc: None + } + .to_spanned(), + ModuleMeta_ { + address: AccountAddress::from_hex_literal("0x3").unwrap(), + name: Identifier::new("FuncsArgs").unwrap(), + funs: vec![ + FuncMeta_ { + name: Identifier::new("f1").unwrap(), + visibility: Visibility::Script, + type_parameters: vec![], + parameters: vec![], + doc: None + } + .to_spanned(), + FuncMeta_ { + name: Identifier::new("f2").unwrap(), + visibility: Visibility::Script, + type_parameters: vec![], + parameters: vec![ + ("_d".to_string(), "signer".to_string(),), + ("d".to_string(), "u8".to_string(),), + ], + doc: None + } + .to_spanned(), + ], + structs: vec![], + doc: None + } + .to_spanned(), + ] + ); + } + + #[test] + fn test_script_meta() { + let source = r" + script { + /// doc for function + fun main() { + } + } + script { + // not doc type comment + fun main_1(_d: signer) { + } + } + script { + /* + not doc type comment + */ + fun main_2(_d: signer) { + } + } + "; + + let mut module = NamedTempFile::new().unwrap(); + module.write_all(source.as_bytes()).unwrap(); + + let dialect = DialectName::Pont.get_dialect(); + + let defs = script_meta( + module.path().to_string_lossy().as_ref(), + dialect.as_ref(), + "0x1", + ) + .unwrap(); + assert_eq!( + defs, + vec![ + FuncMeta_ { + name: Identifier::new("main").unwrap(), + visibility: Visibility::Script, + type_parameters: vec![], + parameters: vec![], + doc: Some("doc for function".to_string()) + } + .to_spanned(), + FuncMeta_ { + name: Identifier::new("main_1").unwrap(), + visibility: Visibility::Script, + type_parameters: vec![], + parameters: vec![("_d".to_string(), "signer".to_string(),)], + doc: None + } + .to_spanned(), + FuncMeta_ { + name: Identifier::new("main_2").unwrap(), + visibility: Visibility::Script, + type_parameters: vec!["T".to_string()], + parameters: vec![("_d".to_string(), "signer".to_string(),)], + doc: None + } + .to_spanned(), + ] + ); + } +} diff --git a/lang/src/compiler/metadata/spanned.rs b/lang/src/compiler/metadata/spanned.rs new file mode 100644 index 00000000..447098ef --- /dev/null +++ b/lang/src/compiler/metadata/spanned.rs @@ -0,0 +1,130 @@ +use serde::{Deserialize, Serialize}; +use std::{ + cmp::Ordering, + fmt, + hash::{Hash, Hasher}, +}; +use codespan::Span; +use move_ir_types::location::Loc as LocDiem; + +//************************************************************************************************** +// Spanned +//************************************************************************************************** + +#[derive(Clone, Serialize, Deserialize)] +pub struct Spanned { + pub loc: Loc, + pub value: T, +} + +impl Spanned { + pub fn new(loc: Loc, value: T) -> Spanned { + Spanned { loc, value } + } + + const NO_LOC_FILE: &'static str = ""; + pub fn unsafe_no_loc(value: T) -> Spanned { + Spanned { + value, + loc: Loc::new(Self::NO_LOC_FILE.to_string(), Span::default()), + } + } +} + +impl PartialEq for Spanned { + fn eq(&self, other: &Spanned) -> bool { + self.value == other.value + } +} + +impl Eq for Spanned {} + +impl Hash for Spanned { + fn hash(&self, state: &mut H) { + self.value.hash(state); + } +} + +impl PartialOrd for Spanned { + fn partial_cmp(&self, other: &Spanned) -> Option { + self.value.partial_cmp(&other.value) + } +} + +impl Ord for Spanned { + fn cmp(&self, other: &Spanned) -> Ordering { + self.value.cmp(&other.value) + } +} + +impl fmt::Display for Spanned { + fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", &self.value) + } +} + +impl fmt::Debug for Spanned { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", &self.value) + } +} + +impl From for Loc { + fn from(locdiem: LocDiem) -> Loc { + Loc { + span: locdiem.span, + file: locdiem.file.to_string(), + } + } +} + +/// Function used to have nearly tuple-like syntax for creating a Spanned +pub const fn sp(loc: Loc, value: T) -> Spanned { + Spanned { loc, value } +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct Loc { + pub file: String, + pub span: Span, +} +impl Loc { + pub fn new(file: String, span: Span) -> Loc { + Loc { file, span } + } + + pub fn file(self) -> String { + self.file + } + + pub fn span(self) -> Span { + self.span + } +} + +impl PartialOrd for Loc { + fn partial_cmp(&self, other: &Loc) -> Option { + let file_ord = self.file.partial_cmp(&other.file)?; + if file_ord != Ordering::Equal { + return Some(file_ord); + } + + let start_ord = self.span.start().partial_cmp(&other.span.start())?; + if start_ord != Ordering::Equal { + return Some(start_ord); + } + + self.span.end().partial_cmp(&other.span.end()) + } +} + +impl Ord for Loc { + fn cmp(&self, other: &Loc) -> Ordering { + self.file.cmp(&other.file).then_with(|| { + self.span + .start() + .cmp(&other.span.start()) + .then_with(|| self.span.end().cmp(&other.span.end())) + }) + } +}