Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement module exports #19

Closed
wants to merge 7 commits into from
Closed

Conversation

charles-cooper
Copy link
Owner

@charles-cooper charles-cooper commented Dec 13, 2023

contracts can now export external functions from an imported module

example

# library.vy

@external
def some_external_function() -> uint256
    return block.number + 5
# contract.vy
import library

exports: library.some_external_function

@external
def foo() -> uint256:
    return 1

export bundles are a way for library authors to group together external
functions in the ABI

example:

bundle: IERC_X  # declare a bundle

https://github.com/internal
def foo() -> uint256:
    return 10

@external
def extern1():
    pass

@external
@Bundle(IERC_X)
def extern2():
    pass

@external
@Bundle(IERC_X, bind=False)  # extern3 ok to export on its own
def extern3():
    pass

@external
@Bundle(IERC_X, bind=True)  # extern4 cannot be exported on its own!
def extern4():
    pass

it can be used like this:

import library

exports: library.IERC_X

or like this:

import library

exports: library.extern1, library.extern2

but not like this(!):

import library

exports: library.extern4  # raises StructureException

What I did

How I did it

How to verify it

Commit message

Commit message for the final, squashed PR. (Optional, but reviewers will appreciate it! Please see our commit message style guide for what we would ideally like to see in a commit message.)

Description for the changelog

Cute Animal Picture

Put a link to a cute animal picture inside the parenthesis-->

clean up `append_instruction` api so it does a bit of magic on its
arguments and figures out whether or not to allocate a stack variable.

remove `append_instruction()` from IRFunction - automatically appending
to the last basic block could be a bit error prone depending on which
order basic blocks are added to the CFG.

---------

Co-authored-by: Charles Cooper <[email protected]>
for some reason, there is a slot named "keyword" on the Call AST node,
which is never used (and doesn't exist in the python AST!).

this commit removes it for hygienic purposes.
right now only certain output formats are tested in the main compiler
test harness, namely bytecode, abi, metadata and some natspec outputs.
in the past, there have been issues where output formats get broken but
don't get detected until release testing or even after release.

this commit adds hooks in `get_contract()` and `deploy_blueprint_for()`
to generate all output formats, which will help detect broken output
formats sooner.
this commit implements support for "stateless" modules in vyper.

this is the first major step in implementing vyper's module system.
it redesigns the language's import system, allows calling internal
functions from imported modules, allows for limited use of types from
imported modules, and introduces support for `.vyi` interface files.

note that the following features are left for future, follow-up work:
- modules with variables (constants, immutables or storage variables)
- full support for imported events (in that they do not get exported
  in the ABI)
- a system for exporting imported functions in the external interface
  of a contract

this commit first and foremost changes how imports are handled in vyper.

previously, an imported file was assumed to be an interface file. some
very limited validation was performed in `InterfaceT.from_ast`, but not
fully typechecked, and no code was generated for it.

now, when a file is imported, it is checked whether it is
  1. a `.vy` file
  2. a `.vyi` file
  3. a `.json` file

the `.json` pathway remains more or less unchanged. the `.vyi` pathway
is new, but it is fairly straightforward and is basically a "simple"
path through the `.vy` pathway which piggy-backs off the `.vy` analysis
to produce an `InterfaceT` object.

the `.vy` pathway now does full typechecking and analysis of the
imported module. some changes were made to support this:
- a new ImportGraph data structure tracks the position in the import
  graph and detects (and bands) import cycles
- InputBundles now implement a `_normalize_path()` method.
  this method normalizes the path so that source IDs are stable no
  matter how a file is accessed in the filesystem (i.e., no matter
  what the search path was at the time `load_file()` was called).
- CompilerInput now has a distinction between `resolved_path` and `path`
  (the original path that was asked for). this allows us to maintain UX
  considerations (showing unresolved paths etc) while still having a
  1:1:1 correspondence between source id, filepath and filesystem.

these changes were needed in order to stabilize notions like "which file
are we looking at?" no matter the way the file was accessed or how it
was imported. this is important so that types imported transitively
can resolve as expected no matter how they are imported - for instance,
`x.SomeType` and `a.x.SomeType` resolving to the same type.

the other changes needed to support code generation and analysis for
imported functions were fairly simple, and mostly involved generalizing
the analysis/code generation to type-based dispatch instead of AST-based
dispatch.

other changes to the language and compiler API include:
- import restrictions are more relaxed - `import x` is allowed now
  (previously, `import x as x` was required)
- change function labels in IR
  function labels are changed to disambiguate functions of the same name
  (but whose parent module are different). this was done by computing a
  unique function_id for every function and using that function_id when
  constructing its IR identifier.
- add compile_from_file_input which accepts a FileInput instead of a
  string. this is now the new preferred entry point into the compiler.
  its usage simplifies several internal APIs which expect to have
  `source_id` and `path` in addition to the raw source code.
- change compile_code api from contract_name= to contract_path=

additional changes to internal APIs and passes include:
- remove `remove_unused_statements()`
  the "unused statements" are now important to keep around for imports!
  in general, it is planned to remove both the AST expansion and
  constant folding passes as copying around the AST results in both
  performance and correctness problems
- abstract out a common exception rewriting pattern.
  instead of raising `exception.with_annotation(node)` -- just catch-all
  in the parent implementation and then don't have to worry about it at
  the exception site.
- rename "type" metadata key on most top-level declarators to more
  specific names (e.g. "func_type", "getter_type", etc).
- remove dead package pytest-rerunfailures
  use of `--reruns` was removed in c913b2d
- refactor: move `parse_*` functions, remove vyper.ast.annotation
  move `parse_*` functions into new module vyper.ast.parse and merge in
  vyper.ast.annotation
- rename the old `GlobalContext` class to `ModuleT`
- refactor: move InterfaceT into `vyper/semantics/types/module.py`
  it makes more sense here since it is closely coupled with `ModuleT`.
contracts can now export external functions from an imported module
export bundles are a way for library authors to group together external
functions in the ABI

example:
```vyper
bundle: IERC_X  # declare a bundle

@internal
def foo() -> uint256:
    return 10

@external
def extern1():
    pass

@external
@Bundle(IERC_X)
def extern2():
    pass

@external
@Bundle(IERC_X, bind=False)  # extern3 ok to export on its own
def extern3():
    pass

@external
@Bundle(IERC_X, bind=True)  # extern4 cannot be exported on its own!
def extern4():
    pass
```

it can be used like this:
```vyper
import library

exports: library.IERC_X

@external
def library_foo() -> uint256:
    return library.foo()
```

or like this:
```vyper
import library

exports: library.extern1, library.extern2

@external
def library_foo() -> uint256:
    return library.foo()
```

but not like this(!):

```vyper
import library

exports: library.extern4  # raises StructureException

@external
def library_foo() -> uint256:
    return library.foo()
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants