From 99304da54d2178960b19106a6d1beb7e37cf4495 Mon Sep 17 00:00:00 2001 From: cyberthirst Date: Mon, 29 Jul 2024 05:09:23 +0200 Subject: [PATCH] fix[lang]: fix `.vyi` function body check (#4177) previously the validation for function bodies in interfaces incorrectly allowed other types of expressions with the `.value` member, e.g. ```vyper @external def some_interface_function(): return ... ``` although the body should only be allowed to be an ellipsis expr (`...`). this commit correctly rejects the above source code --- tests/functional/syntax/test_interfaces.py | 63 ++++++++++++++++++++++ vyper/semantics/types/function.py | 6 ++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/tests/functional/syntax/test_interfaces.py b/tests/functional/syntax/test_interfaces.py index 113629220e..d2e0077cd6 100644 --- a/tests/functional/syntax/test_interfaces.py +++ b/tests/functional/syntax/test_interfaces.py @@ -3,6 +3,7 @@ from vyper import compiler from vyper.exceptions import ( ArgumentException, + FunctionDeclarationException, InterfaceViolation, InvalidReference, InvalidType, @@ -421,3 +422,65 @@ def test_builtins_not_found2(erc): compiler.compile_code(code) assert e.value._message == f"ethereum.ercs.{erc}" assert e.value._hint == f"try renaming `{erc}` to `I{erc}`" + + +def test_interface_body_check(make_input_bundle): + interface_code = """ +@external +def foobar(): + return ... +""" + + input_bundle = make_input_bundle({"foo.vyi": interface_code}) + + code = """ +import foo as Foo + +implements: Foo + +@external +def foobar(): + pass +""" + with pytest.raises(FunctionDeclarationException) as e: + compiler.compile_code(code, input_bundle=input_bundle) + + assert e.value._message == "function body in an interface can only be `...`!" + + +def test_interface_body_check2(make_input_bundle): + interface_code = """ +@external +def foobar(): + ... + +@external +def bar(): + ... + +@external +def baz(): + ... +""" + + input_bundle = make_input_bundle({"foo.vyi": interface_code}) + + code = """ +import foo + +implements: foo + +@external +def foobar(): + pass + +@external +def bar(): + pass + +@external +def baz(): + pass +""" + + assert compiler.compile_code(code, input_bundle=input_bundle) is not None diff --git a/vyper/semantics/types/function.py b/vyper/semantics/types/function.py index ae913b097c..72682c881d 100644 --- a/vyper/semantics/types/function.py +++ b/vyper/semantics/types/function.py @@ -344,7 +344,11 @@ def from_vyi(cls, funcdef: vy_ast.FunctionDef) -> "ContractFunctionT": return_type = _parse_return_type(funcdef) - if len(funcdef.body) != 1 or not isinstance(funcdef.body[0].get("value"), vy_ast.Ellipsis): + body = funcdef.body + + if len(body) != 1 or not ( + isinstance(body[0], vy_ast.Expr) and isinstance(body[0].value, vy_ast.Ellipsis) + ): raise FunctionDeclarationException( "function body in an interface can only be `...`!", funcdef )