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[ux]: raise VersionException with source info #3920

Merged
merged 13 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 39 additions & 4 deletions tests/unit/ast/test_pre_parser.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import pytest

from vyper import compile_code
from vyper.ast.pre_parser import pre_parse, validate_version_pragma
from vyper.compiler.phases import CompilerData
from vyper.compiler.settings import OptimizationLevel, Settings
Expand Down Expand Up @@ -45,14 +46,14 @@ def set_version(version):
@pytest.mark.parametrize("file_version", valid_versions)
def test_valid_version_pragma(file_version, mock_version):
mock_version(COMPILER_VERSION)
validate_version_pragma(f"{file_version}", (SRC_LINE))
validate_version_pragma(f"{file_version}", file_version, (SRC_LINE))


@pytest.mark.parametrize("file_version", invalid_versions)
def test_invalid_version_pragma(file_version, mock_version):
mock_version(COMPILER_VERSION)
with pytest.raises(VersionException):
validate_version_pragma(f"{file_version}", (SRC_LINE))
validate_version_pragma(f"{file_version}", file_version, (SRC_LINE))


prerelease_valid_versions = [
Expand Down Expand Up @@ -82,14 +83,14 @@ def test_invalid_version_pragma(file_version, mock_version):
@pytest.mark.parametrize("file_version", prerelease_valid_versions)
def test_prerelease_valid_version_pragma(file_version, mock_version):
mock_version(PRERELEASE_COMPILER_VERSION)
validate_version_pragma(file_version, (SRC_LINE))
validate_version_pragma(file_version, file_version, (SRC_LINE))


@pytest.mark.parametrize("file_version", prerelease_invalid_versions)
def test_prerelease_invalid_version_pragma(file_version, mock_version):
mock_version(PRERELEASE_COMPILER_VERSION)
with pytest.raises(VersionException):
validate_version_pragma(file_version, (SRC_LINE))
validate_version_pragma(file_version, file_version, (SRC_LINE))


pragma_examples = [
Expand Down Expand Up @@ -224,3 +225,37 @@ def test_parse_pragmas(code, pre_parse_settings, compiler_data_settings, mock_ve
def test_invalid_pragma(code):
with pytest.raises(StructureException):
pre_parse(code)


def test_version_conflict_with_imports(make_input_bundle, mock_version):
tserg marked this conversation as resolved.
Show resolved Hide resolved
lib_version = "~=0.3.10"
contract_version = "0.4.0"
Copy link
Member

Choose a reason for hiding this comment

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

i'm not sure this is necessary (it's also brittle, the test will start failing for the wrong reason after 0.4.0 release)


lib_pragma = f"#pragma version {lib_version}\n"
lib = f"""
{lib_pragma}
@external
def foo():
pass
"""

contract_pragma = f"#pragma version {contract_version}\n"
code = f"""
{contract_pragma}
import lib

uses: lib

@external
def bar():
pass
"""
input_bundle = make_input_bundle({"lib.vy": lib})

mock_version(contract_version)
with pytest.raises(VersionException) as excinfo:
compile_code(code, input_bundle=input_bundle)
annotation = excinfo.value.annotations[0]
assert annotation.lineno == 2
assert annotation.col_offset == 0
assert annotation.full_source_code == lib_pragma
13 changes: 7 additions & 6 deletions vyper/ast/pre_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
from vyper.typing import ModificationOffsets, ParserPosition


def validate_version_pragma(version_str: str, start: ParserPosition) -> None:
def validate_version_pragma(version_str: str, code: str, start: ParserPosition) -> None:
"""
Validates a version pragma directive against the current compiler version.
"""
from vyper import __version__

if len(version_str) == 0:
raise VersionException("Version specification cannot be empty", start)
raise VersionException("Version specification cannot be empty", code, *start)

# X.Y.Z or vX.Y.Z => ==X.Y.Z, ==vX.Y.Z
if re.match("[v0-9]", version_str):
Expand All @@ -34,14 +34,15 @@ def validate_version_pragma(version_str: str, start: ParserPosition) -> None:
spec = SpecifierSet(version_str)
except InvalidSpecifier:
raise VersionException(
f'Version specification "{version_str}" is not a valid PEP440 specifier', start
f'Version specification "{version_str}" is not a valid PEP440 specifier', code, *start
)

if not spec.contains(__version__, prereleases=True):
raise VersionException(
f'Version specification "{version_str}" is not compatible '
f'with compiler version "{__version__}"',
start,
code,
*start,
)


Expand Down Expand Up @@ -176,7 +177,7 @@ def pre_parse(code: str) -> tuple[Settings, ModificationOffsets, dict, str]:
if settings.compiler_version is not None:
raise StructureException("compiler version specified twice!", start)
compiler_version = contents.removeprefix("@version ").strip()
validate_version_pragma(compiler_version, start)
validate_version_pragma(compiler_version, line, start)
Copy link
Member

Choose a reason for hiding this comment

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

i don't think this works if the version pragma is not on the first line?

settings.compiler_version = compiler_version

if contents.startswith("pragma "):
Expand All @@ -185,7 +186,7 @@ def pre_parse(code: str) -> tuple[Settings, ModificationOffsets, dict, str]:
if settings.compiler_version is not None:
raise StructureException("pragma version specified twice!", start)
compiler_version = pragma.removeprefix("version ").strip()
validate_version_pragma(compiler_version, start)
validate_version_pragma(compiler_version, line, start)
settings.compiler_version = compiler_version

elif pragma.startswith("optimize "):
Expand Down
2 changes: 1 addition & 1 deletion vyper/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ class InstantiationException(StructureException):
"""Variable or expression cannot be instantiated"""


class VersionException(VyperException):
class VersionException(SyntaxException):
"""Version string is malformed or incompatible with this compiler version."""


Expand Down
Loading