Skip to content

Commit 305813c

Browse files
feat[tool]: add -Werror and -Wnone options (#4447)
this commit adds `-Werror` and `-Wnone` CLI flags which promote warnings to errors and ignore them, respectively. also adds some small refactoring for warning handling to simplify the implementation (allow us to use warnings.simplefilter). this provides a way moving forward to do fine-grained warnings control, since the warnings filter allows us to use the exception class in the filter. --------- Co-authored-by: tserg <[email protected]>
1 parent a466dc8 commit 305813c

File tree

19 files changed

+269
-80
lines changed

19 files changed

+269
-80
lines changed

docs/compiling-a-contract.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,23 @@ The following is a list of supported EVM versions, and changes in the compiler i
198198
- Functions marked with ``@nonreentrant`` are protected with TLOAD/TSTORE instead of SLOAD/SSTORE
199199
- The ``MCOPY`` opcode will be generated automatically by the compiler for most memory operations.
200200

201+
202+
.. _warnings:
203+
204+
Controlling Warnings
205+
====================
206+
207+
Vyper allows suppression of warnings via the CLI flag ``-Wnone``, or promotion of (all) warnings to errors via the ``-Werror`` flag.
208+
209+
.. code:: shell
210+
211+
$ vyper -Wnone foo.vy # suppress warnings
212+
213+
.. code:: shell
214+
215+
$ vyper -Werror foo.vy # promote warnings to errors
216+
217+
201218
.. _integrity-hash:
202219

203220
Integrity Hash
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import random
2+
3+
import pytest
4+
5+
import vyper
6+
7+
8+
@pytest.fixture
9+
def huge_bytestring():
10+
r = random.Random(b"vyper")
11+
12+
return bytes([r.getrandbits(8) for _ in range(0x6001)])
13+
14+
15+
def test_contract_size_exceeded(huge_bytestring):
16+
code = f"""
17+
@external
18+
def a() -> bool:
19+
q: Bytes[24577] = {huge_bytestring}
20+
return True
21+
"""
22+
with pytest.warns(vyper.warnings.ContractSizeLimit):
23+
vyper.compile_code(code, output_formats=["bytecode_runtime"])
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import json
2+
3+
import pytest
4+
5+
import vyper
6+
from vyper.cli.vyper_json import compile_json
7+
8+
deprecated = [
9+
"""
10+
struct Foo:
11+
a: uint256
12+
b: uint256
13+
14+
@external
15+
def foo():
16+
f: Foo = Foo({a: 128, b: 256})
17+
""",
18+
"""
19+
event Foo:
20+
a: uint256
21+
b: uint256
22+
23+
@external
24+
def foo():
25+
log Foo({a: 128, b: 256})
26+
""",
27+
]
28+
29+
30+
@pytest.mark.parametrize("code", deprecated)
31+
def test_deprecated_warning(code):
32+
with pytest.warns(vyper.warnings.Deprecation):
33+
vyper.compile_code(code)
34+
35+
36+
def test_deprecated_optimize_boolean_flag():
37+
code = """
38+
@external
39+
def foo():
40+
pass
41+
"""
42+
43+
input_json = {
44+
"language": "Vyper",
45+
"sources": {"contracts/foo.vy": {"content": code}},
46+
"settings": {"outputSelection": {"*": ["*"]}, "optimize": True},
47+
}
48+
49+
with pytest.warns(vyper.warnings.Deprecation):
50+
compile_json(json.dumps(input_json))
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import pytest
2+
3+
import vyper
4+
5+
6+
def test_enum_usage_warning():
7+
code = """
8+
enum Foo:
9+
Fe
10+
Fi
11+
Fo
12+
13+
@external
14+
def foo() -> Foo:
15+
return Foo.Fe
16+
"""
17+
with pytest.warns(vyper.warnings.EnumUsage):
18+
vyper.compile_code(code)

tests/unit/cli/vyper_compile/test_parse_args.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import os
2+
import warnings
23

34
import pytest
45

56
from vyper.cli.vyper_compile import _parse_args
7+
from vyper.warnings import VyperWarning
68

79

810
@pytest.fixture
@@ -27,3 +29,33 @@ def foo() -> bool:
2729

2830
_parse_args([str(bar_path)]) # absolute path, subfolder of cwd
2931
_parse_args([str(bar_path.relative_to(chdir_path.parent))]) # relative path
32+
33+
34+
def test_warnings(make_file):
35+
"""
36+
test -Werror and -Wnone
37+
"""
38+
# test code which emits warnings
39+
code = """
40+
x: public(uint256[2**64])
41+
"""
42+
path = make_file("foo.vy", code)
43+
path_str = str(path)
44+
45+
with warnings.catch_warnings(record=True) as w:
46+
_parse_args([str(path)])
47+
48+
with pytest.raises(VyperWarning) as e:
49+
_parse_args([path_str, "-Werror"])
50+
51+
assert len(w) == 1
52+
warning_message = w[0].message.message
53+
54+
assert e.value.message == warning_message
55+
56+
# test squashing warnings
57+
with warnings.catch_warnings(record=True) as w:
58+
_parse_args([path_str, "-Wnone"])
59+
assert len(w) == 0
60+
61+
warnings.resetwarnings()

tests/unit/compiler/test_compile_code.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,6 @@
1-
import random
2-
3-
import pytest
4-
51
import vyper
62

73

8-
@pytest.fixture
9-
def huge_bytestring():
10-
r = random.Random(b"vyper")
11-
12-
return bytes([r.getrandbits(8) for _ in range(0x6001)])
13-
14-
15-
def test_contract_size_exceeded(huge_bytestring):
16-
code = f"""
17-
@external
18-
def a() -> bool:
19-
q: Bytes[24577] = {huge_bytestring}
20-
return True
21-
"""
22-
with pytest.warns(vyper.warnings.ContractSizeLimitWarning):
23-
vyper.compile_code(code, output_formats=["bytecode_runtime"])
24-
25-
264
# test that each compilation run gets a fresh analysis and storage allocator
275
def test_shared_modules_allocation(make_input_bundle):
286
lib1 = """

vyper/ast/nodes.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import operator
88
import pickle
99
import sys
10-
import warnings
1110
from typing import Any, Optional, Union
1211

1312
from vyper.ast.metadata import NodeMetadata
@@ -23,7 +22,6 @@
2322
TypeMismatch,
2423
UnfoldableNode,
2524
VariableDeclarationException,
26-
VyperException,
2725
ZeroDivisionException,
2826
)
2927
from vyper.utils import (
@@ -34,6 +32,7 @@
3432
quantize,
3533
sha256sum,
3634
)
35+
from vyper.warnings import EnumUsage, vyper_warn
3736

3837
NODE_BASE_ATTRIBUTES = (
3938
"_children",
@@ -133,13 +132,9 @@ def get_node(
133132

134133
node = vy_class(parent=parent, **ast_struct)
135134

136-
# TODO: Putting this after node creation to pretty print, remove after enum deprecation
137135
if enum_warn:
138-
# TODO: hack to pretty print, logic should be factored out of exception
139-
pretty_printed_node = str(VyperException("", node))
140-
warnings.warn(
141-
f"enum will be deprecated in a future release, use flag instead. {pretty_printed_node}",
142-
stacklevel=2,
136+
vyper_warn(
137+
EnumUsage("enum will be deprecated in a future release, use flag instead.", node)
143138
)
144139

145140
node.validate()

vyper/ast/parse.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
from vyper.ast.pre_parser import PreParser
1010
from vyper.compiler.settings import Settings
1111
from vyper.exceptions import CompilerPanic, ParserException, SyntaxException
12-
from vyper.utils import sha256sum, vyper_warn
12+
from vyper.utils import sha256sum
13+
from vyper.warnings import Deprecation, vyper_warn
1314

1415

1516
def parse_to_ast(*args: Any, **kwargs: Any) -> vy_ast.Module:
@@ -442,7 +443,7 @@ def visit_Call(self, node):
442443

443444
# add full_source_code so that str(VyperException(msg, node)) works
444445
node.full_source_code = self._source_code
445-
vyper_warn(msg, node)
446+
vyper_warn(Deprecation(msg, node))
446447

447448
dict_ = node.args[0]
448449
kw_list = []

vyper/builtins/functions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@
9696
keccak256,
9797
method_id,
9898
method_id_int,
99-
vyper_warn,
10099
)
100+
from vyper.warnings import vyper_warn
101101

102102
from ._convert import convert
103103
from ._signatures import BuiltinFunctionT, process_inputs

vyper/cli/vyper_compile.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#!/usr/bin/env python3
22
import argparse
3+
import functools
4+
import inspect
35
import json
46
import os
57
import sys
6-
import warnings
78
from pathlib import Path
89
from typing import Any, Optional
910

@@ -16,6 +17,7 @@
1617
from vyper.compiler.settings import VYPER_TRACEBACK_LIMIT, OptimizationLevel, Settings
1718
from vyper.typing import ContractPath, OutputFormats
1819
from vyper.utils import uniq
20+
from vyper.warnings import warnings_filter
1921

2022
format_options_help = """Format to print, one or more of (comma-separated):
2123
bytecode (default) - Deployable bytecode
@@ -97,8 +99,6 @@ def _cli_helper(f, output_formats, compiled):
9799

98100

99101
def _parse_args(argv):
100-
warnings.simplefilter("always")
101-
102102
if "--standard-json" in argv:
103103
argv.remove("--standard-json")
104104
vyper_json._parse_args(argv)
@@ -186,6 +186,10 @@ def _parse_args(argv):
186186
)
187187
parser.add_argument("--enable-decimals", help="Enable decimals", action="store_true")
188188

189+
parser.add_argument(
190+
"-W", help="Control warnings", dest="warnings_control", choices=["error", "none"]
191+
)
192+
189193
args = parser.parse_args(argv)
190194

191195
if args.traceback_limit is not None:
@@ -247,6 +251,7 @@ def _parse_args(argv):
247251
settings,
248252
args.storage_layout,
249253
args.no_bytecode_metadata,
254+
args.warnings_control,
250255
)
251256

252257
mode = "w"
@@ -290,6 +295,21 @@ def get_search_paths(paths: list[str] = None, include_sys_path=True) -> list[Pat
290295
return search_paths
291296

292297

298+
def _apply_warnings_filter(func):
299+
@functools.wraps(func)
300+
def inner(*args, **kwargs):
301+
# find "warnings_control" argument
302+
ba = inspect.signature(func).bind(*args, **kwargs)
303+
ba.apply_defaults()
304+
305+
warnings_control = ba.arguments["warnings_control"]
306+
with warnings_filter(warnings_control):
307+
return func(*args, **kwargs)
308+
309+
return inner
310+
311+
312+
@_apply_warnings_filter
293313
def compile_files(
294314
input_files: list[str],
295315
output_formats: OutputFormats,
@@ -299,6 +319,7 @@ def compile_files(
299319
settings: Optional[Settings] = None,
300320
storage_layout_paths: list[str] = None,
301321
no_bytecode_metadata: bool = False,
322+
warnings_control: Optional[str] = None,
302323
) -> dict:
303324
search_paths = get_search_paths(paths, include_sys_path)
304325
input_bundle = FilesystemInputBundle(search_paths)

0 commit comments

Comments
 (0)