Переглянути джерело

Relative imports are only done with relative paths and expose import_no (#1465)

When importing in Solidity, `import "foo";` does not check path relative
to the currrent file, only `import "./foo";` does.

This work is also a re-write of
https://github.com/hyperledger/solang/pull/1443

Signed-off-by: Sean Young <sean@mess.org>
Co-authored-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
Sean Young 2 роки тому
батько
коміт
c961eb08f5
45 змінених файлів з 375 додано та 265 видалено
  1. 8 2
      CHANGELOG.md
  2. 1 1
      Cargo.toml
  3. 5 0
      docs/examples/solana/bobcat.sol
  4. 1 1
      docs/examples/solana/call_anchor.sol
  5. 5 0
      docs/examples/user.sol
  6. 1 1
      docs/examples/using_imports.sol
  7. 14 2
      docs/language/imports.rst
  8. 1 1
      src/abi/tests.rs
  9. 12 17
      src/bin/cli/mod.rs
  10. 1 1
      src/bin/cli/test.rs
  11. 1 1
      src/bin/languageserver/mod.rs
  12. 9 5
      src/bin/solang.rs
  13. 104 123
      src/file_resolver.rs
  14. 1 6
      src/sema/expression/literals.rs
  15. 8 2
      src/sema/expression/strings.rs
  16. 10 4
      src/sema/expression/tests.rs
  17. 50 4
      src/sema/mod.rs
  18. 7 7
      src/sema/tests/mod.rs
  19. 1 1
      src/sema/yul/expression.rs
  20. 3 3
      src/sema/yul/tests/expression.rs
  21. 1 1
      src/sema/yul/tests/mod.rs
  22. 2 2
      tests/contract.rs
  23. 1 1
      tests/contract_testcases/polkadot/functions/mangling_03.sol
  24. 3 0
      tests/contract_testcases/solana/empty_import.sol
  25. 1 1
      tests/contract_testcases/solana/import_contracts_via_object.sol
  26. 3 3
      tests/contract_testcases/solana/import_free_function.sol
  27. 1 1
      tests/contract_testcases/solana/import_free_function_chain.sol
  28. 1 1
      tests/contract_testcases/solana/keep_on_resolving.sol
  29. 1 1
      tests/contract_testcases/solana/type_decl_broken_more.sol
  30. 1 1
      tests/contract_testcases/solana/type_decl_import.sol
  31. 1 1
      tests/contract_testcases/solana/using_import.sol
  32. 7 25
      tests/doc_examples.rs
  33. 8 8
      tests/evm.rs
  34. 55 0
      tests/imports.rs
  35. 4 0
      tests/imports_testcases/bad_escape.sol
  36. 3 1
      tests/imports_testcases/imports/bar.sol
  37. 3 0
      tests/imports_testcases/imports/bar_backslash.sol
  38. 1 1
      tests/polkadot.rs
  39. 5 5
      tests/polkadot_tests/events.rs
  40. 22 22
      tests/polkadot_tests/imports.rs
  41. 2 2
      tests/polkadot_tests/inheritance.rs
  42. 2 2
      tests/solana.rs
  43. 1 1
      tests/solana_tests/simple.rs
  44. 1 1
      tests/undefined_variable_detection.rs
  45. 2 2
      tests/unused_variable_detection.rs

+ 8 - 2
CHANGELOG.md

@@ -2,6 +2,12 @@
 All notable changes to [Solang](https://github.com/hyperledger/solang/)
 will be documented here.
 
+## Unreleased
+
+### Fixed
+- **breaking** Resolving import paths now matches solc more closely, and only resolves relative
+  paths when specified as `./foo` or `../foo`. [seanyoung](https://github.com/seanyoung)
+
 ## v0.3.1 Göttingen
 
 ### Added
@@ -11,7 +17,7 @@ will be documented here.
 - The `wasm-opt` optimizer now optimizes the Wasm bytecode on the Substrate target. [xermicus](https://github.com/xermicus)
 - Call flags are now available for Substrate. [xermicus](https://github.com/xermicus)
 - Read compiler configurations from toml file. [salaheldinsoliman](https://github.com/salaheldinsoliman)
-- Accounts declared with `@payer(my_account)` can be accessed with the 
+- Accounts declared with `@payer(my_account)` can be accessed with the
   syntax `tx.accounts.my_account`. [LucasSte](https://github.com/LucasSte)
 - `delegatecall()` builtin has been added for Substrate. [xermicus](https://github.com/xermicus)
 - `get_contents_of_file_no` for Solang parser. [BenTheKush](https://github.com/BenTheKush)
@@ -52,7 +58,7 @@ will be documented here.
   contract MyContract {
     @payer(acc) // Declares account acc
     @space(2+3) // Only literals or constant expressions allowed
-    constructor(@seed bytes myseed) {} 
+    constructor(@seed bytes myseed) {}
     // When an annotations refers to a parameter, the former must appear right before the latter.
   }
   ```

+ 1 - 1
Cargo.toml

@@ -64,7 +64,7 @@ toml = "0.7"
 wasm-opt = { version = "0.112.0", optional = true }
 contract-build = { version = "3.0.1", optional = true }
 primitive-types = { version = "0.12", features = ["codec"] }
-
+normalize-path = "0.2.1"
 
 [dev-dependencies]
 num-derive = "0.4"

+ 5 - 0
docs/examples/solana/bobcat.sol

@@ -0,0 +1,5 @@
+anchor_anchor constant bobcat = anchor_anchor(address'z7FbDfQDfucxJz5o8jrGLgvSbdoeSqX5VrxBb5TVjHq');
+interface anchor_anchor {
+	@selector([0xaf, 0xaf, 0x6d, 0x1f, 0x0d, 0x98, 0x9b, 0xed])
+	function pounce() view external returns(int64);
+}

+ 1 - 1
docs/examples/solana/call_anchor.sol

@@ -1,4 +1,4 @@
-import "bobcat.sol";
+import "./bobcat.sol";
 import "solana";
 
 contract example {

+ 5 - 0
docs/examples/user.sol

@@ -0,0 +1,5 @@
+struct User { string name; uint count; }
+function clear_count(User memory user) {
+	user.count = 0;
+}
+using {clear_count} for User global;

+ 1 - 1
docs/examples/using_imports.sol

@@ -1,4 +1,4 @@
-import {User} from "user.sol";
+import {User} from "./user.sol";
 
 contract c {
     function foo(User memory user) public {

+ 14 - 2
docs/language/imports.rst

@@ -26,8 +26,9 @@ or just a select few items. You can also rename the imports. The following direc
 
     import {foo, bar} from "defines.sol";
 
-Solang will look for the file `defines.sol` in the same directory as the current file. You can specify
-more directories to search with the ``--importpath`` commandline option.
+Solang will look for the file `defines.sol` in the paths specified with the ``--importpath``
+commandline option. If the file is relative, e.g. ``import "./defines.sol";`` or
+``import "../defines.sol";``, then the directory relative to the parent file is used.
 Just like with ES6, ``import`` is hoisted to the top and both `foo` and `bar` are usuable
 even before the ``import`` statement. It is also possible to import everything from
 `defines.sol` by leaving the list out. Note that this is different than ES6, which would import nothing
@@ -73,3 +74,14 @@ There is another syntax, which does exactly the same.
 .. code-block:: solidity
 
     import * as defs from "defines.sol";
+
+Just like string literals, import paths can have escape sequences. This is a confusing way of
+writing `a.sol`:
+
+.. code-block:: solidity
+
+    import "\x61.sol";
+
+It is possible to use ``\`` Windows style path separators on Windows, but it is not recommended
+as they do not work on platforms other than Windows (they do not work on WSL either).
+Note they have to be written as ``\\`` due to escape sequences.

+ 1 - 1
src/abi/tests.rs

@@ -16,7 +16,7 @@ use serde_json::json;
 use std::ffi::OsStr;
 
 fn generate_namespace(src: &'static str) -> Namespace {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     cache.set_file_contents("test.sol", src.to_string());
     parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::Solana)
 }

+ 12 - 17
src/bin/cli/mod.rs

@@ -52,7 +52,7 @@ pub struct New {
     #[arg(name = "TARGETNAME",required= true, long = "target", value_parser = ["solana", "polkadot", "evm"], help = "Target to build for [possible values: solana, polkadot]", num_args = 1, hide_possible_values = true)]
     pub target_name: String,
 
-    #[arg(name = "INPUT", help = "Name of the project", num_args = 1, value_parser =  ValueParser::os_string())]
+    #[arg(name = "INPUT", help = "Name of the project", num_args = 1, value_parser = ValueParser::os_string())]
     pub project_name: Option<OsString>,
 }
 
@@ -70,10 +70,10 @@ pub struct LanguageServerCommand {
     #[clap(flatten)]
     pub target: TargetArg,
 
-    #[arg(name = "IMPORTPATH", help = "Directory to search for solidity files", value_parser = ValueParser::path_buf() , action = ArgAction::Append, long = "importpath", short = 'I', num_args = 1)]
+    #[arg(name = "IMPORTPATH", help = "Directory to search for solidity files", value_parser = ValueParser::path_buf(), action = ArgAction::Append, long = "importpath", short = 'I', num_args = 1)]
     pub import_path: Option<Vec<PathBuf>>,
 
-    #[arg(name = "IMPORTMAP", help = "Map directory to search for solidity files [format: map=path]",value_parser = ValueParser::new(parse_import_map) , action = ArgAction::Append, long = "importmap", short = 'm', num_args = 1)]
+    #[arg(name = "IMPORTMAP", help = "Map directory to search for solidity files [format: map=path]",value_parser = ValueParser::new(parse_import_map), action = ArgAction::Append, long = "importmap", short = 'm', num_args = 1)]
     pub import_map: Option<Vec<(String, PathBuf)>>,
 }
 
@@ -245,7 +245,7 @@ pub struct CompilerOutput {
     #[serde(deserialize_with = "deserialize_emit", default)]
     pub emit: Option<String>,
 
-    #[arg(name = "STD-JSON",help = "mimic solidity json output on stdout", conflicts_with_all = ["VERBOSE", "OUTPUT", "EMIT"] , action = ArgAction::SetTrue, long = "standard-json")]
+    #[arg(name = "STD-JSON",help = "mimic solidity json output on stdout", conflicts_with_all = ["VERBOSE", "OUTPUT", "EMIT"], action = ArgAction::SetTrue, long = "standard-json")]
     #[serde(default)]
     pub std_json_output: bool,
 
@@ -294,30 +294,30 @@ pub struct DocPackage {
     #[arg(name = "CONTRACT", help = "Contract names to compile (defaults to all)", value_delimiter = ',', action = ArgAction::Append, long = "contract")]
     pub contracts: Option<Vec<String>>,
 
-    #[arg(name = "IMPORTPATH", help = "Directory to search for solidity files",value_parser = ValueParser::path_buf() , action = ArgAction::Append, long = "importpath", short = 'I', num_args = 1)]
+    #[arg(name = "IMPORTPATH", help = "Directory to search for solidity files",value_parser = ValueParser::path_buf(), action = ArgAction::Append, long = "importpath", short = 'I', num_args = 1)]
     pub import_path: Option<Vec<PathBuf>>,
 
-    #[arg(name = "IMPORTMAP", help = "Map directory to search for solidity files [format: map=path]",value_parser = ValueParser::new(parse_import_map) , action = ArgAction::Append, long = "importmap", short = 'm', num_args = 1)]
+    #[arg(name = "IMPORTMAP", help = "Map directory to search for solidity files [format: map=path]",value_parser = ValueParser::new(parse_import_map), action = ArgAction::Append, long = "importmap", short = 'm', num_args = 1)]
     pub import_map: Option<Vec<(String, PathBuf)>>,
 }
 
 #[derive(Args, Deserialize, Debug, PartialEq)]
 pub struct CompilePackage {
-    #[arg(name = "INPUT", help = "Solidity input files",value_parser = ValueParser::path_buf(), num_args = 1..,)]
+    #[arg(name = "INPUT", help = "Solidity input files",value_parser = ValueParser::path_buf(), num_args = 1..)]
     #[serde(rename(deserialize = "input_files"))]
     pub input: Option<Vec<PathBuf>>,
 
     #[arg(name = "CONTRACT", help = "Contract names to compile (defaults to all)", value_delimiter = ',', action = ArgAction::Append, long = "contract")]
     pub contracts: Option<Vec<String>>,
 
-    #[arg(name = "IMPORTPATH", help = "Directory to search for solidity files",value_parser = ValueParser::path_buf() , action = ArgAction::Append, long = "importpath", short = 'I', num_args = 1)]
+    #[arg(name = "IMPORTPATH", help = "Directory to search for solidity files", value_parser = ValueParser::path_buf(), action = ArgAction::Append, long = "importpath", short = 'I', num_args = 1)]
     pub import_path: Option<Vec<PathBuf>>,
 
-    #[arg(name = "IMPORTMAP", help = "Map directory to search for solidity files [format: map=path]",value_parser = ValueParser::new(parse_import_map) , action = ArgAction::Append, long = "importmap", short = 'm', num_args = 1)]
+    #[arg(name = "IMPORTMAP", help = "Map directory to search for solidity files [format: map=path]",value_parser = ValueParser::new(parse_import_map), action = ArgAction::Append, long = "importmap", short = 'm', num_args = 1)]
     #[serde(deserialize_with = "deserialize_inline_table", default)]
     pub import_map: Option<Vec<(String, PathBuf)>>,
 
-    #[arg(name = "AUTHOR", help = "specify contracts authors" , long = "contract-authors", value_delimiter = ',', action = ArgAction::Append)]
+    #[arg(name = "AUTHOR", help = "specify contracts authors", long = "contract-authors", value_delimiter = ',', action = ArgAction::Append)]
     #[serde(default)]
     pub authors: Option<Vec<String>>,
 
@@ -513,7 +513,7 @@ impl PackageTrait for DocPackage {
 }
 
 pub fn imports_arg<T: PackageTrait>(package: &T) -> FileResolver {
-    let mut resolver = FileResolver::new();
+    let mut resolver = FileResolver::default();
 
     for filename in package.get_input() {
         if let Ok(path) = PathBuf::from(filename).canonicalize() {
@@ -521,11 +521,6 @@ pub fn imports_arg<T: PackageTrait>(package: &T) -> FileResolver {
         }
     }
 
-    if let Err(e) = resolver.add_import_path(&PathBuf::from(".")) {
-        eprintln!("error: cannot add current directory to import path: {e}");
-        exit(1);
-    }
-
     if let Some(paths) = package.get_import_path() {
         for path in paths {
             if let Err(e) = resolver.add_import_path(path) {
@@ -651,7 +646,7 @@ where
     match str {
         Some(value) => {
             match value.as_str() {
-                "ast-dot"|"cfg"|"llvm-ir"|"llvm-bc" |"object"| "asm" => 
+                "ast-dot"|"cfg"|"llvm-ir"|"llvm-bc"|"object"|"asm" =>
                     Ok(Some(value))
                 ,
                 _ => Err(serde::de::Error::custom("Invalid option for `emit`. Valid options are: `ast-dot`, `cfg`, `llvm-ir`, `llvm-bc`, `object`, `asm`"))

+ 1 - 1
src/bin/cli/test.rs

@@ -51,7 +51,7 @@ mod tests {
         let mut package_toml = r#"
         input_files = ["flipper.sol"]   # Files to be compiled. You can define multiple files as : input_files = ["file1", "file2", ..]
         contracts = ["flipper"] # Contracts to include from the compiled files
-        import_path = ["path1", "path2"]   
+        import_path = ["path1", "path2"]
         import_map = {map1="path", map2="path2"}    # Maps to import. Define as : import_paths = ["map=path/to/map", "map2=path/to/map2", ..]"#;
 
         let package: cli::CompilePackage = toml::from_str(package_toml).unwrap();

+ 1 - 1
src/bin/languageserver/mod.rs

@@ -94,7 +94,7 @@ pub async fn start_server(language_args: &LanguageServerCommand) -> ! {
 impl SolangServer {
     /// Parse file
     async fn parse_file(&self, uri: Url) {
-        let mut resolver = FileResolver::new();
+        let mut resolver = FileResolver::default();
         for (path, contents) in &self.files.lock().await.text_buffers {
             resolver.set_file_contents(path.to_str().unwrap(), contents.clone());
         }

+ 9 - 5
src/bin/solang.rs

@@ -16,7 +16,7 @@ use solang::{
 };
 use std::{
     collections::{HashMap, HashSet},
-    ffi::{OsStr, OsString},
+    ffi::OsString,
     fs::{self, create_dir, create_dir_all, File},
     io::prelude::*,
     path::{Path, PathBuf},
@@ -186,7 +186,7 @@ fn compile(compile_args: &Compile) {
     for filename in compile_args.package.get_input() {
         // TODO: this could be parallelized using e.g. rayon
         let ns = process_file(
-            filename.as_os_str(),
+            filename,
             &mut resolver,
             target,
             &compile_args.compiler_output,
@@ -301,7 +301,7 @@ fn output_file(compiler_output: &CompilerOutput, stem: &str, ext: &str, meta: bo
 }
 
 fn process_file(
-    filename: &OsStr,
+    filename: &Path,
     resolver: &mut FileResolver,
     target: solang::Target,
     compiler_output: &CompilerOutput,
@@ -309,14 +309,18 @@ fn process_file(
 ) -> Namespace {
     let verbose = compiler_output.verbose;
 
+    let filepath = match filename.canonicalize() {
+        Ok(filename) => filename,
+        Err(_) => filename.to_path_buf(),
+    };
+
     // resolve phase
-    let mut ns = solang::parse_and_resolve(filename, resolver, target);
+    let mut ns = solang::parse_and_resolve(filepath.as_os_str(), resolver, target);
 
     // codegen all the contracts; some additional errors/warnings will be detected here
     codegen(&mut ns, opt);
 
     if let Some("ast-dot") = compiler_output.emit.as_deref() {
-        let filepath = PathBuf::from(filename);
         let stem = filepath.file_stem().unwrap().to_string_lossy();
         let dot_filename = output_file(compiler_output, &stem, "dot", false);
 

+ 104 - 123
src/file_resolver.rs

@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use crate::sema::ast;
+use normalize_path::NormalizePath;
 use solang_parser::pt::Loc;
 use std::collections::HashMap;
 use std::ffi::{OsStr, OsString};
@@ -10,13 +11,14 @@ use std::io::{prelude::*, Error, ErrorKind};
 use std::path::{Path, PathBuf};
 use std::sync::Arc;
 
+#[derive(Default)]
 pub struct FileResolver {
     /// Set of import paths search for imports
     import_paths: Vec<(Option<OsString>, PathBuf)>,
-    /// List file by import path
+    /// List file by path
     cached_paths: HashMap<PathBuf, usize>,
     /// The actual file contents
-    files: Vec<Arc<str>>,
+    files: Vec<ResolvedFile>,
 }
 
 /// When we resolve a file, we need to know its base compared to the import so
@@ -25,37 +27,17 @@ pub struct FileResolver {
 /// user exactly which file has errors/warnings.
 #[derive(Clone, Debug)]
 pub struct ResolvedFile {
+    /// Original name used on cli or import statement
+    pub path: OsString,
     /// Full path on the filesystem
     pub full_path: PathBuf,
     /// Which import path was used, if any
-    import_no: usize,
-    // Base part relative to import
-    base: PathBuf,
-}
-
-impl ResolvedFile {
-    /// Get the import number associated with this `ResolvedFile`
-    pub fn get_import_no(&self) -> usize {
-        self.import_no
-    }
-}
-
-impl Default for FileResolver {
-    fn default() -> Self {
-        FileResolver::new()
-    }
+    pub import_no: Option<usize>,
+    /// The actual file contents
+    pub contents: Arc<str>,
 }
 
 impl FileResolver {
-    /// Create a new file resolver
-    pub fn new() -> Self {
-        FileResolver {
-            import_paths: Vec::new(),
-            cached_paths: HashMap::new(),
-            files: Vec::new(),
-        }
-    }
-
     /// Add import path
     pub fn add_import_path(&mut self, path: &Path) -> io::Result<()> {
         self.import_paths.push((None, path.canonicalize()?));
@@ -96,14 +78,21 @@ impl FileResolver {
     pub fn set_file_contents(&mut self, path: &str, contents: String) {
         let pos = self.files.len();
 
-        self.files.push(Arc::from(contents));
+        let pathbuf = PathBuf::from(path);
+
+        self.files.push(ResolvedFile {
+            path: path.into(),
+            full_path: pathbuf.clone(),
+            contents: Arc::from(contents),
+            import_no: None,
+        });
 
-        self.cached_paths.insert(PathBuf::from(path), pos);
+        self.cached_paths.insert(pathbuf, pos);
     }
 
     /// Get the file contents of `file_no`th file if it exists
     pub fn get_contents_of_file_no(&self, file_no: usize) -> Option<Arc<str>> {
-        self.files.get(file_no).cloned()
+        self.files.get(file_no).map(|f| f.contents.clone())
     }
 
     /// Get file with contents. This must be a file which was previously
@@ -111,13 +100,47 @@ impl FileResolver {
     pub fn get_file_contents_and_number(&self, file: &Path) -> (Arc<str>, usize) {
         let file_no = self.cached_paths[file];
 
-        (self.files[file_no].clone(), file_no)
+        (self.files[file_no].contents.clone(), file_no)
+    }
+
+    /// Atempt to resolve a file, either from the cache or from the filesystem.
+    /// Returns Ok(Some(..)) if the file is found and loaded
+    /// Returns Ok(None) if no file by this path can be found.
+    /// Returns Err(..) if a file was found but could not be read.
+    fn try_file(
+        &mut self,
+        filename: &OsStr,
+        path: &Path,
+        import_no: Option<usize>,
+    ) -> Result<Option<ResolvedFile>, String> {
+        // For accessing the cache, remove "." and ".." path components
+        let cache_path = path.normalize();
+
+        if let Some(cache) = self.cached_paths.get(&cache_path) {
+            let mut file = self.files[*cache].clone();
+            file.import_no = import_no;
+            return Ok(Some(file));
+        }
+
+        if let Ok(full_path) = path.canonicalize() {
+            let file = self.load_file(filename, &full_path, import_no)?;
+            return Ok(Some(file.clone()));
+        }
+
+        Ok(None)
     }
 
     /// Populate the cache with absolute file path
-    fn load_file(&mut self, path: &Path) -> Result<(), String> {
-        if self.cached_paths.get(path).is_some() {
-            return Ok(());
+    fn load_file(
+        &mut self,
+        filename: &OsStr,
+        path: &Path,
+        import_no: Option<usize>,
+    ) -> Result<&ResolvedFile, String> {
+        if let Some(cache) = self.cached_paths.get(path) {
+            if self.files[*cache].import_no == import_no {
+                return Ok(&self.files[*cache]);
+            }
         }
 
         // read the file
@@ -139,11 +162,16 @@ impl FileResolver {
 
         let pos = self.files.len();
 
-        self.files.push(Arc::from(contents));
+        self.files.push(ResolvedFile {
+            path: filename.into(),
+            full_path: path.to_path_buf(),
+            import_no,
+            contents: Arc::from(contents),
+        });
 
         self.cached_paths.insert(path.to_path_buf(), pos);
 
-        Ok(())
+        Ok(&self.files[pos])
     }
 
     /// Walk the import path to search for a file. If no import path is set up,
@@ -156,107 +184,59 @@ impl FileResolver {
     ) -> Result<ResolvedFile, String> {
         let path = PathBuf::from(filename);
 
-        let path = if let Ok(m) = path.strip_prefix("./") {
-            m.to_path_buf()
-        } else {
-            path
-        };
+        // Only when the path starts with ./ or ../ are relative paths considered; this means
+        // that `import "b.sol";` will check the import paths for b.sol, while `import "./b.sol";`
+        // will only the path relative to the current file.
+        if path.starts_with("./") || path.starts_with("../") {
+            if let Some(ResolvedFile {
+                import_no,
+                full_path,
+                ..
+            }) = parent
+            {
+                let curdir = PathBuf::from(".");
+                let base = full_path.parent().unwrap_or(&curdir);
+                let path = base.join(&path);
+
+                if let Some(file) = self.try_file(filename, &path, *import_no)? {
+                    return Ok(file);
+                }
+            }
+
+            return Err(format!("file not found '{}'", filename.to_string_lossy()));
+        }
+
+        if let Some(file) = self.try_file(filename, &path, None)? {
+            return Ok(file);
+        } else if path.is_absolute() {
+            return Err(format!("file not found '{}'", filename.to_string_lossy()));
+        }
 
         // first check maps
         let mut iter = path.iter();
         if let Some(first_part) = iter.next() {
             let relpath: &PathBuf = &iter.collect();
 
-            for (import_no, import) in self.import_paths.iter().enumerate() {
-                if let (Some(mapping), import_path) = import {
+            for import_no in 0..self.import_paths.len() {
+                if let (Some(mapping), import_path) = &self.import_paths[import_no] {
                     if first_part == mapping {
-                        if let Ok(full_path) = import_path.join(relpath).canonicalize() {
-                            self.load_file(&full_path)?;
-                            let base = full_path
-                                .parent()
-                                .expect("path should include filename")
-                                .to_path_buf();
-
-                            return Ok(ResolvedFile {
-                                full_path,
-                                import_no,
-                                base,
-                            });
+                        let path = import_path.join(relpath);
+
+                        if let Some(file) = self.try_file(filename, &path, Some(import_no))? {
+                            return Ok(file);
                         }
                     }
                 }
             }
         }
 
-        let mut start_import_no = 0;
-
-        // first try relative to the parent
-        if let Some(ResolvedFile {
-            import_no, base, ..
-        }) = parent
-        {
-            if self.import_paths.is_empty() {
-                // we have no import paths, resolve by what's in the cache
-                let full_path = base.join(&path);
-                let base = full_path
-                    .parent()
-                    .expect("path should include filename")
-                    .to_path_buf();
-
-                return Ok(ResolvedFile {
-                    full_path,
-                    base,
-                    import_no: 0,
-                });
-            } else if let Ok(full_path) = base.join(&path).canonicalize() {
-                self.load_file(&full_path)?;
-                let base = full_path
-                    .parent()
-                    .expect("path should include filename")
-                    .to_path_buf();
-
-                return Ok(ResolvedFile {
-                    full_path,
-                    base,
-                    import_no: 0,
-                });
-            }
-
-            // start with this import
-            start_import_no = *import_no;
-        }
-
-        if self.cached_paths.contains_key(&path) {
-            let full_path = path;
-            let base = full_path
-                .parent()
-                .expect("path should include filename")
-                .to_path_buf();
-
-            return Ok(ResolvedFile {
-                full_path,
-                base,
-                import_no: 0,
-            });
-        }
-
         // walk over the import paths until we find one that resolves
-        for i in 0..self.import_paths.len() {
-            let import_no = (i + start_import_no) % self.import_paths.len();
-
+        for import_no in 0..self.import_paths.len() {
             if let (None, import_path) = &self.import_paths[import_no] {
-                if let Ok(full_path) = import_path.join(&path).canonicalize() {
-                    let base = full_path
-                        .parent()
-                        .expect("path should include filename")
-                        .to_path_buf();
-                    self.load_file(&full_path)?;
-
-                    return Ok(ResolvedFile {
-                        full_path,
-                        import_no,
-                        base,
-                    });
+                let path = import_path.join(&path);
+
+                if let Some(file) = self.try_file(filename, &path, Some(import_no))? {
+                    return Ok(file);
                 }
             }
         }
@@ -280,6 +260,7 @@ impl FileResolver {
         let (end_line, mut end_column) = file.offset_to_line_column(*end);
 
         let mut full_line = self.files[cache_no]
+            .contents
             .lines()
             .nth(begin_line)
             .unwrap()
@@ -288,7 +269,7 @@ impl FileResolver {
         // If the loc spans across multiple lines, we concatenate them
         if begin_line != end_line {
             for i in begin_line + 1..=end_line {
-                let line = self.files[cache_no].lines().nth(i).unwrap();
+                let line = self.files[cache_no].contents.lines().nth(i).unwrap();
                 if i == end_line {
                     end_column += full_line.len();
                 }

+ 1 - 6
src/sema/expression/literals.rs

@@ -31,12 +31,7 @@ pub(super) fn string_literal(
     let mut loc = v[0].loc;
 
     for s in v {
-        result.append(&mut unescape(
-            &s.string,
-            s.loc.start(),
-            file_no,
-            diagnostics,
-        ));
+        result.append(&mut unescape(&s.string, s.loc.start(), file_no, diagnostics).1);
         loc.use_end_from(&s.loc);
     }
 

+ 8 - 2
src/sema/expression/strings.rs

@@ -10,9 +10,10 @@ pub(crate) fn unescape(
     start: usize,
     file_no: usize,
     diagnostics: &mut Diagnostics,
-) -> Vec<u8> {
+) -> (bool, Vec<u8>) {
     let mut s: Vec<u8> = Vec::new();
     let mut indeces = literal.char_indices();
+    let mut valid = true;
 
     while let Some((_, ch)) = indeces.next() {
         if ch != '\\' {
@@ -35,6 +36,7 @@ pub(crate) fn unescape(
             Some((i, 'x')) => match get_digits(&mut indeces, 2) {
                 Ok(ch) => s.push(ch as u8),
                 Err(offset) => {
+                    valid = false;
                     diagnostics.push(Diagnostic::error(
                         pt::Loc::File(
                             file_no,
@@ -52,6 +54,7 @@ pub(crate) fn unescape(
                         s.extend_from_slice(ch.encode_utf8(&mut buffer).as_bytes());
                     }
                     None => {
+                        valid = false;
                         diagnostics.push(Diagnostic::error(
                             pt::Loc::File(file_no, start + i, start + i + 6),
                             "Found an invalid unicode character".to_string(),
@@ -59,6 +62,7 @@ pub(crate) fn unescape(
                     }
                 },
                 Err(offset) => {
+                    valid = false;
                     diagnostics.push(Diagnostic::error(
                         pt::Loc::File(
                             file_no,
@@ -70,6 +74,7 @@ pub(crate) fn unescape(
                 }
             },
             Some((i, ch)) => {
+                valid = false;
                 diagnostics.push(Diagnostic::error(
                     pt::Loc::File(file_no, start + i, start + i + ch.len_utf8()),
                     format!("unknown escape character '{ch}'"),
@@ -78,7 +83,8 @@ pub(crate) fn unescape(
             None => unreachable!(),
         }
     }
-    s
+
+    (valid, s)
 }
 
 /// Get the hex digits for an escaped \x or \u. Returns either the value or

+ 10 - 4
src/sema/expression/tests.rs

@@ -8,11 +8,17 @@ use crate::sema::expression::strings::unescape;
 fn test_unescape() {
     let s = r"\u00f3";
     let mut vec = Diagnostics::default();
-    let res = unescape(s, 0, 0, &mut vec);
-    assert!(vec.is_empty());
+    let (valid, res) = unescape(s, 0, 0, &mut vec);
+    assert!(valid && vec.is_empty());
     assert_eq!(res, vec![0xc3, 0xb3]);
+
     let s = r"\xff";
-    let res = unescape(s, 0, 0, &mut vec);
-    assert!(vec.is_empty());
+    let (valid, res) = unescape(s, 0, 0, &mut vec);
+    assert!(valid && vec.is_empty());
     assert_eq!(res, vec![255]);
+
+    let s = r"0\xfg";
+    let (valid, res) = unescape(s, 0, 0, &mut vec);
+    assert!(!valid && !vec.is_empty());
+    assert_eq!(res, b"0");
 }

+ 50 - 4
src/sema/mod.rs

@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use self::{
+    expression::strings::unescape,
     functions::{resolve_params, resolve_returns},
     symtable::Symtable,
     unused_variable::check_unused_errors,
@@ -14,7 +15,7 @@ use solang_parser::{
     parse,
     pt::{self, CodeLocation},
 };
-use std::ffi::OsStr;
+use std::{ffi::OsString, str};
 
 mod address;
 pub mod ast;
@@ -99,7 +100,7 @@ fn sema_file(file: &ResolvedFile, resolver: &mut FileResolver, ns: &mut ast::Nam
         file.full_path.clone(),
         &source_code,
         file_cache_no,
-        Some(file.get_import_no()),
+        file.import_no,
     ));
 
     let (pt, comments) = match parse(&source_code, file_no) {
@@ -233,7 +234,30 @@ fn resolve_import(
         }
     };
 
-    let os_filename = OsStr::new(&filename.string);
+    if filename.string.is_empty() {
+        ns.diagnostics.push(ast::Diagnostic::error(
+            filename.loc,
+            "import path empty".into(),
+        ));
+        return;
+    }
+
+    let (valid, bs) = unescape(
+        &filename.string,
+        filename.loc.start(),
+        filename.loc.file_no(),
+        &mut ns.diagnostics,
+    );
+
+    if !valid {
+        return;
+    }
+
+    let os_filename = if let Some(res) = osstring_from_vec(&filename.loc, bs, ns) {
+        res
+    } else {
+        return;
+    };
 
     let import_file_no = if let Some(builtin_file_no) = ns
         .files
@@ -243,7 +267,7 @@ fn resolve_import(
         // import "solana"
         builtin_file_no
     } else {
-        match resolver.resolve_file(parent, os_filename) {
+        match resolver.resolve_file(parent, &os_filename) {
             Err(message) => {
                 ns.diagnostics
                     .push(ast::Diagnostic::error(filename.loc, message));
@@ -554,3 +578,25 @@ pub trait Recurse {
     /// recurse over a structure
     fn recurse<T>(&self, cx: &mut T, f: fn(expr: &Self::ArgType, ctx: &mut T) -> bool);
 }
+
+#[cfg(unix)]
+fn osstring_from_vec(_: &pt::Loc, bs: Vec<u8>, _: &mut ast::Namespace) -> Option<OsString> {
+    use std::os::unix::ffi::OsStringExt;
+
+    Some(OsString::from_vec(bs))
+}
+
+#[cfg(not(unix))]
+fn osstring_from_vec(loc: &pt::Loc, bs: Vec<u8>, ns: &mut ast::Namespace) -> Option<OsString> {
+    match str::from_utf8(&bs) {
+        Ok(s) => Some(OsString::from(s)),
+        Err(_) => {
+            ns.diagnostics.push(ast::Diagnostic::error(
+                *loc,
+                "string is not a valid filename".into(),
+            ));
+
+            None
+        }
+    }
+}

+ 7 - 7
src/sema/tests/mod.rs

@@ -9,7 +9,7 @@ use std::ffi::{OsStr, OsString};
 use std::path::PathBuf;
 
 pub(crate) fn parse(src: &'static str) -> ast::Namespace {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     cache.set_file_contents("test.sol", src.to_string());
 
     let ns = parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::EVM);
@@ -484,7 +484,7 @@ contract runner {
 }
     "#;
 
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     cache.set_file_contents("test.sol", file.to_string());
 
     let ns = parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::Solana);
@@ -517,7 +517,7 @@ fn solana_discriminator_type() {
 }
     "#;
 
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     cache.set_file_contents("test.sol", src.to_string());
 
     let ns = parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::Solana);
@@ -571,7 +571,7 @@ contract Child {
     }
 }
     "#;
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     cache.set_file_contents("test.sol", src.to_string());
 
     let ns = parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::Solana);
@@ -611,7 +611,7 @@ contract Child {
     }
 }
     "#;
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     cache.set_file_contents("test.sol", src.to_string());
 
     let ns = parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::Solana);
@@ -626,7 +626,7 @@ contract Child {
 
 #[test]
 fn get_import_map() {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     let map = OsString::from("@openzepellin");
     let example_sol_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
         .join("examples")
@@ -643,7 +643,7 @@ fn get_import_map() {
 
 #[test]
 fn get_import_path() {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     let examples = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
         .join("examples")
         .canonicalize()

+ 1 - 1
src/sema/yul/expression.rs

@@ -69,7 +69,7 @@ pub(crate) fn resolve_yul_expression(
 
         pt::YulExpression::StringLiteral(value, ty) => {
             let mut diagnostics = Diagnostics::default();
-            let unescaped_string =
+            let (_, unescaped_string) =
                 unescape(&value.string[..], 0, value.loc.file_no(), &mut diagnostics);
             ns.diagnostics.extend(diagnostics);
             resolve_string_literal(&value.loc, unescaped_string, ty, ns)

+ 3 - 3
src/sema/yul/tests/expression.rs

@@ -1584,7 +1584,7 @@ contract foo {
     }
 }
     "#;
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     cache.set_file_contents("test.sol", file.to_string());
 
     let ns = parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::Solana);
@@ -1601,7 +1601,7 @@ contract foo {
 }
     "#;
 
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     cache.set_file_contents("test.sol", file.to_string());
 
     let ns = parse_and_resolve(
@@ -1625,7 +1625,7 @@ contract foo {
 }
     "#;
 
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     cache.set_file_contents("test.sol", file.to_string());
 
     let ns = parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::Solana);

+ 1 - 1
src/sema/yul/tests/mod.rs

@@ -16,7 +16,7 @@ mod types;
 mod unused_variable;
 
 pub(crate) fn parse(src: &'static str) -> ast::Namespace {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     cache.set_file_contents("test.sol", src.to_string());
 
     let ns = parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::EVM);

+ 2 - 2
tests/contract.rs

@@ -63,7 +63,7 @@ fn recurse_directory(path: PathBuf, target: Target) -> io::Result<()> {
 }
 
 fn parse_file(path: PathBuf, target: Target) -> io::Result<()> {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     let filename = add_file(&mut cache, &path, target)?;
 
@@ -128,7 +128,7 @@ fn add_file(cache: &mut FileResolver, path: &Path, target: Target) -> io::Result
         if line.starts_with("import") {
             if let (Some(start), Some(end)) = (line.find('"'), line.rfind('"')) {
                 let file = &line[start + 1..end];
-                if file != "solana" {
+                if !file.is_empty() && file != "solana" {
                     let mut import_path = path.parent().unwrap().to_path_buf();
                     import_path.push(file);
                     println!("adding import {}", import_path.display());

+ 1 - 1
tests/contract_testcases/polkadot/functions/mangling_03.sol

@@ -1,4 +1,4 @@
-import "mangling_02.sol" as X;
+import "./mangling_02.sol" as X;
 
 struct A { uint256 foo; }
 

+ 3 - 0
tests/contract_testcases/solana/empty_import.sol

@@ -0,0 +1,3 @@
+import "" as foo;
+// ---- Expect: diagnostics ----
+// error: 1:8-10: import path empty

+ 1 - 1
tests/contract_testcases/solana/import_contracts_via_object.sol

@@ -1,4 +1,4 @@
-import "simple.sol" as IMP;
+import "./simple.sol" as IMP;
 
 contract C is IMP.A {
 	using IMP.L for *;

+ 3 - 3
tests/contract_testcases/solana/import_free_function.sol

@@ -1,7 +1,7 @@
 // ensure free function can be imported (plain, renamed, import symbol)
-import "for_if_no_else.sol";
-import {foo as renamed_foo} from "for_if_no_else.sol";
-import * as X from "for_if_no_else.sol";
+import "./for_if_no_else.sol";
+import {foo as renamed_foo} from "./for_if_no_else.sol";
+import * as X from "./for_if_no_else.sol";
 
 function bar() {
 	int x = foo();

+ 1 - 1
tests/contract_testcases/solana/import_free_function_chain.sol

@@ -1,5 +1,5 @@
 // ensure free function can be imported via chain
-import "import_free_function.sol" as Y;
+import "./import_free_function.sol" as Y;
 
 function baz() {
 	int x = Y.X.foo();

+ 1 - 1
tests/contract_testcases/solana/keep_on_resolving.sol

@@ -1,4 +1,4 @@
-import "type_decl_broken.sol";
+import "./type_decl_broken.sol";
 
 struct S {
 	in f1;

+ 1 - 1
tests/contract_testcases/solana/type_decl_broken_more.sol

@@ -1,4 +1,4 @@
-import "type_decl.sol";
+import "./type_decl.sol";
 
 function foo(Addr.X x) {}
 

+ 1 - 1
tests/contract_testcases/solana/type_decl_import.sol

@@ -1,4 +1,4 @@
-import "type_decl.sol" as IMP;
+import "./type_decl.sol" as IMP;
 
 contract d {
 	function f(IMP.x c) public {

+ 1 - 1
tests/contract_testcases/solana/using_import.sol

@@ -1,4 +1,4 @@
-import "simple.sol" as simpels;
+import "./simple.sol" as simpels;
 
 function dec(simpels.S s) pure { s.f1 -= 1; }
 using {dec} for simpels.S;

+ 7 - 25
tests/doc_examples.rs

@@ -10,33 +10,15 @@ use solang::{
 use std::{
     ffi::OsStr,
     fs::{read_dir, read_to_string},
+    path::PathBuf,
 };
 
 /// Populates a file resolver with all imports that could be used by some example.
-fn file_resolver(target: Target) -> FileResolver {
-    let mut result = FileResolver::new();
-    result.set_file_contents(
-        "docs/examples/user.sol",
-        r##"
-        struct User { string name; uint count; }
-        function clear_count(User memory user) {
-            user.count = 0;
-        }
-        using {clear_count} for User global;"##
-            .into(),
-    );
-    if let Target::Solana = target {
-        result.set_file_contents(
-                "docs/examples/solana/bobcat.sol",
-                r##"
-                anchor_anchor constant bobcat = anchor_anchor(address'z7FbDfQDfucxJz5o8jrGLgvSbdoeSqX5VrxBb5TVjHq');
-                interface anchor_anchor {
-                    @selector([0xaf, 0xaf, 0x6d, 0x1f, 0x0d, 0x98, 0x9b, 0xed])
-                    function pounce() view external returns(int64);
-                }"##.into(),
-            );
-    }
-    result
+fn file_resolver() -> FileResolver {
+    let mut resolver = FileResolver::default();
+    resolver.add_import_path(&PathBuf::from(".")).unwrap();
+
+    resolver
 }
 
 /// Returns a list of all `.sol` files in the given `dir` path.
@@ -57,7 +39,7 @@ fn get_source_files(dir: &str) -> Vec<String> {
 
 /// Attempts to compile the file at `path` for the `target`, returning the diagnostics on fail.
 fn try_compile(path: &str, target: Target) -> Result<(), Diagnostics> {
-    let mut cache = file_resolver(target);
+    let mut cache = file_resolver();
     cache.set_file_contents(path, read_to_string(path).unwrap());
     let mut ns = solang::parse_and_resolve(OsStr::new(path), &mut cache, target);
     if ns.diagnostics.any_errors() {

+ 8 - 8
tests/evm.rs

@@ -6,7 +6,7 @@ use std::{ffi::OsStr, fs, path::Path};
 use walkdir::WalkDir;
 
 fn test_solidity(src: &str) -> ast::Namespace {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents("test.sol", src.to_string());
 
@@ -210,7 +210,7 @@ fn ethereum_solidity_tests() {
                 .captures(&source)
                 .map(|captures| captures.get(3).unwrap().as_str());
 
-            let (mut cache, names) = set_file_contents(&source, path);
+            let (mut cache, names) = set_file_contents(&source, entry.path());
 
             cache.add_import_path(path).unwrap();
 
@@ -221,7 +221,7 @@ fn ethereum_solidity_tests() {
 
                     if ns.diagnostics.any_errors() {
                         if expect_error.is_none() {
-                            println!("file: {}", entry.path().display());
+                            println!("file: {} name:{}", entry.path().display(), name);
 
                             ns.print_diagnostics_in_plain(&cache, false);
 
@@ -230,7 +230,7 @@ fn ethereum_solidity_tests() {
                             0
                         }
                     } else if let Some(error) = expect_error {
-                        println!("file: {}", entry.path().display());
+                        println!("file: {} name:{}", entry.path().display(), name);
 
                         println!("expecting error {error}");
 
@@ -245,12 +245,12 @@ fn ethereum_solidity_tests() {
         })
         .sum();
 
-    assert_eq!(errors, 1038);
+    assert_eq!(errors, 1036);
 }
 
 fn set_file_contents(source: &str, path: &Path) -> (FileResolver, Vec<String>) {
-    let mut cache = FileResolver::new();
-    let mut name = "test.sol".to_owned();
+    let mut cache = FileResolver::default();
+    let mut name = path.to_string_lossy().to_string();
     let mut names = Vec::new();
     let mut contents = String::new();
     let source_delimiter = regex::Regex::new(r"==== Source: (.*) ====").unwrap();
@@ -271,7 +271,7 @@ fn set_file_contents(source: &str, path: &Path) -> (FileResolver, Vec<String>) {
         } else if let Some(cap) = external_source_delimiter.captures(line) {
             let mut name = cap.get(1).unwrap().as_str().to_owned();
             if let Some(cap) = equals.captures(&name) {
-                let mut ext = path.to_path_buf();
+                let mut ext = path.parent().unwrap().to_path_buf();
                 ext.push(cap.get(2).unwrap().as_str());
                 name = cap.get(1).unwrap().as_str().to_owned();
                 let source = fs::read_to_string(ext).unwrap();

+ 55 - 0
tests/imports.rs

@@ -155,3 +155,58 @@ fn contract_name_defined_twice() {
     assert!(err.contains("relative_import.sol:1:1-6:2 and "));
     assert!(err.ends_with("rel.sol:2:1-16\n"));
 }
+
+#[test]
+fn bad_escape() {
+    let mut cmd = Command::cargo_bin("solang").unwrap();
+
+    let not_ok = cmd
+        .args([
+            "compile",
+            "--target",
+            "solana",
+            "tests/imports_testcases/bad_escape.sol",
+        ])
+        .assert();
+
+    let output = not_ok.get_output();
+    let err = String::from_utf8_lossy(&output.stderr);
+
+    println!("{}", err);
+
+    // The error contains the absolute paths, so we cannot assert the whole string
+    assert!(err.contains(": \\x escape should be followed by two hex digits"));
+    #[cfg(windows)]
+    assert!(err.contains(": string is not a valid filename"));
+    #[cfg(not(windows))]
+    assert!(err.contains(": file not found 'bar�.sol'"));
+}
+
+// Ensure that .\ and ..\ are not interpreted as relative paths on Unix/MacOS
+// Note Windows allows these as relative paths, but we do not.
+#[test]
+fn backslash_path() {
+    let mut cmd = Command::cargo_bin("solang").unwrap();
+
+    let not_ok = cmd
+        .args([
+            "compile",
+            "--target",
+            "solana",
+            "tests/imports_testcases/imports/bar_backslash.sol",
+        ])
+        .assert();
+
+    let output = not_ok.get_output();
+    let err = String::from_utf8_lossy(&output.stderr);
+
+    println!("{}", err);
+
+    #[cfg(windows)]
+    assert!(err.is_empty());
+
+    #[cfg(not(windows))]
+    assert!(err.contains(": file not found '.\\relative_import.sol'"));
+    #[cfg(not(windows))]
+    assert!(err.contains(": file not found '..\\import.sol'"));
+}

+ 4 - 0
tests/imports_testcases/bad_escape.sol

@@ -0,0 +1,4 @@
+
+import "bar\xyf.sol";
+
+import "bar\xff.sol";

+ 3 - 1
tests/imports_testcases/imports/bar.sol

@@ -1,4 +1,6 @@
-import {rel} from "relative_import.sol";
+
+// \u0061 => 'a', \x6f => 'o'
+import {rel} from "./rel\u0061tive_imp\x6frt.sol";
 
 contract c is rel {
     function exceeds() public {

+ 3 - 0
tests/imports_testcases/imports/bar_backslash.sol

@@ -0,0 +1,3 @@
+
+import ".\\relative_import.sol";
+import "..\\import.sol";

+ 1 - 1
tests/polkadot.rs

@@ -1038,7 +1038,7 @@ pub fn build_solidity_with_options(src: &str, log_ret: bool, log_err: bool) -> M
 
 pub fn build_wasm(src: &str, log_ret: bool, log_err: bool) -> Vec<(Vec<u8>, String)> {
     let tmp_file = OsStr::new("test.sol");
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     cache.set_file_contents(tmp_file.to_str().unwrap(), src.to_string());
     let opt = inkwell::OptimizationLevel::Default;
     let target = Target::default_polkadot();

+ 5 - 5
tests/polkadot_tests/events.rs

@@ -95,7 +95,7 @@ fn emit() {
 
 #[test]
 fn event_imported() {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -123,7 +123,7 @@ fn event_imported() {
 
     assert!(!ns.diagnostics.any_errors());
 
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -153,7 +153,7 @@ fn event_imported() {
 
     assert!(!ns.diagnostics.any_errors());
 
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -183,7 +183,7 @@ fn event_imported() {
 
     assert!(!ns.diagnostics.any_errors());
 
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -235,7 +235,7 @@ fn erc20_ink_example() {
                 address indexed to,
                 uint128 value
             );
-        
+
             function emit_event(address from, address to, uint128 value) public {
                 emit Transfer(from, to, value);
             }

+ 22 - 22
tests/polkadot_tests/imports.rs

@@ -6,7 +6,7 @@ use std::ffi::OsStr;
 
 #[test]
 fn enum_import() {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -32,7 +32,7 @@ fn enum_import() {
 
     assert!(!ns.diagnostics.any_errors());
 
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -58,7 +58,7 @@ fn enum_import() {
 
     assert!(!ns.diagnostics.any_errors());
 
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -84,7 +84,7 @@ fn enum_import() {
 
     assert!(!ns.diagnostics.any_errors());
 
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -110,7 +110,7 @@ fn enum_import() {
     );
 
     // from has special handling to avoid making it a keyword
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -127,7 +127,7 @@ fn enum_import() {
         "'frum' found where 'from' expected"
     );
 
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -147,7 +147,7 @@ fn enum_import() {
 
 #[test]
 fn struct_import() {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -173,7 +173,7 @@ fn struct_import() {
 
     assert!(!ns.diagnostics.any_errors());
 
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -202,7 +202,7 @@ fn struct_import() {
 
 #[test]
 fn contract_import() {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -237,7 +237,7 @@ fn contract_import() {
     assert!(!ns.diagnostics.any_errors());
 
     // lets try a importing an import
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -280,7 +280,7 @@ fn contract_import() {
     assert!(!ns.diagnostics.any_errors());
 
     // now let's rename an import in a chain
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -325,7 +325,7 @@ fn contract_import() {
 
 #[test]
 fn circular_import() {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "self.sol",
@@ -349,7 +349,7 @@ fn circular_import() {
 
     assert!(!ns.diagnostics.any_errors());
 
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -394,7 +394,7 @@ fn circular_import() {
 #[test]
 fn import_symbol() {
     // import struct via import symbol
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -425,7 +425,7 @@ fn import_symbol() {
     assert!(!ns.diagnostics.any_errors());
 
     // import contract via import symbol
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -460,7 +460,7 @@ fn import_symbol() {
     assert!(!ns.diagnostics.any_errors());
 
     // import enum in contract via import symbol
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -495,7 +495,7 @@ fn import_symbol() {
     assert!(!ns.diagnostics.any_errors());
 
     // import struct in contract via import symbol chain
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -543,7 +543,7 @@ fn import_symbol() {
 #[test]
 fn enum_import_chain() {
     // import struct in contract via import symbol chain
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -590,7 +590,7 @@ fn enum_import_chain() {
     assert!(!ns.diagnostics.any_errors());
 
     // now with error
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -643,7 +643,7 @@ fn enum_import_chain() {
 #[test]
 fn import_base_dir() {
     // if a imports x/b.sol then when x/b.sol imports, it should use x/ as a base
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -664,7 +664,7 @@ fn import_base_dir() {
     cache.set_file_contents(
         "x/b.sol",
         r#"
-        import "c.sol";
+        import "x/c.sol";
         "#
         .to_string(),
     );
@@ -688,7 +688,7 @@ fn import_base_dir() {
 
 #[test]
 fn event_resolve() {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "IThing.sol",

+ 2 - 2
tests/polkadot_tests/inheritance.rs

@@ -9,7 +9,7 @@ use std::ffi::OsStr;
 
 #[test]
 fn test_abstract() {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",
@@ -52,7 +52,7 @@ fn test_abstract() {
 
     assert_eq!(contracts.len(), 1);
 
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents(
         "a.sol",

+ 2 - 2
tests/solana.rs

@@ -156,7 +156,7 @@ impl<'a> VirtualMachineBuilder<'a> {
     }
 
     pub(crate) fn build(self) -> VirtualMachine {
-        let mut cache = FileResolver::new();
+        let mut cache = FileResolver::default();
 
         cache.set_file_contents("test.sol", self.src.to_string());
 
@@ -1620,7 +1620,7 @@ impl VirtualMachine {
 }
 
 pub fn parse_and_resolve(src: &'static str, target: Target) -> ast::Namespace {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     cache.set_file_contents("test.sol", src.to_string());
 

+ 1 - 1
tests/solana_tests/simple.rs

@@ -331,7 +331,7 @@ fn incrementer() {
 
 #[test]
 fn infinite_loop() {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
 
     let src = String::from(
         r#"

+ 1 - 1
tests/undefined_variable_detection.rs

@@ -8,7 +8,7 @@ use solang::{parse_and_resolve, Target};
 use std::ffi::OsStr;
 
 fn parse_and_codegen(src: &'static str) -> Namespace {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     cache.set_file_contents("test.sol", src.to_string());
     let mut ns = parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::EVM);
     let opt = Options {

+ 2 - 2
tests/unused_variable_detection.rs

@@ -6,14 +6,14 @@ use solang::{parse_and_resolve, Target};
 use std::ffi::OsStr;
 
 fn parse(src: &'static str) -> ast::Namespace {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     cache.set_file_contents("test.sol", src.to_string());
 
     parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::EVM)
 }
 
 fn parse_two_files(src1: &'static str, src2: &'static str) -> ast::Namespace {
-    let mut cache = FileResolver::new();
+    let mut cache = FileResolver::default();
     cache.set_file_contents("test.sol", src1.to_string());
     cache.set_file_contents("test2.sol", src2.to_string());