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[ux]: compile .vyi files #4290

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fb72306
set is_interface based on the suffix of an input file
sandbubbles Oct 11, 2024
8bc61ed
construct ir_runtime only if input isn't interface
sandbubbles Oct 11, 2024
112c76f
exclude some output formats when compiling interface
sandbubbles Oct 11, 2024
fc05efe
add test for interface outputs
sandbubbles Oct 11, 2024
e5fc9a7
iterate through all unsupported formats
sandbubbles Oct 12, 2024
328f7fd
tag modules with is_interface
sandbubbles Oct 12, 2024
36c620c
add is_interface tag to ModuleT
sandbubbles Oct 13, 2024
d181da1
add is_interface to parse_to_ast_with_settings
sandbubbles Oct 14, 2024
5d4e55b
adjust tests
sandbubbles Oct 14, 2024
6641092
lint
sandbubbles Oct 14, 2024
87bc04e
merge master
sandbubbles Oct 14, 2024
0908bdf
Merge branch 'master' into feat/compile-solely-vyi
sandbubbles Oct 15, 2024
afd8f8e
adjust solution due to merge
sandbubbles Oct 15, 2024
8048d4f
reduce a multiline expression
charles-cooper Oct 15, 2024
2651d89
fix mypy
charles-cooper Oct 15, 2024
1134c3a
style: factor out an expression
charles-cooper Oct 15, 2024
b582f8f
Merge branch 'master' into feat/compile-solely-vyi
charles-cooper Oct 15, 2024
5cdb2da
Merge branch 'master' into feat/compile-solely-vyi
charles-cooper Oct 17, 2024
324b598
add flags into interface output
sandbubbles Oct 25, 2024
50692a1
add test for presence of flags in interface output
sandbubbles Oct 25, 2024
fcfc2ec
lint
sandbubbles Oct 25, 2024
60dddd4
capitalize the first letter but leave the rest the same
sandbubbles Oct 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions tests/functional/codegen/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -774,3 +774,39 @@ def foo(s: MyStruct) -> MyStruct:
assert "b: uint256" in out
assert "struct Voter:" in out
assert "voted: bool" in out


def test_interface_with_flags():
code = """
struct MyStruct:
a: address

flag Foo:
BOO
MOO
POO

event Transfer:
sender: indexed(address)

@external
def bar():
pass
flag BAR:
BIZ
BAZ
BOO

@external
@view
def foo(s: MyStruct) -> MyStruct:
return s
"""

out = compile_code(code, contract_path="code.vy", output_formats=["interface"])["interface"]

assert "# Flags" in out
assert "flag Foo:" in out
assert "flag BAR" in out
assert "BOO" in out
assert "MOO" in out
2 changes: 2 additions & 0 deletions tests/unit/ast/test_ast_dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ def foo():
"node_id": 0,
"path": "main.vy",
"source_id": 1,
"is_interface": False,
"type": {
"name": "main.vy",
"type_decl_node": {"node_id": 0, "source_id": 1},
Expand Down Expand Up @@ -1175,6 +1176,7 @@ def foo():
"node_id": 0,
"path": "lib1.vy",
"source_id": 0,
"is_interface": False,
"type": {
"name": "lib1.vy",
"type_decl_node": {"node_id": 0, "source_id": 0},
Expand Down
53 changes: 53 additions & 0 deletions tests/unit/cli/vyper_compile/test_compile_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,3 +425,56 @@ def test_archive_search_path(tmp_path_factory, make_file, chdir_tmp_path):

used_dir = search_paths[-1].stem # either dir1 or dir2
assert output_bundle.used_search_paths == [".", "0/" + used_dir]


def test_compile_interface_file(make_file):
interface = """
@view
@external
def foo() -> String[1]:
...

@view
@external
def bar() -> String[1]:
...

@external
def baz() -> uint8:
...

"""
file = make_file("interface.vyi", interface)
compile_files([file], ["ast", "annotated_ast", "interface", "external_interface", "abi"])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to use this here? https://github.com/vyperlang/vyper/pull/4290/files#diff-d428d4c971c9f7166899f6f2d3da5e17ca4478d8d371d803e735d6ee36b39d30R49

so we don't have to maintain the list on 2 places


unallowed_formats = [
"layout",
"devdoc",
"userdoc",
"archive",
"archive_b64",
"integrity",
"solc_json",
"bb",
"bb_runtime",
"cfg",
"cfg_runtime",
"ir",
"ir_runtime",
"ir_dict",
"ir_runtime_dict",
"method_identifiers",
"metadata",
"asm",
"source_map",
"source_map_runtime",
"bytecode",
"bytecode_runtime",
"blueprint_bytecode",
"opcodes",
"opcodes_runtime",
]

for f in unallowed_formats:
with pytest.raises(ValueError):
compile_files([file], [f])
2 changes: 1 addition & 1 deletion vyper/ast/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ class TopLevel(VyperNode):

class Module(TopLevel):
# metadata
__slots__ = ("path", "resolved_path", "source_id")
__slots__ = ("path", "resolved_path", "source_id", "is_interface")

def to_dict(self):
return dict(source_sha256sum=self.source_sha256sum, **super().to_dict())
Expand Down
1 change: 1 addition & 0 deletions vyper/ast/nodes.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class Module(TopLevel):
path: str = ...
resolved_path: str = ...
source_id: int = ...
is_interface: bool = ...
def namespace(self) -> Any: ... # context manager

class FunctionDef(TopLevel):
Expand Down
2 changes: 2 additions & 0 deletions vyper/ast/parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def parse_to_ast_with_settings(
module_path: Optional[str] = None,
resolved_path: Optional[str] = None,
add_fn_node: Optional[str] = None,
is_interface: bool = False,
) -> tuple[Settings, vy_ast.Module]:
"""
Parses a Vyper source string and generates basic Vyper AST nodes.
Expand Down Expand Up @@ -84,6 +85,7 @@ def parse_to_ast_with_settings(
# Convert to Vyper AST.
module = vy_ast.get_node(py_ast)
assert isinstance(module, vy_ast.Module) # mypy hint
module.is_interface = is_interface

return pre_parse_result.settings, module

Expand Down
15 changes: 15 additions & 0 deletions vyper/compiler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@
"opcodes_runtime": output.build_opcodes_runtime_output,
}

INTERFACE_OUTPUT_FORMATS = [
"ast_dict",
"annotated_ast_dict",
"interface",
"external_interface",
"abi",
]

UNKNOWN_CONTRACT_NAME = "<unknown>"

Expand Down Expand Up @@ -117,10 +124,18 @@ def compile_from_file_input(
)

ret = {}

with anchor_settings(compiler_data.settings):
for output_format in output_formats:
if output_format not in OUTPUT_FORMATS:
raise ValueError(f"Unsupported format type {repr(output_format)}")

is_vyi = file_input.resolved_path.suffix == ".vyi"
if is_vyi and output_format not in INTERFACE_OUTPUT_FORMATS:
raise ValueError(
f"Unsupported format for compiling interface: {repr(output_format)}"
)

try:
formatter = OUTPUT_FORMATS[output_format]
ret[output_format] = formatter(compiler_data)
Expand Down
13 changes: 11 additions & 2 deletions vyper/compiler/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def build_external_interface_output(compiler_data: CompilerData) -> str:
stem = PurePath(compiler_data.contract_path).stem
# capitalize words separated by '_'
# ex: test_interface.vy -> TestInterface
name = "".join([x.capitalize() for x in stem.split("_")])
name = "".join([x[0].upper() + x[1:] for x in stem.split("_")])
out = f"\n# External Interfaces\ninterface {name}:\n"

for func in interface.functions.values():
Expand All @@ -136,6 +136,14 @@ def build_interface_output(compiler_data: CompilerData) -> str:
out += f" {member_name}: {member_type}\n"
out += "\n\n"

if len(interface.flags) > 0:
out += "# Flags\n\n"
for flag in interface.flags.values():
out += f"flag {flag.name}:\n"
for flag_value in flag._flag_members:
out += f" {flag_value}\n"
out += "\n\n"

if len(interface.events) > 0:
out += "# Events\n\n"
for event in interface.events.values():
Expand Down Expand Up @@ -265,7 +273,8 @@ def build_method_identifiers_output(compiler_data: CompilerData) -> dict:

def build_abi_output(compiler_data: CompilerData) -> list:
module_t = compiler_data.annotated_vyper_module._metadata["type"]
_ = compiler_data.ir_runtime # ensure _ir_info is generated
if not compiler_data.annotated_vyper_module.is_interface:
_ = compiler_data.ir_runtime # ensure _ir_info is generated

abi = module_t.interface.to_toplevel_abi_dict()
if compiler_data.show_gas_estimates:
Expand Down
3 changes: 3 additions & 0 deletions vyper/compiler/phases.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,14 @@ def contract_path(self):

@cached_property
def _generate_ast(self):
is_vyi = self.contract_path.suffix == ".vyi"

settings, ast = vy_ast.parse_to_ast_with_settings(
self.source_code,
self.source_id,
module_path=self.contract_path.as_posix(),
resolved_path=self.file_input.resolved_path.as_posix(),
is_interface=is_vyi,
)

if self.original_settings:
Expand Down
2 changes: 1 addition & 1 deletion vyper/semantics/analysis/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def analyze_module(module_ast: vy_ast.Module) -> ModuleT:
add all module-level objects to the namespace, type-check/validate
semantics and annotate with type and analysis info
"""
return _analyze_module_r(module_ast)
return _analyze_module_r(module_ast, module_ast.is_interface)


def _analyze_module_r(module_ast: vy_ast.Module, is_interface: bool = False):
Expand Down
Loading