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

fix: type inference and annotation of arguments for builtin functions #2817

Merged
merged 161 commits into from
May 30, 2022

Conversation

tserg
Copy link
Collaborator

@tserg tserg commented Apr 20, 2022

What I did

Fix #2706, fix #2788, fix #2819, fix #2827

How I did it

Annotate Int Vyper nodes.

For min-max and unsafe maths with a literal and a variable, annotate the Int Vyper node with the variable type. Subsequently, use the propagated type in codegen.

  • Validation of call arguments is moved into a new _validate_arg_types helper function.
  • Infer and annotate the types for the arguments of builtin functions in a new infer_arg_type function.
  • _BuiltinFunction class has been removed and subsumed under _SimpleBuiltinFunction.
  • Renamed Optional class to KwargSettings.
  • Introduced TypeTypeDefinition to handle nodes representing pure typestrings.
  • Refactored validate_inputs and process_arg for builtin_functions/signatures.

For _UnsafeMath and _MinMax, the type inferred is the common type of both operands.

In addition, validation of arguments is moved from fetch_call_return to the new infer_arg_type function.

How to verify it

See tests.

Commit message

Type inference and annotation of inputs for builtin functions

This commit introduces type inference or derivation and annotation for
 arguments and kwargs of builtin functions, through the use of two helper 
functions, `infer_arg_types` and `infer_kwarg_types`.

Generally, the type is derived for each argument/kwarg and annotated as it is 
(see the default implementation in the `_SimpleBuiltinFunction` class).
Type inference is performed where (i) the input is an abstract type (e.g. the
start index for Extract32); (ii) the input is a range of possible types (e.g. the input
to slice can be a bytes32, bytes array or string); or (iii) a common type is to be derived
between multiple arguments (e.g. for _UnsafeMath and _MinMax, the type 
inferred being the common type of both operands). 

Other major refactoring include:
- Validation of call arguments is moved into a new `_validate_arg_types` 
helper function.
- `_BuiltinFunction` class has been removed and subsumed under 
`_SimpleBuiltinFunction`.
- Renamed `Optional` class to `KwargSettings`.
- Introduced `TypeTypeDefinition` to handle nodes representing pure typestrings.
- Refactored `validate_inputs` and `process_arg` for `builtin_functions/signatures`.

Fix #2706, fix #2788, fix #2819, fix #2827

Description for the changelog

Type inference and annotation of arguments for builtin functions

Cute Animal Picture

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

@tserg
Copy link
Collaborator Author

tserg commented Apr 20, 2022

Annotation of Int Vyper node results in an unrelated failing test in _check_valid_range_constant.

e.g. minimal repro from examples/auctions/blind_auction.vy

MAX_BIDS: constant(int128) = 128

for i in range(MAX_BIDS):
    // do something

Even though MAX_BIDS is declared as int128 type, it was treated as uint256 or int256 during codegen.

@codecov-commenter
Copy link

codecov-commenter commented Apr 20, 2022

Codecov Report

Merging #2817 (82527af) into master (3195701) will increase coverage by 0.27%.
The diff coverage is 94.42%.

@@            Coverage Diff             @@
##           master    #2817      +/-   ##
==========================================
+ Coverage   87.67%   87.94%   +0.27%     
==========================================
  Files          94       94              
  Lines       10183    10257      +74     
  Branches     2501     2478      -23     
==========================================
+ Hits         8928     9021      +93     
+ Misses        790      783       -7     
+ Partials      465      453      -12     
Impacted Files Coverage Δ
vyper/semantics/validation/local.py 91.18% <ø> (+0.33%) ⬆️
vyper/codegen/expr.py 79.31% <66.66%> (-1.43%) ⬇️
vyper/semantics/types/bases.py 84.34% <66.66%> (-0.36%) ⬇️
vyper/builtin_functions/signatures.py 88.63% <85.29%> (+14.72%) ⬆️
vyper/builtin_functions/functions.py 91.72% <95.73%> (+2.48%) ⬆️
vyper/semantics/validation/annotation.py 89.38% <96.55%> (-3.02%) ⬇️
vyper/builtin_functions/utils.py 100.00% <100.00%> (ø)
vyper/codegen/stmt.py 88.16% <100.00%> (+0.26%) ⬆️
vyper/semantics/namespace.py 97.29% <100.00%> (+0.32%) ⬆️
vyper/semantics/types/function.py 87.12% <100.00%> (-0.10%) ⬇️
... and 17 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3195701...82527af. Read the comment docs.

@charles-cooper
Copy link
Member

Annotation of Int Vyper node results in an unrelated failing test in _check_valid_range_constant.

it's very related to this: #2707. i think instead of having ad-hoc rules for min/max, we need to be able to annotate all builtins with the correct argument types

@tserg
Copy link
Collaborator Author

tserg commented Apr 20, 2022

Annotation of Int Vyper node results in an unrelated failing test in _check_valid_range_constant.

it's very related to this: #2707. i think instead of having ad-hoc rules for min/max, we need to be able to annotate all builtins with the correct argument types

Got it, will consolidate under either of these two PRs.

Btw, Is it bad practice to change the type definition of an integer literal during codegen phase? Using minmax as an example where the operand is an integer literal and a variable, we may want to cast the integer literal to be of the same type as the variable.

@charles-cooper
Copy link
Member

Btw, Is it bad practice to change the type definition of an integer literal during codegen phase? Using minmax as an example where the operand is an integer literal and a variable, we may want to cast the integer literal to be of the same type as the variable.

yeah i think that, ideally, literals should use the type definition inferred during the annotation phase.

@tserg
Copy link
Collaborator Author

tserg commented Apr 21, 2022

Interestingly, this PR seems to handle #2707.

@tserg
Copy link
Collaborator Author

tserg commented Apr 21, 2022

i think instead of having ad-hoc rules for min/max, we need to be able to annotate all builtins with the correct argument types

After looking through the builtin functions, I think the annotation/typechecking issue only arises for those that require its arguments to be of the same type (specifically min, max and unsafe maths). Only in these situations, where the arguments consist of a variable and a literal, do we need to cast the literal to be of the variable's type.

@tserg
Copy link
Collaborator Author

tserg commented Apr 21, 2022

Some convert tests involving integer literals are failing as they were treated as uint256 or int256 type. If we infer the integer type for the convert builtin, we can support this syntax.

Example - this raises a TypeMismatch exception in 0.3.2, but is valid if we infer the type of the integer literal.

@external
@view
def test_convert() -> bytes29:
    return convert(1, bytes29)

@tserg
Copy link
Collaborator Author

tserg commented Apr 22, 2022

There is some code duplication between the new helper function and fetch_call_return. Also, fetch_call_return is sometimes validating the argument types in order to determine the return type. I think it will be cleaner to have fetch_call_return return a tuple of a list of argument types and the return type.

@charles-cooper
Copy link
Member

The issue is that fetch_call_return is a general mechanism that all Calls use. But maybe for builtins we can add an interface function like "check_and_infer_arg_types".

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.

great work! and thanks for your patience through many rounds of review

Copy link
Member

@fubuloubu fubuloubu left a comment

Choose a reason for hiding this comment

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

Address the feedback and it has my approval to merge

@@ -2,16 +2,16 @@ def test_extract32_extraction(assert_tx_failed, get_contract_with_gas_estimation
extract32_code = """
y: Bytes[100]
@external
def extrakt32(inp: Bytes[100], index: int128) -> bytes32:
Copy link
Member

Choose a reason for hiding this comment

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

Why was this type changed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Negative index is not allowed for the index argument extract32 even though the index type was int128. As I understand, int128 was the only integer type when this was implemented. Since Vyper has unsigned integer types now, index type has been updated to be of UnsignedIntegerAbstractType instead and would be more reflective as well.

Copy link
Member

Choose a reason for hiding this comment

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

uint256 was added several years ago, so is kind of strange it was like that

tests/parser/functions/test_extract32.py Show resolved Hide resolved
tests/parser/functions/test_convert.py Show resolved Hide resolved
tests/parser/functions/test_concat.py Show resolved Hide resolved
tests/parser/features/iteration/test_for_in_list.py Outdated Show resolved Hide resolved
@charles-cooper charles-cooper merged commit 02d02db into vyperlang:master May 30, 2022
@charles-cooper
Copy link
Member

note: @fubuloubu gave approval offline

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants