fix(solc): flatten import aliases (#1192)
* import aliases * fix solang alias parsing * fix token replacement * minor * remove log * remove log * address pr comments * rollback
This commit is contained in:
parent
b34c034bc4
commit
a656830790
|
@ -3,7 +3,7 @@ use crate::{
|
|||
cache::SOLIDITY_FILES_CACHE_FILENAME,
|
||||
error::{Result, SolcError, SolcIoError},
|
||||
remappings::Remapping,
|
||||
resolver::Graph,
|
||||
resolver::{Graph, SolImportAlias},
|
||||
utils, Source, Sources,
|
||||
};
|
||||
|
||||
|
@ -281,43 +281,69 @@ impl ProjectPathsConfig {
|
|||
let target_node = graph.node(*target_index);
|
||||
|
||||
let mut imports = target_node.imports().clone();
|
||||
imports.sort_by_key(|x| x.loc().0);
|
||||
imports.sort_by_key(|x| x.loc().start);
|
||||
|
||||
let mut content = target_node.content().as_bytes().to_vec();
|
||||
let mut content = target_node.content().to_owned();
|
||||
|
||||
for alias in imports.iter().flat_map(|i| i.data().aliases()) {
|
||||
let (alias, target) = match alias {
|
||||
SolImportAlias::Contract(alias, target) => (alias.clone(), target.clone()),
|
||||
_ => continue,
|
||||
};
|
||||
let name_regex = utils::create_contract_or_lib_name_regex(&alias);
|
||||
let target_len = target.len() as isize;
|
||||
let mut replace_offset = 0;
|
||||
for cap in name_regex.captures_iter(&content.clone()) {
|
||||
if cap.name("ignore").is_some() {
|
||||
continue
|
||||
}
|
||||
if let Some(name_match) =
|
||||
vec!["n1", "n2", "n3"].iter().find_map(|name| cap.name(name))
|
||||
{
|
||||
let name_match_range =
|
||||
utils::range_by_offset(&name_match.range(), replace_offset);
|
||||
replace_offset += target_len - (name_match_range.len() as isize);
|
||||
content.replace_range(name_match_range, &target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut content = content.as_bytes().to_vec();
|
||||
let mut offset = 0_isize;
|
||||
|
||||
if strip_license {
|
||||
if let Some(license) = target_node.license() {
|
||||
let (start, end) = license.loc_by_offset(offset);
|
||||
content.splice(start..end, std::iter::empty());
|
||||
offset -= (end - start) as isize;
|
||||
let license_range = license.loc_by_offset(offset);
|
||||
offset -= license_range.len() as isize;
|
||||
content.splice(license_range, std::iter::empty());
|
||||
}
|
||||
}
|
||||
|
||||
if strip_version_pragma {
|
||||
if let Some(version) = target_node.version() {
|
||||
let (start, end) = version.loc_by_offset(offset);
|
||||
content.splice(start..end, std::iter::empty());
|
||||
offset -= (end - start) as isize;
|
||||
let version_range = version.loc_by_offset(offset);
|
||||
offset -= version_range.len() as isize;
|
||||
content.splice(version_range, std::iter::empty());
|
||||
}
|
||||
}
|
||||
|
||||
if strip_experimental_pragma {
|
||||
if let Some(experiment) = target_node.experimental() {
|
||||
let (start, end) = experiment.loc_by_offset(offset);
|
||||
content.splice(start..end, std::iter::empty());
|
||||
offset -= (end - start) as isize;
|
||||
let experimental_pragma_range = experiment.loc_by_offset(offset);
|
||||
offset -= experimental_pragma_range.len() as isize;
|
||||
content.splice(experimental_pragma_range, std::iter::empty());
|
||||
}
|
||||
}
|
||||
|
||||
for import in imports.iter() {
|
||||
let import_path = self.resolve_import(target_dir, import.data())?;
|
||||
let import_path = self.resolve_import(target_dir, import.data().path())?;
|
||||
let s = self.flatten_node(&import_path, graph, imported, true, true, true)?;
|
||||
|
||||
let import_content = s.as_bytes();
|
||||
let import_content_len = import_content.len() as isize;
|
||||
let (start, end) = import.loc_by_offset(offset);
|
||||
content.splice(start..end, import_content.iter().copied());
|
||||
offset += import_content_len - ((end - start) as isize);
|
||||
let import_range = import.loc_by_offset(offset);
|
||||
offset += import_content_len - (import_range.len() as isize);
|
||||
content.splice(import_range, import_content.iter().copied());
|
||||
}
|
||||
|
||||
let result = String::from_utf8(content).map_err(|err| {
|
||||
|
|
|
@ -52,7 +52,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use parse::{SolData, SolDataUnit};
|
||||
use parse::{SolData, SolDataUnit, SolImport};
|
||||
use rayon::prelude::*;
|
||||
|
||||
use semver::VersionReq;
|
||||
|
@ -62,6 +62,7 @@ use crate::{error::Result, utils, ProjectPathsConfig, SolcError, Source, Sources
|
|||
mod parse;
|
||||
mod tree;
|
||||
|
||||
pub use parse::SolImportAlias;
|
||||
pub use tree::{print, Charset, TreeOptions};
|
||||
|
||||
/// The underlying edges of the graph which only contains the raw relationship data.
|
||||
|
@ -339,13 +340,14 @@ impl Graph {
|
|||
};
|
||||
|
||||
for import in node.data.imports.iter() {
|
||||
match paths.resolve_import(cwd, import.data()) {
|
||||
let import_path = import.data().path();
|
||||
match paths.resolve_import(cwd, import_path) {
|
||||
Ok(import) => {
|
||||
add_node(&mut unresolved, &mut index, &mut resolved_imports, import)?;
|
||||
}
|
||||
Err(err) => {
|
||||
if unresolved_paths.insert(import.data().to_path_buf()) {
|
||||
crate::report::unresolved_import(import.data(), &paths.remappings);
|
||||
if unresolved_paths.insert(import_path.to_path_buf()) {
|
||||
crate::report::unresolved_import(import_path, &paths.remappings);
|
||||
}
|
||||
tracing::trace!(
|
||||
"failed to resolve import component \"{:?}\" for {:?}",
|
||||
|
@ -772,7 +774,7 @@ impl Node {
|
|||
&self.source.content
|
||||
}
|
||||
|
||||
pub fn imports(&self) -> &Vec<SolDataUnit<PathBuf>> {
|
||||
pub fn imports(&self) -> &Vec<SolDataUnit<SolImport>> {
|
||||
&self.data.imports
|
||||
}
|
||||
|
||||
|
@ -854,7 +856,7 @@ mod tests {
|
|||
let dapp_test = graph.node(1);
|
||||
assert_eq!(dapp_test.path, paths.sources.join("Dapp.t.sol"));
|
||||
assert_eq!(
|
||||
dapp_test.data.imports.iter().map(|i| i.data()).collect::<Vec<&PathBuf>>(),
|
||||
dapp_test.data.imports.iter().map(|i| i.data().path()).collect::<Vec<&PathBuf>>(),
|
||||
vec![&PathBuf::from("ds-test/test.sol"), &PathBuf::from("./Dapp.sol")]
|
||||
);
|
||||
assert_eq!(graph.imported_nodes(1).to_vec(), vec![2, 0]);
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
use crate::{utils, Solc};
|
||||
use regex::Match;
|
||||
use semver::VersionReq;
|
||||
use solang_parser::pt::{
|
||||
ContractPart, ContractTy, FunctionAttribute, FunctionDefinition, Import, Loc, SourceUnitPart,
|
||||
Visibility,
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
ops::Range,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// Represents various information about a solidity file parsed via [solang_parser]
|
||||
#[derive(Debug)]
|
||||
|
@ -14,7 +16,7 @@ pub struct SolData {
|
|||
pub license: Option<SolDataUnit<String>>,
|
||||
pub version: Option<SolDataUnit<String>>,
|
||||
pub experimental: Option<SolDataUnit<String>>,
|
||||
pub imports: Vec<SolDataUnit<PathBuf>>,
|
||||
pub imports: Vec<SolDataUnit<SolImport>>,
|
||||
pub version_req: Option<VersionReq>,
|
||||
pub libraries: Vec<SolLibrary>,
|
||||
pub contracts: Vec<SolContract>,
|
||||
|
@ -39,7 +41,7 @@ impl SolData {
|
|||
pub fn parse(content: &str, file: &Path) -> Self {
|
||||
let mut version = None;
|
||||
let mut experimental = None;
|
||||
let mut imports = Vec::<SolDataUnit<PathBuf>>::new();
|
||||
let mut imports = Vec::<SolDataUnit<SolImport>>::new();
|
||||
let mut libraries = Vec::new();
|
||||
let mut contracts = Vec::new();
|
||||
|
||||
|
@ -50,22 +52,30 @@ impl SolData {
|
|||
SourceUnitPart::PragmaDirective(loc, _, pragma, value) => {
|
||||
if pragma.name == "solidity" {
|
||||
// we're only interested in the solidity version pragma
|
||||
version = Some(SolDataUnit::new(value.string.clone(), loc.into()));
|
||||
version = Some(SolDataUnit::from_loc(value.string.clone(), loc));
|
||||
}
|
||||
|
||||
if pragma.name == "experimental" {
|
||||
experimental =
|
||||
Some(SolDataUnit::new(value.string.clone(), loc.into()));
|
||||
Some(SolDataUnit::from_loc(value.string.clone(), loc));
|
||||
}
|
||||
}
|
||||
SourceUnitPart::ImportDirective(_, import) => {
|
||||
let (import, loc) = match import {
|
||||
Import::Plain(s, l) => (s, l),
|
||||
Import::GlobalSymbol(s, _, l) => (s, l),
|
||||
Import::Rename(s, _, l) => (s, l),
|
||||
let (import, ids, loc) = match import {
|
||||
Import::Plain(s, l) => (s, vec![], l),
|
||||
Import::GlobalSymbol(s, i, l) => (s, vec![(i, None)], l),
|
||||
Import::Rename(s, i, l) => (s, i, l),
|
||||
};
|
||||
imports
|
||||
.push(SolDataUnit::new(PathBuf::from(import.string), loc.into()));
|
||||
let sol_import = SolImport::new(PathBuf::from(import.string))
|
||||
.set_aliases(
|
||||
ids.into_iter()
|
||||
.map(|(id, alias)| match alias {
|
||||
Some(al) => SolImportAlias::Contract(al.name, id.name),
|
||||
None => SolImportAlias::File(id.name),
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
imports.push(SolDataUnit::from_loc(sol_import, loc));
|
||||
}
|
||||
SourceUnitPart::ContractDefinition(def) => {
|
||||
let functions = def
|
||||
|
@ -100,16 +110,14 @@ impl SolData {
|
|||
version =
|
||||
capture_outer_and_inner(content, &utils::RE_SOL_PRAGMA_VERSION, &["version"])
|
||||
.first()
|
||||
.map(|(cap, name)| {
|
||||
SolDataUnit::new(name.as_str().to_owned(), cap.to_owned().into())
|
||||
});
|
||||
.map(|(cap, name)| SolDataUnit::new(name.as_str().to_owned(), cap.range()));
|
||||
imports = capture_imports(content);
|
||||
}
|
||||
};
|
||||
let license = content.lines().next().and_then(|line| {
|
||||
capture_outer_and_inner(line, &utils::RE_SOL_SDPX_LICENSE_IDENTIFIER, &["license"])
|
||||
.first()
|
||||
.map(|(cap, l)| SolDataUnit::new(l.as_str().to_owned(), cap.to_owned().into()))
|
||||
.map(|(cap, l)| SolDataUnit::new(l.as_str().to_owned(), cap.range()))
|
||||
});
|
||||
let version_req = version.as_ref().and_then(|v| Solc::version_req(v.data()).ok());
|
||||
|
||||
|
@ -130,6 +138,37 @@ pub struct SolContract {
|
|||
pub functions: Vec<FunctionDefinition>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SolImport {
|
||||
path: PathBuf,
|
||||
aliases: Vec<SolImportAlias>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum SolImportAlias {
|
||||
File(String),
|
||||
Contract(String, String),
|
||||
}
|
||||
|
||||
impl SolImport {
|
||||
pub fn new(path: PathBuf) -> Self {
|
||||
Self { path, aliases: vec![] }
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &PathBuf {
|
||||
&self.path
|
||||
}
|
||||
|
||||
pub fn aliases(&self) -> &Vec<SolImportAlias> {
|
||||
&self.aliases
|
||||
}
|
||||
|
||||
fn set_aliases(mut self, aliases: Vec<SolImportAlias>) -> Self {
|
||||
self.aliases = aliases;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal representation of a contract inside a solidity file
|
||||
#[derive(Debug)]
|
||||
pub struct SolLibrary {
|
||||
|
@ -164,57 +203,41 @@ impl SolLibrary {
|
|||
/// Represents an item in a solidity file with its location in the file
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SolDataUnit<T> {
|
||||
loc: Location,
|
||||
loc: Range<usize>,
|
||||
data: T,
|
||||
}
|
||||
|
||||
/// Location in a text file buffer
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Location {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
/// Solidity Data Unit decorated with its location within the file
|
||||
impl<T> SolDataUnit<T> {
|
||||
pub fn new(data: T, loc: Location) -> Self {
|
||||
pub fn new(data: T, loc: Range<usize>) -> Self {
|
||||
Self { data, loc }
|
||||
}
|
||||
|
||||
pub fn from_loc(data: T, loc: Loc) -> Self {
|
||||
Self {
|
||||
data,
|
||||
loc: match loc {
|
||||
Loc::File(_, start, end) => Range { start, end: end + 1 },
|
||||
_ => Range { start: 0, end: 0 },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the underlying data for the unit
|
||||
pub fn data(&self) -> &T {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// Returns the location of the given data unit
|
||||
pub fn loc(&self) -> (usize, usize) {
|
||||
(self.loc.start, self.loc.end)
|
||||
pub fn loc(&self) -> Range<usize> {
|
||||
self.loc.clone()
|
||||
}
|
||||
|
||||
/// Returns the location of the given data unit adjusted by an offset.
|
||||
/// Used to determine new position of the unit within the file after
|
||||
/// content manipulation.
|
||||
pub fn loc_by_offset(&self, offset: isize) -> (usize, usize) {
|
||||
(
|
||||
offset.saturating_add(self.loc.start as isize) as usize,
|
||||
// make the end location exclusive
|
||||
offset.saturating_add(self.loc.end as isize + 1) as usize,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Match<'_>> for Location {
|
||||
fn from(src: Match) -> Self {
|
||||
Location { start: src.start(), end: src.end() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Loc> for Location {
|
||||
fn from(src: Loc) -> Self {
|
||||
match src {
|
||||
Loc::File(_, start, end) => Location { start, end },
|
||||
_ => Location { start: 0, end: 0 },
|
||||
}
|
||||
pub fn loc_by_offset(&self, offset: isize) -> Range<usize> {
|
||||
utils::range_by_offset(&self.loc, offset)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,12 +261,31 @@ fn capture_outer_and_inner<'a>(
|
|||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn capture_imports(content: &str) -> Vec<SolDataUnit<PathBuf>> {
|
||||
capture_outer_and_inner(content, &utils::RE_SOL_IMPORT, &["p1", "p2", "p3", "p4"])
|
||||
.iter()
|
||||
.map(|(cap, m)| SolDataUnit::new(PathBuf::from(m.as_str()), cap.to_owned().into()))
|
||||
.collect()
|
||||
/// Capture the import statement information together with aliases
|
||||
pub fn capture_imports(content: &str) -> Vec<SolDataUnit<SolImport>> {
|
||||
let mut imports = vec![];
|
||||
for cap in utils::RE_SOL_IMPORT.captures_iter(content) {
|
||||
if let Some(name_match) =
|
||||
vec!["p1", "p2", "p3", "p4"].iter().find_map(|name| cap.name(name))
|
||||
{
|
||||
let statement_match = cap.get(0).unwrap();
|
||||
let mut aliases = vec![];
|
||||
for alias_cap in utils::RE_SOL_IMPORT_ALIAS.captures_iter(statement_match.as_str()) {
|
||||
if let Some(alias) = alias_cap.name("alias") {
|
||||
let alias = alias.as_str().to_owned();
|
||||
let import_alias = match alias_cap.name("target") {
|
||||
Some(target) => SolImportAlias::Contract(alias, target.as_str().to_owned()),
|
||||
None => SolImportAlias::File(alias),
|
||||
};
|
||||
aliases.push(import_alias);
|
||||
}
|
||||
}
|
||||
let sol_import =
|
||||
SolImport::new(PathBuf::from(name_match.as_str())).set_aliases(aliases);
|
||||
imports.push(SolDataUnit::new(sol_import, statement_match.range()));
|
||||
}
|
||||
}
|
||||
imports
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -259,7 +301,7 @@ import {DsTest} from "ds-test/test.sol";
|
|||
"#;
|
||||
|
||||
let captured_imports =
|
||||
capture_imports(content).into_iter().map(|s| s.data).collect::<Vec<_>>();
|
||||
capture_imports(content).into_iter().map(|s| s.data.path).collect::<Vec<_>>();
|
||||
|
||||
let expected =
|
||||
utils::find_import_paths(content).map(|m| m.as_str().into()).collect::<Vec<PathBuf>>();
|
||||
|
@ -275,4 +317,29 @@ import {DsTest} from "ds-test/test.sol";
|
|||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cap_capture_aliases() {
|
||||
let content = r#"
|
||||
import * as T from "./Test.sol";
|
||||
import { DsTest as Test } from "ds-test/test.sol";
|
||||
import "ds-test/test.sol" as Test;
|
||||
import { FloatMath as Math, Math as FloatMath } from "./Math.sol";
|
||||
"#;
|
||||
|
||||
let caputred_imports =
|
||||
capture_imports(content).into_iter().map(|s| s.data.aliases).collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
caputred_imports,
|
||||
vec![
|
||||
vec![SolImportAlias::File("T".into())],
|
||||
vec![SolImportAlias::Contract("Test".into(), "DsTest".into())],
|
||||
vec![SolImportAlias::File("Test".into())],
|
||||
vec![
|
||||
SolImportAlias::Contract("Math".into(), "FloatMath".into()),
|
||||
SolImportAlias::Contract("FloatMath".into(), "Math".into()),
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
ops::Range,
|
||||
path::{Component, Path, PathBuf},
|
||||
};
|
||||
|
||||
|
@ -17,9 +18,13 @@ use walkdir::WalkDir;
|
|||
/// statement with the named groups "path", "id".
|
||||
// Adapted from <https://github.com/nomiclabs/hardhat/blob/cced766c65b25d3d0beb39ef847246ac9618bdd9/packages/hardhat-core/src/internal/solidity/parse.ts#L100>
|
||||
pub static RE_SOL_IMPORT: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r#"import\s+(?:(?:"(?P<p1>[^;]*)"|'(?P<p2>[^;]*)')(?:;|\s+as\s+(?P<id>[^;]*);)|.+from\s+(?:"(?P<p3>.*)"|'(?P<p4>.*)');)"#).unwrap()
|
||||
Regex::new(r#"import\s+(?:(?:"(?P<p1>.*)"|'(?P<p2>.*)')(?:\s+as\s+\w+)?|(?:(?:\w+(?:\s+as\s+\w+)?|\*\s+as\s+\w+|\{\s*(?:\w+(?:\s+as\s+\w+)?(?:\s*,\s*)?)+\s*\})\s+from\s+(?:"(?P<p3>.*)"|'(?P<p4>.*)')))\s*;"#).unwrap()
|
||||
});
|
||||
|
||||
/// A regex that matches an alias within an import statement
|
||||
pub static RE_SOL_IMPORT_ALIAS: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new(r#"(?:(?P<target>\w+)|\*|'|")\s+as\s+(?P<alias>\w+)"#).unwrap());
|
||||
|
||||
/// A regex that matches the version part of a solidity pragma
|
||||
/// as follows: `pragma solidity ^0.5.2;` => `^0.5.2`
|
||||
/// statement with the named group "version".
|
||||
|
@ -35,6 +40,19 @@ pub static RE_SOL_SDPX_LICENSE_IDENTIFIER: Lazy<Regex> =
|
|||
/// A regex used to remove extra lines in flatenned files
|
||||
pub static RE_THREE_OR_MORE_NEWLINES: Lazy<Regex> = Lazy::new(|| Regex::new("\n{3,}").unwrap());
|
||||
|
||||
/// Create a regex that matches any library or contract name inside a file
|
||||
pub fn create_contract_or_lib_name_regex(name: &str) -> Regex {
|
||||
Regex::new(&format!(r#"(?:using\s+(?P<n1>{name})\s+|is\s+(?:\w+\s*,\s*)*(?P<n2>{name})(?:\s*,\s*\w+)*|(?:(?P<ignore>(?:function|error|as)\s+|\n[^\n]*(?:"([^"\n]|\\")*|'([^'\n]|\\')*))|\W+)(?P<n3>{name})(?:\.|\(| ))"#, name = name)).unwrap()
|
||||
}
|
||||
|
||||
/// Move a range by a specified offset
|
||||
pub fn range_by_offset(range: &Range<usize>, offset: isize) -> Range<usize> {
|
||||
Range {
|
||||
start: offset.saturating_add(range.start as isize) as usize,
|
||||
end: offset.saturating_add(range.end as isize) as usize,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all path parts from any solidity import statement in a string,
|
||||
/// `import "./contracts/Contract.sol";` -> `"./contracts/Contract.sol"`.
|
||||
///
|
||||
|
|
|
@ -721,6 +721,154 @@ contract A { }
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_flatten_with_alias() {
|
||||
let project = TempProject::dapptools().unwrap();
|
||||
|
||||
let f = project
|
||||
.add_source(
|
||||
"Contract",
|
||||
r#"pragma solidity ^0.8.10;
|
||||
import { ParentContract as Parent } from "./Parent.sol";
|
||||
import { AnotherParentContract as AnotherParent } from "./AnotherParent.sol";
|
||||
import { PeerContract as Peer } from "./Peer.sol";
|
||||
import { MathLibrary as Math } from "./Math.sol";
|
||||
import * as Lib from "./SomeLib.sol";
|
||||
|
||||
contract Contract is Parent,
|
||||
AnotherParent {
|
||||
using Math for uint256;
|
||||
|
||||
string public usingString = "using Math for uint256;";
|
||||
string public inheritanceString = "\"Contract is Parent {\"";
|
||||
string public castString = 'Peer(smth) ';
|
||||
string public methodString = '\' Math.max()';
|
||||
|
||||
Peer public peer;
|
||||
|
||||
error Peer();
|
||||
|
||||
constructor(address _peer) {
|
||||
peer = Peer(_peer);
|
||||
}
|
||||
|
||||
function Math(uint256 value) external pure returns (uint256) {
|
||||
return Math.minusOne(Math.max() - value.diffMax());
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
project
|
||||
.add_source(
|
||||
"Parent",
|
||||
r#"pragma solidity ^0.8.10;
|
||||
contract ParentContract { }
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
project
|
||||
.add_source(
|
||||
"AnotherParent",
|
||||
r#"pragma solidity ^0.8.10;
|
||||
contract AnotherParentContract { }
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
project
|
||||
.add_source(
|
||||
"Peer",
|
||||
r#"pragma solidity ^0.8.10;
|
||||
contract PeerContract { }
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
project
|
||||
.add_source(
|
||||
"Math",
|
||||
r#"pragma solidity ^0.8.10;
|
||||
library MathLibrary {
|
||||
function minusOne(uint256 val) internal returns (uint256) {
|
||||
return val - 1;
|
||||
}
|
||||
|
||||
function max() internal returns (uint256) {
|
||||
return type(uint256).max;
|
||||
}
|
||||
|
||||
function diffMax(uint256 value) internal returns (uint256) {
|
||||
return type(uint256).max - value;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
project
|
||||
.add_source(
|
||||
"SomeLib",
|
||||
r#"pragma solidity ^0.8.10;
|
||||
library SomeLib { }
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let result = project.flatten(&f).unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"pragma solidity ^0.8.10;
|
||||
|
||||
contract ParentContract { }
|
||||
|
||||
contract AnotherParentContract { }
|
||||
|
||||
contract PeerContract { }
|
||||
|
||||
library MathLibrary {
|
||||
function minusOne(uint256 val) internal returns (uint256) {
|
||||
return val - 1;
|
||||
}
|
||||
|
||||
function max() internal returns (uint256) {
|
||||
return type(uint256).max;
|
||||
}
|
||||
|
||||
function diffMax(uint256 value) internal returns (uint256) {
|
||||
return type(uint256).max - value;
|
||||
}
|
||||
}
|
||||
|
||||
library SomeLib { }
|
||||
|
||||
contract Contract is ParentContract,
|
||||
AnotherParentContract {
|
||||
using MathLibrary for uint256;
|
||||
|
||||
string public usingString = "using Math for uint256;";
|
||||
string public inheritanceString = "\"Contract is Parent {\"";
|
||||
string public castString = 'Peer(smth) ';
|
||||
string public methodString = '\' Math.max()';
|
||||
|
||||
PeerContract public peer;
|
||||
|
||||
error Peer();
|
||||
|
||||
constructor(address _peer) {
|
||||
peer = PeerContract(_peer);
|
||||
}
|
||||
|
||||
function Math(uint256 value) external pure returns (uint256) {
|
||||
return MathLibrary.minusOne(MathLibrary.max() - value.diffMax());
|
||||
}
|
||||
}
|
||||
"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_detect_type_error() {
|
||||
let project = TempProject::<ConfigurableArtifacts>::dapptools().unwrap();
|
||||
|
|
Loading…
Reference in New Issue