Skip to content

feat: add method_id member to interface functions#4903

Open
johnnyonline wants to merge 6 commits into
vyperlang:masterfrom
johnnyonline:feat/interface-method-id
Open

feat: add method_id member to interface functions#4903
johnnyonline wants to merge 6 commits into
vyperlang:masterfrom
johnnyonline:feat/interface-method-id

Conversation

@johnnyonline

@johnnyonline johnnyonline commented Apr 6, 2026

Copy link
Copy Markdown

What I did

Implemented VIP #2098 - added a method_id_of builtin function, allowing direct access to the 4-byte function selector via method_id_of(Interface.function).

How I did it

  • InterfaceT.get_member_in_expr exposes functions in expression context (gated from annotation path to prevent use as type)
  • Added method_id_of builtin in builtins/functions.py with build_IR for legacy codegen
  • Added venom handler in codegen_venom/builtins/misc.py
  • Optional n_optional_args kwarg to specify how many default args to include in the signature (defaults to 0, positional-only)

How to verify it

pytest tests/functional/codegen/test_interface_method_id.py tests/functional/syntax/test_interface_method_id.py -x -v

Commit message

feat: add method_id_of builtin function

Description for the changelog

Added method_id_of builtin function, enabling method_id_of(ERC20.transfer) syntax to retrieve the 4-byte function selector as bytes4.

Cute Animal Picture

image

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b5dcdff3b0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +71 to +72
if attr in self.functions:
return TYPE_T(self.functions[attr])

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 Reject interface function types in annotations

Exposing interface functions from get_type_member makes IFoo.transfer parse as a concrete annotation type via type_from_annotation(...), even though ContractFunctionT is not a valid ABI/storage type. As a result, declarations such as x: public(IFoo.transfer) can get past type parsing and then fail later when ABI/getter generation needs to_abi_arg (function types do not implement abi_type), producing an internal compiler failure instead of a user-facing type error. This member should be gated to expression use (...method_id) and not accepted as a general annotation type.

Useful? React with 👍 / 👎.

@charles-cooper charles-cooper left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

i think maybe it makes sense for the evaluation to happen during constant folding? cc @Sporarum

@charles-cooper charles-cooper requested a review from Sporarum April 8, 2026 09:23
@Sporarum

Sporarum commented Apr 8, 2026

Copy link
Copy Markdown
Collaborator

As for constant folding, I guess it technically is a compile-time constant, but I would not expect constant folding to be doing that job

We could instead move the computation to something like ContractFunctionT.encoded_method_id
(encoded because we apparently need to modify the value we got from method_ids: value = method_id << 224)

Also see my comments on the VIP: #2098 (comment)

Comment thread vyper/codegen_venom/expr.py Outdated
return VyperValue.from_stack_op(IRLiteral(value), typ)

# Case 2: Address properties
# Case 1b: Interface.function.method_id (e.g. ERC20.transfer.method_id)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Case 1 should be modified to Case 1a then

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed here 532e685

@johnnyonline

Copy link
Copy Markdown
Author

We could instead move the computation to something like ContractFunctionT.encoded_method_id
(encoded because we apparently need to modify the value we got from method_ids: value = method_id << 224)

Done here 9f51422

Also see my comments on the VIP: #2098 (comment)

Re default args, current implementation uses the full signature (all args). Happy to change if a different behavior is preferred

Re naming (method_id vs __method_id__), FWIW, the reasoning in @Sporarum's comment makes sense to me

@charles-cooper

Copy link
Copy Markdown
Member

cf. #2098 (comment) on functions with default args

@johnnyonline

Copy link
Copy Markdown
Author

First commit defaulted n_args to all args (full signature) but realized that probably not alligns with the spec so second commit renames to n_optional_args and defaults to 0

This way method_id_of(ERC20.transfer) just works for the common case and for functions with defaults like def foo(x: uint256 = 0), method_id_of(IFoo.foo) --> foo() and method_id_of(IFoo.foo, n_optional_args=1) --> foo(uint256)

If n_optional_args is too verbose i will happily shorten it back to n_args. Mostly went with this so we're fully clear on the intent (tbh personally i also prefer more explicit too)

@charles-cooper

Copy link
Copy Markdown
Member

do we want to handle things like n_args=-2?

@charles-cooper

Copy link
Copy Markdown
Member

do we want to handle things like n_args=-2?

ok i think we can actually handle this as a follow up. the PR looks good to me, @Sporarum do you have any more comments?

@Sporarum Sporarum left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@charles-cooper I think we should not default to 0, and instead force the user to specify the count if there are kwargs: #2098 (comment)

And a couple small comments:

Comment on lines +657 to +658
builtin = MethodIDOf()
selector = builtin._compute_method_id(node)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
builtin = MethodIDOf()
selector = builtin._compute_method_id(node)
selector = MethodIDOf()._compute_method_id(node)

@@ -443,6 +443,10 @@ def infer_kwarg_types(self, node):
# dispatch into get_type_member if it's dereferenced, ex.
# MyFlag.FOO
def get_member(self, key, node):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why is this change required ?

# get an event, struct or flag from this interface
return TYPE_T(self._helper.get_member(attr, node))

def get_member_in_expr(self, attr, node):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this function name is confusing, I don't see what it semantically means

Comment on lines +793 to +794
for kw in node.keywords:
if kw.arg == "n_optional_args":

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This seems a bit convoluted, wouldn't node.keywords.get("n_optional_args", 0) or something like it work ?

How do the other built-ins deal with kwargs ?

# default: 0 optional args (positional only)
return 0

def _compute_method_id(self, node):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this logic a duplicate ?
It seems like we would already have something to compute that

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.

3 participants