Skip to content

Commit

Permalink
add support for tricky remappings cases
Browse files Browse the repository at this point in the history
  • Loading branch information
t81lal committed May 13, 2024
1 parent afe9f43 commit 58277d6
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 25 deletions.
4 changes: 3 additions & 1 deletion src/solidity_parser/ast/ast2builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2334,7 +2334,9 @@ def refine_unit_or_part(self, ast1_node: Union[solnodes1.SourceUnit, solnodes2.F
for part in ast1_node.parts:
if isinstance(part, solnodes1.UsingDirective):
if part.library_name:
library_scope = part.scope.find_user_type_scope(part.library_name.text, find_base_symbol=True)
# get the parent scope of the usingdirective, usually a FileScope as the proxy scope gets
# in the using directive scope
library_scope = part.scope.parent_scope.find_user_type_scope(part.library_name.text, find_base_symbol=True)
assert isinstance(library_scope.value, solnodes1.LibraryDefinition)
library = self.type_helper.get_contract_type(library_scope)
if isinstance(part.override_type, soltypes.AnyType):
Expand Down
23 changes: 18 additions & 5 deletions src/solidity_parser/ast/symtab.py
Original file line number Diff line number Diff line change
Expand Up @@ -1036,11 +1036,24 @@ def process_or_find_from_base_dir(self, relative_source_unit_name: str | pathlib
# sanitise inputs if windows paths are given
relative_source_unit_name = relative_source_unit_name.replace('\\', '/')

source_unit_name = self.vfs._compute_source_unit_name(relative_source_unit_name, '')
fs = self.root_scope.find_single(FileScope.alias(source_unit_name))
if fs:
return fs
return self.process_file(source_unit_name)
source_unit_names = self.vfs._compute_possible_source_unit_names(relative_source_unit_name, '')

# try to find already loaded ones first
for sun in source_unit_names:
fs = self.root_scope.find_single(FileScope.alias(sun))
if fs:
return fs

# try to load it
for sun in source_unit_names:
try:
return self.process_file(sun)
except ValueError:
# thrown when process_file can't find anything: try the next sun
logging.getLogger('ST').exception(f'FS Processing attempt failed for {sun}')
continue

raise ValueError(f'Could not load {relative_source_unit_name}')

def process_file(self, source_unit_name: str, source_units: list[solnodes.SourceUnit] = None):
if source_units is None:
Expand Down
48 changes: 32 additions & 16 deletions src/solidity_parser/filesys.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,23 +155,28 @@ def add_import_remapping(self, context, prefix, target):
self.import_remaps.append(ImportMapping(context, prefix, target))

def lookup_import_path(self, import_path: str, importer_source_unit_name: str = None) -> LoadedSource:
import_source_name = self._compute_source_unit_name(import_path, importer_source_unit_name)
import_source_unit_names = self._compute_possible_source_unit_names(import_path, importer_source_unit_name)

if import_source_name in self.sources:
return self.sources[import_source_name]
for source_unit_name in import_source_unit_names:
if source_unit_name in self.sources:
return self.sources[source_unit_name]

logging.getLogger('VFS').debug(f'Import path {import_path} in {importer_source_unit_name} => {import_source_name}')
logging.getLogger('VFS').debug(f'Possible Import path {import_path} in {importer_source_unit_name} => {source_unit_name}')

# When the source is not available in the virtual filesystem, the compiler passes the source unit name to the
# import callback. The Host Filesystem Loader will attempt to use it as a path and look up the file on disk.
origin, contents = self._read_file_callback(import_source_name, self._base_path, self.include_paths)
# "When the source is not available in the virtual filesystem, the compiler passes the source unit name to the
# import callback. The Host Filesystem Loader will attempt to use it as a path and look up the file on disk."
try:
origin, contents = self._read_file_callback(source_unit_name, self._base_path, self.include_paths)
except ValueError:
# thrown if the callback fails, try the next source unit name
continue

if contents:
loaded_source = self._add_loaded_source(import_source_name, contents, origin=origin)
if loaded_source:
return loaded_source
if contents:
loaded_source = self._add_loaded_source(source_unit_name, contents, origin=origin)
if loaded_source:
return loaded_source

raise ValueError(f"Can't import {import_path} from {importer_source_unit_name} ({bool(contents)},{bool(loaded_source)})")
raise ValueError(f"Can't import {import_path} from {importer_source_unit_name}")

def _add_loaded_source(self, source_unit_name: str, source_code: str, creator=None, origin=None) -> LoadedSource:
if source_unit_name in self.sources:
Expand Down Expand Up @@ -262,10 +267,13 @@ def _read_file_callback(self, su_name: str, base_dir: str, include_paths: List[s
contents = self._read_file(candidates[0], is_cli_path=False)
return candidates[0], contents

def _remap_import(self, source_unit_name: str, importer_source_unit_name: str) -> str:
def _remap_import(self, source_unit_name: str, importer_source_unit_name: str) -> list[str]:
"""Takes a source unit name and checks if it should be remapped
Note: do not pass an import path as the source unit name
"""

possible_remappings = []

for mapping in self.import_remaps:
# context must match the beginning of the source unit name of the file containing the import
if mapping.context and not importer_source_unit_name.startswith(mapping.context):
Expand All @@ -274,11 +282,19 @@ def _remap_import(self, source_unit_name: str, importer_source_unit_name: str) -
# prefix must match the beginning of the source unit name resulting from the import
if source_unit_name.startswith(mapping.prefix):
# target is the value the prefix is replaced with
return self._clean_path(mapping.target, source_unit_name[len(mapping.prefix):])
possible_remappings.append(self._clean_path(mapping.target, source_unit_name[len(mapping.prefix):]))

return source_unit_name
if not possible_remappings:
return [source_unit_name]
else:
return possible_remappings

def _compute_possible_source_unit_names(self, path: str, importer_source_unit_name: str) -> list[str]:
"""
Computes a list of possible source unit names for an import path. Usually there is only 1, but if there are
multiple matching import remappings, we have to test each one later on when the file lookup happens
"""

def _compute_source_unit_name(self, path: str, importer_source_unit_name: str) -> str:
if not self._is_relative_import(path):
return self._remap_import(path, importer_source_unit_name)

Expand Down
6 changes: 3 additions & 3 deletions test/solidity_parser/test_filesys.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ def test_remove_last_path_segment(self, input, expected):
['../.././../util.sol', 'lib/src/../contract.sol', 'util.sol'],
['../../.././../util.sol', 'lib/src/../contract.sol', 'util.sol']
])
def test_compute_source_unit_name(self, path, importer, expected):
result = self.vfs._compute_source_unit_name(path, importer)
self.assertEqual(expected, result)
def test_compute_possible_source_unit_names(self, path, importer, expected):
results = self.vfs._compute_possible_source_unit_names(path, importer)
self.assertEqual([expected], results)

# def test_remap_import(self):
# self.vfs.add_import_remapping(None, 'github.com/ethereum/dapp-bin/', 'dapp-bin/')
Expand Down

0 comments on commit 58277d6

Please sign in to comment.