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: allow constant interfaces #3718

Merged
merged 11 commits into from
Jan 15, 2024

Conversation

tserg
Copy link
Collaborator

@tserg tserg commented Jan 3, 2024

What I did

Fix #3407

How I did it

Handle interfaces for check_modifiability and in codegen.

How to verify it

See tests.

Commit message

feat: allow constant interfaces

This PR adds support for interfaces as constants.
- also introduced a mild refactor of `check_modifiability()`

Description for the changelog

Allow interfaces as constants.

Cute Animal Picture

Put a link to a cute animal picture inside the parenthesis-->

@codecov-commenter
Copy link

codecov-commenter commented Jan 3, 2024

Codecov Report

Attention: 2 lines in your changes are missing coverage. Please review.

Comparison is base (af5c49f) 84.16% compared to head (4924a85) 84.11%.

Files Patch % Lines
vyper/semantics/types/base.py 50.00% 1 Missing and 1 partial ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3718      +/-   ##
==========================================
- Coverage   84.16%   84.11%   -0.06%     
==========================================
  Files          92       92              
  Lines       13133    13141       +8     
  Branches     2926     2927       +1     
==========================================
- Hits        11054    11053       -1     
- Misses       1659     1667       +8     
- Partials      420      421       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

FOO: constant(Foo) = Foo(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF)

@external
def bar(a: uint256, b: Foo = Foo(0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF)) -> Foo:
Copy link
Member

Choose a reason for hiding this comment

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

it would be good for the tests if bar() and faz() returned something different

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

do you mean like any constant interface?

Copy link
Member

Choose a reason for hiding this comment

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

yea, like 0xaaaa vs 0xbbbbb

Copy link
Member

@charles-cooper charles-cooper left a comment

Choose a reason for hiding this comment

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

per https://github.com/vyperlang/vyper/pull/3718/files#r1440581290, i think it's going to be cleaner if we can get more annotation into the constant folding pass. i think this can be best accomplished if we interleave constant folding into the dependency resolving loop in the module analyzer. we can also probably clean up the constant folding pass further by implementing it as a separate Visitor implementation. this will separate it from the concerns of VyperNode definitions (which should be more pure and not really "know" how to fold themselves) and also be able to incorporate other information that should belong to later passes (mainly, constants structs and interfaces which only get populated during module analysis).

@charles-cooper
Copy link
Member

per https://github.com/vyperlang/vyper/pull/3718/files#r1440581290, i think it's going to be cleaner if we can get more annotation into the constant folding pass. i think this can be best accomplished if we interleave constant folding into the dependency resolving loop in the module analyzer. we can also probably clean up the constant folding pass further by implementing it as a separate Visitor implementation. this will separate it from the concerns of VyperNode definitions (which should be more pure and not really "know" how to fold themselves) and also be able to incorporate other information that should belong to later passes (mainly, constants structs and interfaces which only get populated during module analysis).

alright, i performed this refactor in tserg#1. but i think actually a simpler fix to the problem at hand is to just introduce a check_modifiability_for_call() method on structs and interfaces which basically just checks the modifiability of all the arguments to the constructor. this is similar to the technique used for builtins, which is to just check for the "_modifiability" attribute instead of needing an import cycle. (actually for cleanliness, BuiltinFunctionT._modifiability could use the same API, although this might be overkill).

Comment on lines +9 to +13
from vyper.semantics.analysis.utils import (
check_modifiability,
validate_expected_type,
validate_unique_method_ids,
)

Check notice

Code scanning / CodeQL

Cyclic import Note

Import of module
vyper.semantics.analysis.utils
begins an import cycle.
from vyper.semantics.analysis.levenshtein_utils import get_levenshtein_error_suggestions
from vyper.semantics.analysis.utils import validate_expected_type
from vyper.semantics.analysis.utils import check_modifiability, validate_expected_type

Check notice

Code scanning / CodeQL

Cyclic import Note

Import of module
vyper.semantics.analysis.utils
begins an import cycle.
@charles-cooper
Copy link
Member

can you merge in the latest changes from master? and then i think this should be more or less good to go

call_type_modifiability = getattr(call_type, "_modifiability", Modifiability.MODIFIABLE)
return call_type_modifiability >= modifiability
# structs and interfaces
if isinstance(call_type, TYPE_T):
Copy link
Member

Choose a reason for hiding this comment

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

it's more complicated but i think this actually should use TYPE_T.check_modifiability_for_call (which can dispatch to something like StructT.ctor_modifiability_for_call). the way you have it written, a struct instance can pass the modifiability check.

call_type_modifiability = getattr(call_type, "_modifiability", Modifiability.MODIFIABLE)
return call_type_modifiability >= modifiability
# structs and interfaces
if hasattr(call_type, "check_modifiability_for_call"):
Copy link
Member

Choose a reason for hiding this comment

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

as a general comment, i'd like to move these hasattr checks to be more like

if call_type._is_callable:
    call_type.check_modifiability_for_call(...)

that way we impose more structure on the APIs of callable types. but i think that can be pushed to "future work"

@@ -334,6 +334,11 @@ def __init__(self, typedef):
def __repr__(self):
return f"type({self.typedef})"

def check_modifiability_for_call(self, node, modifiability):
if hasattr(self.typedef, "_ctor_modifiability_for_call"):
Copy link
Member

Choose a reason for hiding this comment

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

ditto here -- all these hasattr(self.typedef, "_ctor_...") could be more like

if self._is_callable:
    self.typedef._ctor_modifiability_for_call(...)

@@ -81,6 +85,9 @@ def _ctor_arg_types(self, node):
def _ctor_kwarg_types(self, node):
return {}

def _ctor_modifiability_for_call(self, node: vy_ast.Call, modifiability: Modifiability) -> bool:
return check_modifiability(node.args[0], modifiability)
Copy link
Member

Choose a reason for hiding this comment

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

this will panic if len(node.args) < 1 (because in some cases, check_modifiability runs before validate_call_args).

@@ -419,3 +420,6 @@ def _ctor_call_return(self, node: vy_ast.Call) -> "StructT":
)

return self

def _ctor_modifiability_for_call(self, node: vy_ast.Call, modifiability: Modifiability) -> bool:
return all(check_modifiability(v, modifiability) for v in node.args[0].values)
Copy link
Member

Choose a reason for hiding this comment

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

same -- will panic if len(node.args) < 1

validate_expected_type,
)
from vyper.semantics.analysis.base import FunctionVisibility, StateMutability, StorageSlot
from vyper.semantics.analysis.utils import get_exact_type_from_node, validate_expected_type

Check notice

Code scanning / CodeQL

Cyclic import

Import of module [vyper.semantics.analysis.utils](1) begins an import cycle.
Copy link
Member

@charles-cooper charles-cooper left a comment

Choose a reason for hiding this comment

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

looks good. i worked a bit on the panic issue in bf7b346 but i think we can punt on it to a future PR since it's a fairly minor UX thing. (and it's not really a regression since that ordering of check_modifiability before the call to validate_call_args was already latent in the codebase).

@charles-cooper charles-cooper changed the title feat: allow interface constants feat: allow constant interfaces Jan 15, 2024
@charles-cooper charles-cooper enabled auto-merge (squash) January 15, 2024 15:03
@charles-cooper charles-cooper merged commit 88d9c22 into vyperlang:master Jan 15, 2024
164 checks passed
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.

Can't have an interface as a constant
3 participants