-
-
Notifications
You must be signed in to change notification settings - Fork 802
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[lang]: support flags from imported interfaces #4253
Changes from 11 commits
6a87f54
9edcd52
2065c23
c718e28
8a932a3
69e5b3e
8439d5b
2539997
20d1d33
d2e8aea
f9e4004
76824a1
98e4925
be60015
223d6b3
39e955d
785b606
94b70fc
482f306
d936b86
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ | |
StructureException, | ||
UnfoldableNode, | ||
) | ||
from vyper.semantics.analysis.base import Modifiability | ||
from vyper.semantics.analysis.base import Modifiability, VarInfo | ||
|
||
from vyper.semantics.analysis.utils import ( | ||
check_modifiability, | ||
get_exact_type_from_node, | ||
|
@@ -21,7 +21,7 @@ | |
from vyper.semantics.types.base import TYPE_T, VyperType, is_type_t | ||
from vyper.semantics.types.function import ContractFunctionT | ||
from vyper.semantics.types.primitives import AddressT | ||
from vyper.semantics.types.user import EventT, StructT, _UserType | ||
from vyper.semantics.types.user import EventT, FlagT, StructT, _UserType | ||
|
||
from vyper.utils import OrderedSet, sha256sum | ||
|
||
if TYPE_CHECKING: | ||
|
@@ -45,28 +45,41 @@ | |
functions: dict, | ||
events: dict, | ||
structs: dict, | ||
flags: dict, | ||
constants: dict, | ||
) -> None: | ||
validate_unique_method_ids(list(functions.values())) | ||
|
||
members = functions | events | structs | ||
public_constants = { | ||
k: varinfo for k, varinfo in constants.items() if varinfo.decl_node.is_public | ||
} | ||
|
||
members = functions | events | structs | flags | constants | ||
|
||
# sanity check: by construction, there should be no duplicates. | ||
assert len(members) == len(functions) + len(events) + len(structs) | ||
assert len(members) == len(functions) + len(events) + len(structs) + len(flags) + len( | ||
constants | ||
) - len(public_constants) | ||
|
||
super().__init__(functions) | ||
|
||
self._helper = VyperType(events | structs) | ||
self._helper = VyperType(events | structs | flags | constants) | ||
self._id = _id | ||
self._helper._id = _id | ||
self.functions = functions | ||
self.events = events | ||
self.structs = structs | ||
self.flags = flags | ||
self.constants = constants | ||
|
||
self.decl_node = decl_node | ||
|
||
def get_type_member(self, attr, node): | ||
# get an event or struct from this interface | ||
return TYPE_T(self._helper.get_member(attr, node)) | ||
# get an event, struct or constant from this interface | ||
type_member = self._helper.get_member(attr, node) | ||
if isinstance(type_member, _UserType): | ||
return TYPE_T(type_member) | ||
return type_member | ||
|
||
@property | ||
def getter_signature(self): | ||
|
@@ -159,35 +172,57 @@ | |
interface_name: str, | ||
decl_node: Optional[vy_ast.VyperNode], | ||
function_list: list[tuple[str, ContractFunctionT]], | ||
event_list: list[tuple[str, EventT]], | ||
struct_list: list[tuple[str, StructT]], | ||
event_list: Optional[list[tuple[str, EventT]]] = None, | ||
struct_list: Optional[list[tuple[str, StructT]]] = None, | ||
flag_list: Optional[list[tuple[str, FlagT]]] = None, | ||
constant_list: Optional[list[tuple[str, VarInfo]]] = None, | ||
) -> "InterfaceT": | ||
functions = {} | ||
events = {} | ||
structs = {} | ||
flags = {} | ||
constants = {} | ||
|
||
seen_items: dict = {} | ||
|
||
def _mark_seen(name, item): | ||
if name in seen_items: | ||
prev = seen_items[name] | ||
if ( | ||
isinstance(prev, ContractFunctionT) | ||
and isinstance(item, VarInfo) | ||
and item.decl_node.is_public | ||
): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm, why is this needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Without this, a public constant would result in a duplicate because it would be in |
||
return | ||
msg = f"multiple functions or events named '{name}'!" | ||
prev_decl = seen_items[name].decl_node | ||
raise NamespaceCollision(msg, item.decl_node, prev_decl=prev_decl) | ||
raise NamespaceCollision(msg, item.decl_node, prev_decl=prev.decl_node) | ||
seen_items[name] = item | ||
|
||
for name, function in function_list: | ||
_mark_seen(name, function) | ||
functions[name] = function | ||
|
||
for name, event in event_list: | ||
_mark_seen(name, event) | ||
events[name] = event | ||
if event_list: | ||
for name, event in event_list: | ||
_mark_seen(name, event) | ||
events[name] = event | ||
|
||
if struct_list: | ||
for name, struct in struct_list: | ||
_mark_seen(name, struct) | ||
structs[name] = struct | ||
|
||
if flag_list: | ||
for name, flag in flag_list: | ||
_mark_seen(name, flag) | ||
flags[name] = flag | ||
|
||
for name, struct in struct_list: | ||
_mark_seen(name, struct) | ||
structs[name] = struct | ||
if constant_list: | ||
for name, constant in constant_list: | ||
_mark_seen(name, constant) | ||
constants[name] = constant | ||
|
||
return cls(interface_name, decl_node, functions, events, structs) | ||
return cls(interface_name, decl_node, functions, events, structs, flags, constants) | ||
|
||
@classmethod | ||
def from_json_abi(cls, name: str, abi: dict) -> "InterfaceT": | ||
|
@@ -214,8 +249,7 @@ | |
for item in [i for i in abi if i.get("type") == "event"]: | ||
events.append((item["name"], EventT.from_abi(item))) | ||
|
||
structs: list = [] # no structs in json ABI (as of yet) | ||
return cls._from_lists(name, None, functions, events, structs) | ||
return cls._from_lists(name, None, functions, events) | ||
|
||
@classmethod | ||
def from_ModuleT(cls, module_t: "ModuleT") -> "InterfaceT": | ||
|
@@ -247,8 +281,15 @@ | |
# these are accessible via import, but they do not show up | ||
# in the ABI json | ||
structs = [(node.name, node._metadata["struct_type"]) for node in module_t.struct_defs] | ||
|
||
return cls._from_lists(module_t._id, module_t.decl_node, funcs, events, structs) | ||
flags = [(node.name, node._metadata["flag_type"]) for node in module_t.flag_defs] | ||
constants = [ | ||
(node.target.id, node.target._metadata["varinfo"]) | ||
for node in module_t.variable_decls | ||
if node.is_constant | ||
] | ||
return cls._from_lists( | ||
module_t._id, module_t.decl_node, funcs, events, structs, flags, constants | ||
) | ||
|
||
@classmethod | ||
def from_InterfaceDef(cls, node: vy_ast.InterfaceDef) -> "InterfaceT": | ||
|
@@ -265,11 +306,7 @@ | |
) | ||
functions.append((func_ast.name, ContractFunctionT.from_InterfaceDef(func_ast))) | ||
|
||
# no structs or events in InterfaceDefs | ||
events: list = [] | ||
structs: list = [] | ||
|
||
return cls._from_lists(node.name, node, functions, events, structs) | ||
return cls._from_lists(node.name, node, functions) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. much cleaner now, thanks |
||
|
||
|
||
# Datatype to store all module information. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a question here (without having read the full changelog). If you define a
constant
in an interface, thisconstant
has the visibilitypublic
by default I think (see #4178) and thus if you doimplements
you must create a getter in the main contract to be compliant? It just confuses me how we handle this properly.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't consider that, but as it is at the moment, constants in an interface are not external by default`. Do we want the behaviour to be public by default?
I can see why that makes sense for consistency but IMO it would be better to not treat constants the same way. The mode of declaring a constant as public is different from that for a function, and also we would not expect the getter of a constant to be part of the interface if it was not explicitly declared as public.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, that's exactly why I raise this point to highlight the inconsistency here. And I agree with you that it should not be handled the same way, but given the current semantics, it could be interpreted like this. Also, that's why I think having optional
external
visibility in interfaces is not appropriate really; explicitness is better.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, that is a good point. the idea here is that constants are like types, they can be imported for use in your contract, but they shouldn't be allowed to be marked
external
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we maybe make this more clear via some syntax sugar? An interface, in the context of smart contract programming, signals something "external", and given that now the visibility modifier is even only optional, we should make the scope clear for
constant
values to avoid any ambiguity.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That feels a little hacky.
How about we skip this for
0.4.1
and introduce type aliases in the next release?something in this spirit:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in the past, Charles suggested that aliases might also help with this (a bit ugly 😉 ) interface syntax
#4090 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i mean i think if you can use it in type declarations, you should be able to use it like any other constant. e.g.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on offline discussion I think we should keep the changes for flags here, roll back the changes for constants here and punt to future work / a separate PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sgtm