Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
29 changes: 29 additions & 0 deletions tests/functional/codegen/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,35 @@ def test(addr: address):
c.test(address)


def test_interface_widens_to_address_in_exprs(env, get_contract):
code = """
interface Foo:
def foo(): payable

f: Foo

@internal
def _take_addr(a: address) -> address:
return a

@external
def test(addr: address) -> address:
self.f = Foo(addr)
assert addr == self.f # widen from equals

local_addr: address = self.f # widen from assignment
assert local_addr == addr

passed: address = self._take_addr(self.f) # widen from argument passing
assert passed == addr

return self.f # widen from return
"""
c = get_contract(code)
some_address = env.accounts[1]
assert c.test(some_address) == some_address


# test data returned from external interface gets clamped
@pytest.mark.parametrize("typ", ("int128", "uint8"))
def test_external_interface_int_clampers(get_contract, tx_failed, typ):
Expand Down
56 changes: 56 additions & 0 deletions tests/functional/syntax/modules/test_implements.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,59 @@ def bar(): # implementation

assert e.value._message == "some_interface implemented more than once"
assert e.value._hint is None


@pytest.mark.parametrize(
"var_decl,iface_method,store_stmt",
[
pytest.param(
"foo: public(IERC20)", "def foo() -> address: view", "self.foo = asset_", id="storage"
),
pytest.param( # GH issue 3954
"foo: public(immutable(IERC20))",
"def foo() -> address: view",
"self.foo = asset_",
id="immutable",
),
pytest.param(
"foo: public(HashMap[uint256, IERC20])",
"def foo(k: uint256) -> address: view",
"self.foo[7] = asset_",
id="hashmap",
),
pytest.param(
"foo: public(DynArray[IERC20, 3])",
"def foo(i: uint256) -> address: view",
"self.foo.append(asset_)",
id="dynarray",
),
pytest.param( # GH issue 4721
"foo: public(IERC20)",
"def foo() -> IERC20: view",
"self.foo = asset_",
id="interface_return",
),
],
)
def test_implements_with_public_interface(var_decl, iface_method, store_stmt):
"""
Tests that `var_decl` correctly implements `iface_method`

For example `foo: public(IERC20)` implements `def foo() -> address: view`
"""

main = f"""
from ethereum.ercs import IERC20

{var_decl}

interface IAsset:
{iface_method}

implements: IAsset

@deploy
def __init__(asset_: IERC20):
{store_stmt}
"""
compile_code(main)
10 changes: 10 additions & 0 deletions tests/functional/syntax/test_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,16 @@ def f():
""",
InterfaceViolation,
),
(
"""
from ethereum.ercs import IERC20

@external
def test(a: address):
x: IERC20 = a # Should not narrow
""",
TypeMismatch,
),
]


Expand Down
4 changes: 0 additions & 4 deletions vyper/semantics/types/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,6 @@ def get_type_member(self, attr, node):
# get an event, struct or flag from this interface
return TYPE_T(self._helper.get_member(attr, node))

@property
def getter_signature(self):
return (), AddressT()

@property
def abi_type(self) -> ABIType:
return ABI_Address()
Comment on lines 80 to 82

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Emit address for standalone public getter interfaces

With the getter override removed here, foo: public(IERC20) now has a getter return type of IERC20, so external_interface/interface output prints def foo() -> IERC20 without also emitting the import or interface definition needed to resolve IERC20. That regresses the common use case where generated interface output is consumed as a standalone .vyi; before this change the same getter printed -> address and remained self-contained. Please keep generated interface text self-contained, e.g. by rendering public interface getter returns as their ABI address type or by emitting the required interface definition/import.

Useful? React with 👍 / 👎.

Expand Down
6 changes: 6 additions & 0 deletions vyper/semantics/types/primitives.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,12 @@
node,
)

def compare_type(self, other):
from vyper.semantics.types.module import InterfaceT

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Will be solved once we refactor compare_type away


# interfaces can be widened to addresses
return isinstance(other, InterfaceT) or super().compare_type(other)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Add address to common-type inference for interfaces

This subtype hook makes an individual interface acceptable where address is expected, but expressions that first compute a common type still fail for two different interface types because get_common_types() only selects one of the operands' possible types and never adds their AddressT supertype. For example, in a function returning address, return a if cond else b still raises TypeMismatch when a: IFoo and b: IBar, and the same applies to list literals such as [a, b] assigned to DynArray[address, ...], even though each operand is now meant to widen to an address. Please either add AddressT as the common candidate for interface/address operands or contextually type these expression forms against the expected address.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Accept interfaces in address-array membership checks

This only teaches direct expected-type checks that an interface is assignable to address; membership validation still uses _is_type_in_list()/is_equivalent_to(), so a in xs is rejected when a: IFoo and xs: DynArray[address, 3] with Cannot perform membership comparison between dislike types. Since the array element type is an address position and the generated comparison would be a word equality, this leaves another address-expected context that the new widening rule does not cover.

Useful? React with 👍 / 👎.



# type for "self"
# refactoring note: it might be best for this to be a ModuleT actually
Expand Down