浏览代码

Add import mapping to match solc's mapping functionality

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 4 年之前
父节点
当前提交
6a8a5f0c15

+ 5 - 0
docs/running.rst

@@ -51,6 +51,11 @@ Options:
   will only search the current directory. This option can be specified multiple times
   will only search the current directory. This option can be specified multiple times
   and the directories will be searched in the order specified.
   and the directories will be searched in the order specified.
 
 
+\\-\\-importmap *map=directory*
+  When resolving ``import`` directives, if the first part of the path matches *map*,
+  search the directory provided for the file. This option can be specified multiple times
+  with different values for map.
+
 \\-\\-help, -h
 \\-\\-help, -h
   This displays a short description of all the options
   This displays a short description of all the options
 
 

+ 4 - 6
src/bin/languageserver/mod.rs

@@ -9,7 +9,7 @@ use tower_lsp::{Client, LanguageServer};
 use tower_lsp::{LspService, Server};
 use tower_lsp::{LspService, Server};
 
 
 use solang::codegen::codegen;
 use solang::codegen::codegen;
-use solang::file_cache::FileCache;
+use solang::file_resolver::FileResolver;
 use solang::parse_and_resolve;
 use solang::parse_and_resolve;
 use solang::parser::pt;
 use solang::parser::pt;
 use solang::sema::{ast, builtin::get_prototype, symtable, tags::render};
 use solang::sema::{ast, builtin::get_prototype, symtable, tags::render};
@@ -49,17 +49,15 @@ impl SolangServer {
     /// Parse file
     /// Parse file
     async fn parse_file(&self, uri: Url) {
     async fn parse_file(&self, uri: Url) {
         if let Ok(path) = uri.to_file_path() {
         if let Ok(path) = uri.to_file_path() {
-            let mut filecache = FileCache::new();
+            let mut resolver = FileResolver::new();
 
 
             let dir = path.parent().unwrap();
             let dir = path.parent().unwrap();
 
 
-            if let Ok(dir) = dir.canonicalize() {
-                filecache.add_import_path(dir);
-            }
+            let _ = resolver.add_import_path(PathBuf::from(dir));
 
 
             let os_str = path.file_name().unwrap();
             let os_str = path.file_name().unwrap();
 
 
-            let mut ns = parse_and_resolve(os_str.to_str().unwrap(), &mut filecache, self.target);
+            let mut ns = parse_and_resolve(os_str.to_str().unwrap(), &mut resolver, self.target);
 
 
             // codegen all the contracts; some additional errors/warnings will be detected here
             // codegen all the contracts; some additional errors/warnings will be detected here
             codegen(&mut ns, &Default::default());
             codegen(&mut ns, &Default::default());

+ 41 - 23
src/bin/solang.rs

@@ -2,6 +2,7 @@ use clap::{App, Arg, ArgMatches};
 use itertools::Itertools;
 use itertools::Itertools;
 use serde::Serialize;
 use serde::Serialize;
 use std::collections::HashMap;
 use std::collections::HashMap;
+use std::ffi::OsString;
 use std::fs::File;
 use std::fs::File;
 use std::io::prelude::*;
 use std::io::prelude::*;
 use std::path::{Path, PathBuf};
 use std::path::{Path, PathBuf};
@@ -9,7 +10,7 @@ use std::path::{Path, PathBuf};
 use solang::abi;
 use solang::abi;
 use solang::codegen::{codegen, Options};
 use solang::codegen::{codegen, Options};
 use solang::emit::Generate;
 use solang::emit::Generate;
-use solang::file_cache::FileCache;
+use solang::file_resolver::FileResolver;
 use solang::sema::{ast::Namespace, diagnostics};
 use solang::sema::{ast::Namespace, diagnostics};
 
 
 mod doc;
 mod doc;
@@ -91,7 +92,17 @@ fn main() {
                 .short("I")
                 .short("I")
                 .long("importpath")
                 .long("importpath")
                 .takes_value(true)
                 .takes_value(true)
-                .multiple(true),
+                .multiple(true)
+                .require_delimiter(true),
+        )
+        .arg(
+            Arg::with_name("IMPORTMAP")
+                .help("Map directory to search for solidity files [format: map=path]")
+                .short("m")
+                .long("importmap")
+                .takes_value(true)
+                .multiple(true)
+                .require_delimiter(true),
         )
         )
         .arg(
         .arg(
             Arg::with_name("CONSTANTFOLDING")
             Arg::with_name("CONSTANTFOLDING")
@@ -161,34 +172,41 @@ fn main() {
 
 
     let math_overflow_check = matches.is_present("MATHOVERFLOW");
     let math_overflow_check = matches.is_present("MATHOVERFLOW");
 
 
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     for filename in matches.values_of("INPUT").unwrap() {
     for filename in matches.values_of("INPUT").unwrap() {
         if let Ok(path) = PathBuf::from(filename).canonicalize() {
         if let Ok(path) = PathBuf::from(filename).canonicalize() {
-            cache.add_import_path(path.parent().unwrap().to_path_buf());
+            let _ = cache.add_import_path(path.parent().unwrap().to_path_buf());
         }
         }
     }
     }
 
 
-    match PathBuf::from(".").canonicalize() {
-        Ok(p) => cache.add_import_path(p),
-        Err(e) => {
-            eprintln!(
-                "error: cannot add current directory to import path: {}",
-                e.to_string()
-            );
-            std::process::exit(1);
-        }
+    if let Err(e) = cache.add_import_path(PathBuf::from(".")) {
+        eprintln!(
+            "error: cannot add current directory to import path: {}",
+            e.to_string()
+        );
+        std::process::exit(1);
     }
     }
 
 
     if let Some(paths) = matches.values_of("IMPORTPATH") {
     if let Some(paths) = matches.values_of("IMPORTPATH") {
-        for p in paths {
-            let path = PathBuf::from(p);
-            match path.canonicalize() {
-                Ok(p) => cache.add_import_path(p),
-                Err(e) => {
-                    eprintln!("error: import path ‘{}’: {}", p, e.to_string());
+        for path in paths {
+            if let Err(e) = cache.add_import_path(PathBuf::from(path)) {
+                eprintln!("error: import path ‘{}’: {}", path, e.to_string());
+                std::process::exit(1);
+            }
+        }
+    }
+
+    if let Some(maps) = matches.values_of("IMPORTMAP") {
+        for p in maps {
+            if let Some((map, path)) = p.split_once('=') {
+                if let Err(e) = cache.add_import_map(OsString::from(map), PathBuf::from(path)) {
+                    eprintln!("error: import path ‘{}’: {}", path, e.to_string());
                     std::process::exit(1);
                     std::process::exit(1);
                 }
                 }
+            } else {
+                eprintln!("error: import map ‘{}’: contains no ‘=’", p);
+                std::process::exit(1);
             }
             }
         }
         }
     }
     }
@@ -356,7 +374,7 @@ fn output_file(matches: &ArgMatches, stem: &str, ext: &str) -> PathBuf {
 
 
 fn process_filename(
 fn process_filename(
     filename: &str,
     filename: &str,
-    cache: &mut FileCache,
+    resolver: &mut FileResolver,
     target: solang::Target,
     target: solang::Target,
     matches: &ArgMatches,
     matches: &ArgMatches,
     json: &mut JsonResult,
     json: &mut JsonResult,
@@ -367,16 +385,16 @@ fn process_filename(
     let mut json_contracts = HashMap::new();
     let mut json_contracts = HashMap::new();
 
 
     // resolve phase
     // resolve phase
-    let mut ns = solang::parse_and_resolve(filename, cache, target);
+    let mut ns = solang::parse_and_resolve(filename, resolver, target);
 
 
     // codegen all the contracts; some additional errors/warnings will be detected here
     // codegen all the contracts; some additional errors/warnings will be detected here
     codegen(&mut ns, opt);
     codegen(&mut ns, opt);
 
 
     if matches.is_present("STD-JSON") {
     if matches.is_present("STD-JSON") {
-        let mut out = diagnostics::message_as_json(&ns, cache);
+        let mut out = diagnostics::message_as_json(&ns, resolver);
         json.errors.append(&mut out);
         json.errors.append(&mut out);
     } else {
     } else {
-        diagnostics::print_messages(cache, &ns, verbose);
+        diagnostics::print_messages(resolver, &ns, verbose);
     }
     }
 
 
     if ns.contracts.is_empty() || diagnostics::any_errors(&ns.diagnostics) {
     if ns.contracts.is_empty() || diagnostics::any_errors(&ns.diagnostics) {

+ 91 - 39
src/file_cache.rs → src/file_resolver.rs

@@ -1,14 +1,16 @@
 use crate::parser::pt::Loc;
 use crate::parser::pt::Loc;
 use crate::sema::ast;
 use crate::sema::ast;
 use std::collections::HashMap;
 use std::collections::HashMap;
+use std::ffi::OsString;
 use std::fs::File;
 use std::fs::File;
-use std::io::prelude::*;
+use std::io;
+use std::io::{prelude::*, Error, ErrorKind};
 use std::path::{Path, PathBuf};
 use std::path::{Path, PathBuf};
 use std::sync::Arc;
 use std::sync::Arc;
 
 
-pub struct FileCache {
+pub struct FileResolver {
     /// Set of import paths search for imports
     /// Set of import paths search for imports
-    import_paths: Vec<PathBuf>,
+    import_paths: Vec<(Option<OsString>, PathBuf)>,
     /// List file by import path
     /// List file by import path
     cached_paths: HashMap<PathBuf, usize>,
     cached_paths: HashMap<PathBuf, usize>,
     /// The actual file contents
     /// The actual file contents
@@ -31,25 +33,43 @@ pub struct ResolvedFile {
     base: PathBuf,
     base: PathBuf,
 }
 }
 
 
-impl Default for FileCache {
+impl Default for FileResolver {
     fn default() -> Self {
     fn default() -> Self {
-        FileCache::new()
+        FileResolver::new()
     }
     }
 }
 }
 
 
-impl FileCache {
+impl FileResolver {
     /// Create a new file cache object
     /// Create a new file cache object
     pub fn new() -> Self {
     pub fn new() -> Self {
-        FileCache {
+        FileResolver {
             import_paths: Vec::new(),
             import_paths: Vec::new(),
             cached_paths: HashMap::new(),
             cached_paths: HashMap::new(),
             files: Vec::new(),
             files: Vec::new(),
         }
         }
     }
     }
 
 
-    /// Add import path. This must be the canonicalized path
-    pub fn add_import_path(&mut self, path: PathBuf) {
-        self.import_paths.push(path);
+    /// Add import path
+    pub fn add_import_path(&mut self, path: PathBuf) -> io::Result<()> {
+        self.import_paths.push((None, path.canonicalize()?));
+        Ok(())
+    }
+
+    /// Add import map
+    pub fn add_import_map(&mut self, map: OsString, path: PathBuf) -> io::Result<()> {
+        if self
+            .import_paths
+            .iter()
+            .any(|(m, _)| m.as_ref() == Some(&map))
+        {
+            Err(Error::new(
+                ErrorKind::Other,
+                format!("duplicate mapping for ‘{}’", map.to_string_lossy()),
+            ))
+        } else {
+            self.import_paths.push((Some(map), path.canonicalize()?));
+            Ok(())
+        }
     }
     }
 
 
     /// Update the cache for the filename with the given contents
     /// Update the cache for the filename with the given contents
@@ -114,6 +134,35 @@ impl FileCache {
         filename: &str,
         filename: &str,
     ) -> Result<ResolvedFile, String> {
     ) -> Result<ResolvedFile, String> {
         let path = PathBuf::from(filename);
         let path = PathBuf::from(filename);
+
+        // 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 {
+                    if first_part == mapping {
+                        // match!
+                        if let Ok(full_path) = import_path.join(&relpath).canonicalize() {
+                            let file_no = 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,
+                                file_no,
+                            });
+                        }
+                    }
+                }
+            }
+        }
+
         let mut start_import_no = 0;
         let mut start_import_no = 0;
 
 
         // first try relative to the parent
         // first try relative to the parent
@@ -138,21 +187,23 @@ impl FileCache {
                 });
                 });
             }
             }
 
 
-            let import_path = self.import_paths[*import_no].join(base);
-
-            if let Ok(full_path) = import_path.join(path.clone()).canonicalize() {
-                let file_no = 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: *import_no,
-                    file_no,
-                });
+            if let (None, import_path) = &self.import_paths[*import_no] {
+                let import_path = import_path.join(base);
+
+                if let Ok(full_path) = import_path.join(path.clone()).canonicalize() {
+                    let file_no = 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: *import_no,
+                        file_no,
+                    });
+                }
             }
             }
 
 
             // start with the next import
             // start with the next import
@@ -178,21 +229,22 @@ impl FileCache {
         // walk over the import paths until we find one that resolves
         // walk over the import paths until we find one that resolves
         for i in 0..self.import_paths.len() {
         for i in 0..self.import_paths.len() {
             let import_no = (i + start_import_no) % self.import_paths.len();
             let import_no = (i + start_import_no) % self.import_paths.len();
-            let import_path = &self.import_paths[import_no];
-
-            if let Ok(full_path) = import_path.join(path.clone()).canonicalize() {
-                let base = full_path
-                    .parent()
-                    .expect("path should include filename")
-                    .to_path_buf();
-                let file_no = self.load_file(&full_path)?;
 
 
-                return Ok(ResolvedFile {
-                    full_path,
-                    file_no,
-                    import_no,
-                    base,
-                });
+            if let (None, import_path) = &self.import_paths[import_no] {
+                if let Ok(full_path) = import_path.join(path.clone()).canonicalize() {
+                    let base = full_path
+                        .parent()
+                        .expect("path should include filename")
+                        .to_path_buf();
+                    let file_no = self.load_file(&full_path)?;
+
+                    return Ok(ResolvedFile {
+                        full_path,
+                        file_no,
+                        import_no,
+                        base,
+                    });
+                }
             }
             }
         }
         }
 
 

+ 11 - 7
src/lib.rs

@@ -1,7 +1,7 @@
 pub mod abi;
 pub mod abi;
 pub mod codegen;
 pub mod codegen;
 pub mod emit;
 pub mod emit;
-pub mod file_cache;
+pub mod file_resolver;
 pub mod linker;
 pub mod linker;
 pub mod parser;
 pub mod parser;
 
 
@@ -11,7 +11,7 @@ pub mod parser;
 #[allow(clippy::result_unit_err)]
 #[allow(clippy::result_unit_err)]
 pub mod sema;
 pub mod sema;
 
 
-use file_cache::FileCache;
+use file_resolver::FileResolver;
 use inkwell::OptimizationLevel;
 use inkwell::OptimizationLevel;
 use sema::ast;
 use sema::ast;
 use sema::diagnostics;
 use sema::diagnostics;
@@ -53,12 +53,12 @@ impl fmt::Display for Target {
 /// The ctx is the inkwell llvm context.
 /// The ctx is the inkwell llvm context.
 pub fn compile(
 pub fn compile(
     filename: &str,
     filename: &str,
-    cache: &mut FileCache,
+    resolver: &mut FileResolver,
     opt_level: OptimizationLevel,
     opt_level: OptimizationLevel,
     target: Target,
     target: Target,
     math_overflow_check: bool,
     math_overflow_check: bool,
 ) -> (Vec<(Vec<u8>, String)>, ast::Namespace) {
 ) -> (Vec<(Vec<u8>, String)>, ast::Namespace) {
-    let mut ns = parse_and_resolve(filename, cache, target);
+    let mut ns = parse_and_resolve(filename, resolver, target);
 
 
     if diagnostics::any_errors(&ns.diagnostics) {
     if diagnostics::any_errors(&ns.diagnostics) {
         return (Vec::new(), ns);
         return (Vec::new(), ns);
@@ -106,7 +106,11 @@ pub fn compile_many<'a>(
 /// informational messages like `found contact N`.
 /// informational messages like `found contact N`.
 ///
 ///
 /// Note that multiple contracts can be specified in on solidity source file.
 /// Note that multiple contracts can be specified in on solidity source file.
-pub fn parse_and_resolve(filename: &str, cache: &mut FileCache, target: Target) -> ast::Namespace {
+pub fn parse_and_resolve(
+    filename: &str,
+    resolver: &mut FileResolver,
+    target: Target,
+) -> ast::Namespace {
     let mut ns = ast::Namespace::new(
     let mut ns = ast::Namespace::new(
         target,
         target,
         match target {
         match target {
@@ -123,7 +127,7 @@ pub fn parse_and_resolve(filename: &str, cache: &mut FileCache, target: Target)
         },
         },
     );
     );
 
 
-    match cache.resolve_file(None, filename) {
+    match resolver.resolve_file(None, filename) {
         Err(message) => {
         Err(message) => {
             ns.diagnostics.push(ast::Diagnostic {
             ns.diagnostics.push(ast::Diagnostic {
                 ty: ast::ErrorType::ParserError,
                 ty: ast::ErrorType::ParserError,
@@ -134,7 +138,7 @@ pub fn parse_and_resolve(filename: &str, cache: &mut FileCache, target: Target)
             });
             });
         }
         }
         Ok(file) => {
         Ok(file) => {
-            sema::sema(&file, cache, &mut ns);
+            sema::sema(&file, resolver, &mut ns);
         }
         }
     }
     }
 
 

+ 1 - 1
src/sema/ast.rs

@@ -370,7 +370,7 @@ pub struct File {
     pub path: PathBuf,
     pub path: PathBuf,
     /// Used for offset to line-column conversions
     /// Used for offset to line-column conversions
     pub line_starts: Vec<usize>,
     pub line_starts: Vec<usize>,
-    /// Indicates the file number in FileCache.files
+    /// Indicates the file number in FileResolver.files
     pub cache_no: usize,
     pub cache_no: usize,
 }
 }
 
 

+ 4 - 4
src/sema/diagnostics.rs

@@ -1,5 +1,5 @@
 use super::ast::{Diagnostic, ErrorType, Level, Namespace, Note};
 use super::ast::{Diagnostic, ErrorType, Level, Namespace, Note};
-use crate::file_cache::FileCache;
+use crate::file_resolver::FileResolver;
 use crate::parser::pt::Loc;
 use crate::parser::pt::Loc;
 use serde::Serialize;
 use serde::Serialize;
 
 
@@ -131,7 +131,7 @@ impl Diagnostic {
         }
         }
     }
     }
 
 
-    fn formatted_message(&self, ns: &Namespace, cache: &FileCache) -> String {
+    fn formatted_message(&self, ns: &Namespace, cache: &FileResolver) -> String {
         let mut s = if let Some(pos) = self.pos {
         let mut s = if let Some(pos) = self.pos {
             let loc = ns.files[pos.0].loc_to_string(&pos);
             let loc = ns.files[pos.0].loc_to_string(&pos);
 
 
@@ -178,7 +178,7 @@ impl Diagnostic {
     }
     }
 }
 }
 
 
-pub fn print_messages(cache: &FileCache, ns: &Namespace, debug: bool) {
+pub fn print_messages(cache: &FileResolver, ns: &Namespace, debug: bool) {
     for msg in &ns.diagnostics {
     for msg in &ns.diagnostics {
         if !debug && msg.level == Level::Debug {
         if !debug && msg.level == Level::Debug {
             continue;
             continue;
@@ -212,7 +212,7 @@ pub struct OutputJson {
     pub formattedMessage: String,
     pub formattedMessage: String,
 }
 }
 
 
-pub fn message_as_json(ns: &Namespace, cache: &FileCache) -> Vec<OutputJson> {
+pub fn message_as_json(ns: &Namespace, cache: &FileResolver) -> Vec<OutputJson> {
     let mut json = Vec::new();
     let mut json = Vec::new();
 
 
     for msg in &ns.diagnostics {
     for msg in &ns.diagnostics {

+ 9 - 9
src/sema/mod.rs

@@ -31,7 +31,7 @@ use self::expression::expression;
 use self::functions::{resolve_params, resolve_returns};
 use self::functions::{resolve_params, resolve_returns};
 use self::symtable::Symtable;
 use self::symtable::Symtable;
 use self::variables::var_decl;
 use self::variables::var_decl;
-use crate::file_cache::{FileCache, ResolvedFile};
+use crate::file_resolver::{FileResolver, ResolvedFile};
 use crate::sema::unused_variable::{check_unused_events, check_unused_namespace_variables};
 use crate::sema::unused_variable::{check_unused_events, check_unused_namespace_variables};
 
 
 pub type ArrayDimension = Option<(pt::Loc, BigInt)>;
 pub type ArrayDimension = Option<(pt::Loc, BigInt)>;
@@ -42,8 +42,8 @@ pub const SOLANA_SPARSE_ARRAY_SIZE: u64 = 1024;
 
 
 /// Load a file file from the cache, parse and resolve it. The file must be present in
 /// Load a file file from the cache, parse and resolve it. The file must be present in
 /// the cache.
 /// the cache.
-pub fn sema(file: &ResolvedFile, cache: &mut FileCache, ns: &mut ast::Namespace) {
-    sema_file(file, cache, ns);
+pub fn sema(file: &ResolvedFile, resolver: &mut FileResolver, ns: &mut ast::Namespace) {
+    sema_file(file, resolver, ns);
 
 
     // Checks for unused variables
     // Checks for unused variables
     check_unused_namespace_variables(ns);
     check_unused_namespace_variables(ns);
@@ -51,10 +51,10 @@ pub fn sema(file: &ResolvedFile, cache: &mut FileCache, ns: &mut ast::Namespace)
 }
 }
 
 
 /// Parse and resolve a file and its imports in a recursive manner.
 /// Parse and resolve a file and its imports in a recursive manner.
-fn sema_file(file: &ResolvedFile, cache: &mut FileCache, ns: &mut ast::Namespace) {
+fn sema_file(file: &ResolvedFile, resolver: &mut FileResolver, ns: &mut ast::Namespace) {
     let file_no = ns.files.len();
     let file_no = ns.files.len();
 
 
-    let (source_code, file_cache_no) = cache.get_file_contents_and_number(&file.full_path);
+    let (source_code, file_cache_no) = resolver.get_file_contents_and_number(&file.full_path);
 
 
     ns.files.push(ast::File::new(
     ns.files.push(ast::File::new(
         file.full_path.clone(),
         file.full_path.clone(),
@@ -96,7 +96,7 @@ fn sema_file(file: &ResolvedFile, cache: &mut FileCache, ns: &mut ast::Namespace
                 resolve_pragma(name, value, ns);
                 resolve_pragma(name, value, ns);
             }
             }
             pt::SourceUnitPart::ImportDirective(import) => {
             pt::SourceUnitPart::ImportDirective(import) => {
-                resolve_import(import, Some(file), file_no, cache, ns);
+                resolve_import(import, Some(file), file_no, resolver, ns);
             }
             }
             _ => (),
             _ => (),
         }
         }
@@ -164,7 +164,7 @@ fn resolve_import(
     import: &pt::Import,
     import: &pt::Import,
     parent: Option<&ResolvedFile>,
     parent: Option<&ResolvedFile>,
     file_no: usize,
     file_no: usize,
-    cache: &mut FileCache,
+    resolver: &mut FileResolver,
     ns: &mut ast::Namespace,
     ns: &mut ast::Namespace,
 ) {
 ) {
     let filename = match import {
     let filename = match import {
@@ -173,7 +173,7 @@ fn resolve_import(
         pt::Import::Rename(f, _) => f,
         pt::Import::Rename(f, _) => f,
     };
     };
 
 
-    let import_file_no = match cache.resolve_file(parent, &filename.string) {
+    let import_file_no = match resolver.resolve_file(parent, &filename.string) {
         Err(message) => {
         Err(message) => {
             ns.diagnostics
             ns.diagnostics
                 .push(ast::Diagnostic::error(filename.loc, message));
                 .push(ast::Diagnostic::error(filename.loc, message));
@@ -182,7 +182,7 @@ fn resolve_import(
         }
         }
         Ok(file) => {
         Ok(file) => {
             if !ns.files.iter().any(|f| f.path == file.full_path) {
             if !ns.files.iter().any(|f| f.path == file.full_path) {
-                sema_file(&file, cache, ns);
+                sema_file(&file, resolver, ns);
 
 
                 // give up if we failed
                 // give up if we failed
                 if diagnostics::any_errors(&ns.diagnostics) {
                 if diagnostics::any_errors(&ns.diagnostics) {

+ 3 - 3
tests/ewasm.rs

@@ -10,7 +10,7 @@ use tiny_keccak::{Hasher, Keccak};
 use wasmi::memory_units::Pages;
 use wasmi::memory_units::Pages;
 use wasmi::*;
 use wasmi::*;
 
 
-use solang::file_cache::FileCache;
+use solang::file_resolver::FileResolver;
 use solang::sema::{ast, diagnostics};
 use solang::sema::{ast, diagnostics};
 use solang::{compile, Target};
 use solang::{compile, Target};
 
 
@@ -794,7 +794,7 @@ impl TestRuntime {
 }
 }
 
 
 fn build_solidity(src: &str) -> TestRuntime {
 fn build_solidity(src: &str) -> TestRuntime {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents("test.sol", src.to_string());
     cache.set_file_contents("test.sol", src.to_string());
 
 
@@ -852,7 +852,7 @@ fn simple_solidiy_compile_and_run() {
 }
 }
 
 
 pub fn parse_and_resolve(src: &'static str, target: Target) -> ast::Namespace {
 pub fn parse_and_resolve(src: &'static str, target: Target) -> ast::Namespace {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents("test.sol", src.to_string());
     cache.set_file_contents("test.sol", src.to_string());
 
 

+ 94 - 0
tests/imports.rs

@@ -0,0 +1,94 @@
+use assert_cmd::Command;
+
+#[test]
+fn import_map_dup() {
+    let mut cmd = Command::cargo_bin("solang").unwrap();
+    let dup = cmd
+        .args(&[
+            "--importmap",
+            "foo=tests",
+            "--importmap",
+            "foo=tests",
+            "foo.sol",
+        ])
+        .env("exit", "1")
+        .assert();
+
+    let output = dup.get_output();
+    let stderr = String::from_utf8_lossy(&output.stderr);
+
+    println!("stderr: {}", stderr);
+
+    assert_eq!(
+        stderr,
+        "error: import path ‘tests’: duplicate mapping for ‘foo’\n"
+    );
+}
+
+#[test]
+fn import_map_badpath() {
+    let mut cmd = Command::cargo_bin("solang").unwrap();
+    let badpath = cmd
+        .args(&["--importmap", "foo=/does/not/exist", "bar.sol"])
+        .env("exit", "1")
+        .assert();
+
+    let output = badpath.get_output();
+    let stderr = String::from_utf8_lossy(&output.stderr);
+
+    println!("stderr: {}", stderr);
+
+    assert!(stderr.contains("error: import path ‘/does/not/exist’: "));
+}
+
+#[test]
+fn import_map() {
+    let mut cmd = Command::cargo_bin("solang").unwrap();
+    let assert = cmd
+        .args(&["--importmap", "foo=imports/", "import_map.sol"])
+        .current_dir("tests/imports_testcases")
+        .assert();
+
+    let output = assert.get_output();
+
+    assert_eq!(String::from_utf8_lossy(&output.stderr), "");
+
+    let mut cmd = Command::cargo_bin("solang").unwrap();
+    let badpath = cmd
+        .args(&["import_map.sol"])
+        .current_dir("tests/imports_testcases")
+        .assert();
+
+    let output = badpath.get_output();
+    let stderr = String::from_utf8_lossy(&output.stderr);
+
+    println!("stderr: {}", stderr);
+
+    assert!(stderr.contains("import_map.sol:1:8-21: error: file not found ‘foo/bar.sol’"));
+}
+
+#[test]
+fn import() {
+    let mut cmd = Command::cargo_bin("solang").unwrap();
+    let assert = cmd
+        .args(&["--importpath", "imports", "import.sol"])
+        .current_dir("tests/imports_testcases")
+        .assert();
+
+    let output = assert.get_output();
+
+    assert_eq!(String::from_utf8_lossy(&output.stderr), "");
+
+    let mut cmd = Command::cargo_bin("solang").unwrap();
+    let badpath = cmd
+        .args(&["import.sol"])
+        .current_dir("tests/imports_testcases")
+        .assert();
+
+    let output = badpath.get_output();
+    let stderr = String::from_utf8_lossy(&output.stderr);
+
+    println!("stderr: {}", stderr);
+
+    assert!(stderr.contains("error: file not found ‘bar.sol’"));
+}

+ 1 - 0
tests/imports_testcases/import.sol

@@ -0,0 +1 @@
+import "bar.sol";

+ 1 - 0
tests/imports_testcases/import_map.sol

@@ -0,0 +1 @@
+import "foo/bar.sol";

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

@@ -0,0 +1 @@
+contract c {}

+ 3 - 3
tests/solana.rs

@@ -22,7 +22,7 @@ use solang::{
     codegen::{codegen, Options},
     codegen::{codegen, Options},
     compile_many,
     compile_many,
     emit::Generate,
     emit::Generate,
-    file_cache::FileCache,
+    file_resolver::FileResolver,
     sema::{ast, diagnostics},
     sema::{ast, diagnostics},
     Target,
     Target,
 };
 };
@@ -110,7 +110,7 @@ struct Assign {
 }
 }
 
 
 fn build_solidity(src: &str) -> VirtualMachine {
 fn build_solidity(src: &str) -> VirtualMachine {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents("test.sol", src.to_string());
     cache.set_file_contents("test.sol", src.to_string());
 
 
@@ -1522,7 +1522,7 @@ impl VirtualMachine {
 }
 }
 
 
 pub fn parse_and_resolve(src: &'static str, target: Target) -> ast::Namespace {
 pub fn parse_and_resolve(src: &'static str, target: Target) -> ast::Namespace {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents("test.sol", src.to_string());
     cache.set_file_contents("test.sol", src.to_string());
 
 

+ 4 - 4
tests/substrate.rs

@@ -10,7 +10,7 @@ use wasmi::memory_units::Pages;
 use wasmi::*;
 use wasmi::*;
 
 
 use solang::abi;
 use solang::abi;
-use solang::file_cache::FileCache;
+use solang::file_resolver::FileResolver;
 use solang::sema::ast;
 use solang::sema::ast;
 use solang::sema::diagnostics;
 use solang::sema::diagnostics;
 use solang::{compile, Target};
 use solang::{compile, Target};
@@ -1181,7 +1181,7 @@ impl TestRuntime {
 }
 }
 
 
 pub fn parse_and_resolve(src: &'static str, target: Target) -> ast::Namespace {
 pub fn parse_and_resolve(src: &'static str, target: Target) -> ast::Namespace {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents("test.sol", src.to_string());
     cache.set_file_contents("test.sol", src.to_string());
 
 
@@ -1189,7 +1189,7 @@ pub fn parse_and_resolve(src: &'static str, target: Target) -> ast::Namespace {
 }
 }
 
 
 pub fn build_solidity(src: &'static str) -> TestRuntime {
 pub fn build_solidity(src: &'static str) -> TestRuntime {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents("test.sol", src.to_string());
     cache.set_file_contents("test.sol", src.to_string());
 
 
@@ -1226,7 +1226,7 @@ pub fn build_solidity(src: &'static str) -> TestRuntime {
 }
 }
 
 
 pub fn build_solidity_with_overflow_check(src: &'static str) -> TestRuntime {
 pub fn build_solidity_with_overflow_check(src: &'static str) -> TestRuntime {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents("test.sol", src.to_string());
     cache.set_file_contents("test.sol", src.to_string());
 
 

+ 5 - 5
tests/substrate_tests/events.rs

@@ -1,7 +1,7 @@
 use crate::{build_solidity, first_error, first_warning, no_errors, parse_and_resolve};
 use crate::{build_solidity, first_error, first_warning, no_errors, parse_and_resolve};
 use parity_scale_codec::Encode;
 use parity_scale_codec::Encode;
 use parity_scale_codec_derive::{Decode, Encode};
 use parity_scale_codec_derive::{Decode, Encode};
-use solang::file_cache::FileCache;
+use solang::file_resolver::FileResolver;
 use solang::Target;
 use solang::Target;
 
 
 #[test]
 #[test]
@@ -315,7 +315,7 @@ fn emit() {
 
 
 #[test]
 #[test]
 fn event_imported() {
 fn event_imported() {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -343,7 +343,7 @@ fn event_imported() {
 
 
     no_errors(ns.diagnostics);
     no_errors(ns.diagnostics);
 
 
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -373,7 +373,7 @@ fn event_imported() {
 
 
     no_errors(ns.diagnostics);
     no_errors(ns.diagnostics);
 
 
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -403,7 +403,7 @@ fn event_imported() {
 
 
     no_errors(ns.diagnostics);
     no_errors(ns.diagnostics);
 
 
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",

+ 21 - 21
tests/substrate_tests/imports.rs

@@ -1,10 +1,10 @@
 use crate::{first_error, no_errors};
 use crate::{first_error, no_errors};
-use solang::file_cache::FileCache;
+use solang::file_resolver::FileResolver;
 use solang::Target;
 use solang::Target;
 
 
 #[test]
 #[test]
 fn enum_import() {
 fn enum_import() {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -30,7 +30,7 @@ fn enum_import() {
 
 
     no_errors(ns.diagnostics);
     no_errors(ns.diagnostics);
 
 
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -56,7 +56,7 @@ fn enum_import() {
 
 
     no_errors(ns.diagnostics);
     no_errors(ns.diagnostics);
 
 
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -82,7 +82,7 @@ fn enum_import() {
 
 
     no_errors(ns.diagnostics);
     no_errors(ns.diagnostics);
 
 
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -108,7 +108,7 @@ fn enum_import() {
     );
     );
 
 
     // from has special handling to avoid making it a keyword
     // from has special handling to avoid making it a keyword
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -125,7 +125,7 @@ fn enum_import() {
         "‘frum’ found where ‘from’ expected"
         "‘frum’ found where ‘from’ expected"
     );
     );
 
 
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -145,7 +145,7 @@ fn enum_import() {
 
 
 #[test]
 #[test]
 fn struct_import() {
 fn struct_import() {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -171,7 +171,7 @@ fn struct_import() {
 
 
     no_errors(ns.diagnostics);
     no_errors(ns.diagnostics);
 
 
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -200,7 +200,7 @@ fn struct_import() {
 
 
 #[test]
 #[test]
 fn contract_import() {
 fn contract_import() {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -235,7 +235,7 @@ fn contract_import() {
     no_errors(ns.diagnostics);
     no_errors(ns.diagnostics);
 
 
     // lets try a importing an import
     // lets try a importing an import
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -278,7 +278,7 @@ fn contract_import() {
     no_errors(ns.diagnostics);
     no_errors(ns.diagnostics);
 
 
     // now let's rename an import in a chain
     // now let's rename an import in a chain
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -323,7 +323,7 @@ fn contract_import() {
 
 
 #[test]
 #[test]
 fn circular_import() {
 fn circular_import() {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "self.sol",
         "self.sol",
@@ -343,7 +343,7 @@ fn circular_import() {
 
 
     no_errors(ns.diagnostics);
     no_errors(ns.diagnostics);
 
 
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -388,7 +388,7 @@ fn circular_import() {
 #[test]
 #[test]
 fn import_symbol() {
 fn import_symbol() {
     // import struct via import symbol
     // import struct via import symbol
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -419,7 +419,7 @@ fn import_symbol() {
     no_errors(ns.diagnostics);
     no_errors(ns.diagnostics);
 
 
     // import contract via import symbol
     // import contract via import symbol
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -454,7 +454,7 @@ fn import_symbol() {
     no_errors(ns.diagnostics);
     no_errors(ns.diagnostics);
 
 
     // import enum in contract via import symbol
     // import enum in contract via import symbol
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -489,7 +489,7 @@ fn import_symbol() {
     no_errors(ns.diagnostics);
     no_errors(ns.diagnostics);
 
 
     // import struct in contract via import symbol chain
     // import struct in contract via import symbol chain
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -537,7 +537,7 @@ fn import_symbol() {
 #[test]
 #[test]
 fn enum_import_chain() {
 fn enum_import_chain() {
     // import struct in contract via import symbol chain
     // import struct in contract via import symbol chain
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -584,7 +584,7 @@ fn enum_import_chain() {
     no_errors(ns.diagnostics);
     no_errors(ns.diagnostics);
 
 
     // now with error
     // now with error
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -637,7 +637,7 @@ fn enum_import_chain() {
 #[test]
 #[test]
 fn import_base_dir() {
 fn import_base_dir() {
     // if a imports x/b.sol then when x/b.sol imports, it should use x/ as a base
     // if a imports x/b.sol then when x/b.sol imports, it should use x/ as a base
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",

+ 3 - 3
tests/substrate_tests/inheritance.rs

@@ -1,7 +1,7 @@
 use crate::{build_solidity, first_error, first_warning, no_errors, parse_and_resolve};
 use crate::{build_solidity, first_error, first_warning, no_errors, parse_and_resolve};
 use parity_scale_codec::Encode;
 use parity_scale_codec::Encode;
 use parity_scale_codec_derive::{Decode, Encode};
 use parity_scale_codec_derive::{Decode, Encode};
-use solang::file_cache::FileCache;
+use solang::file_resolver::FileResolver;
 use solang::Target;
 use solang::Target;
 
 
 #[test]
 #[test]
@@ -94,7 +94,7 @@ fn test_abstract() {
         "cannot construct ‘foo’ of type ‘abstract contract’"
         "cannot construct ‘foo’ of type ‘abstract contract’"
     );
     );
 
 
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",
@@ -128,7 +128,7 @@ fn test_abstract() {
 
 
     assert_eq!(contracts.len(), 1);
     assert_eq!(contracts.len(), 1);
 
 
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
 
 
     cache.set_file_contents(
     cache.set_file_contents(
         "a.sol",
         "a.sol",

+ 2 - 2
tests/undefined_variable_detection.rs

@@ -1,11 +1,11 @@
 use solang::codegen::{codegen, Options};
 use solang::codegen::{codegen, Options};
-use solang::file_cache::FileCache;
+use solang::file_resolver::FileResolver;
 use solang::sema::ast::Diagnostic;
 use solang::sema::ast::Diagnostic;
 use solang::sema::ast::{Level, Namespace};
 use solang::sema::ast::{Level, Namespace};
 use solang::{parse_and_resolve, Target};
 use solang::{parse_and_resolve, Target};
 
 
 fn parse_and_codegen(src: &'static str) -> Namespace {
 fn parse_and_codegen(src: &'static str) -> Namespace {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
     cache.set_file_contents("test.sol", src.to_string());
     cache.set_file_contents("test.sol", src.to_string());
     let mut ns = parse_and_resolve("test.sol", &mut cache, Target::Generic);
     let mut ns = parse_and_resolve("test.sol", &mut cache, Target::Generic);
 
 

+ 3 - 3
tests/unused_variable_detection.rs

@@ -1,18 +1,18 @@
 use itertools::Itertools;
 use itertools::Itertools;
-use solang::file_cache::FileCache;
+use solang::file_resolver::FileResolver;
 use solang::sema::ast;
 use solang::sema::ast;
 use solang::sema::ast::{Diagnostic, Level};
 use solang::sema::ast::{Diagnostic, Level};
 use solang::{parse_and_resolve, Target};
 use solang::{parse_and_resolve, Target};
 
 
 fn generic_target_parse(src: &'static str) -> ast::Namespace {
 fn generic_target_parse(src: &'static str) -> ast::Namespace {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
     cache.set_file_contents("test.sol", src.to_string());
     cache.set_file_contents("test.sol", src.to_string());
 
 
     parse_and_resolve("test.sol", &mut cache, Target::Generic)
     parse_and_resolve("test.sol", &mut cache, Target::Generic)
 }
 }
 
 
 fn generic_parse_two_files(src1: &'static str, src2: &'static str) -> ast::Namespace {
 fn generic_parse_two_files(src1: &'static str, src2: &'static str) -> ast::Namespace {
-    let mut cache = FileCache::new();
+    let mut cache = FileResolver::new();
     cache.set_file_contents("test.sol", src1.to_string());
     cache.set_file_contents("test.sol", src1.to_string());
     cache.set_file_contents("test2.sol", src2.to_string());
     cache.set_file_contents("test2.sol", src2.to_string());