From e1f4756b410bc82d257c9cfebbaca62a880528a4 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:17:17 +0800 Subject: [PATCH 01/13] inherit syntax exception --- vyper/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vyper/exceptions.py b/vyper/exceptions.py index 996a1ddbd9..7fb7e33da5 100644 --- a/vyper/exceptions.py +++ b/vyper/exceptions.py @@ -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.""" From 27c25bc7a4de3146e23f3a26495d62ce43f6ba65 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:17:25 +0800 Subject: [PATCH 02/13] add line info --- vyper/ast/pre_parser.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/vyper/ast/pre_parser.py b/vyper/ast/pre_parser.py index f0c339cca7..f81ae68ddd 100644 --- a/vyper/ast/pre_parser.py +++ b/vyper/ast/pre_parser.py @@ -15,14 +15,15 @@ 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__ + lineno, col_offset = start if len(version_str) == 0: - raise VersionException("Version specification cannot be empty", start) + raise VersionException("Version specification cannot be empty", code, lineno, col_offset) # X.Y.Z or vX.Y.Z => ==X.Y.Z, ==vX.Y.Z if re.match("[v0-9]", version_str): @@ -34,14 +35,19 @@ 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, + lineno, + col_offset, ) if not spec.contains(__version__, prereleases=True): raise VersionException( f'Version specification "{version_str}" is not compatible ' f'with compiler version "{__version__}"', - start, + code, + lineno, + col_offset, ) @@ -176,7 +182,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) settings.compiler_version = compiler_version if contents.startswith("pragma "): @@ -185,7 +191,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 "): From 0a481c37ebac713a4abd7b5e36de4334f96fb3e8 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 8 Apr 2024 16:17:30 +0800 Subject: [PATCH 03/13] add test --- tests/unit/ast/test_pre_parser.py | 43 ++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/tests/unit/ast/test_pre_parser.py b/tests/unit/ast/test_pre_parser.py index 020e83627c..2d554c9981 100644 --- a/tests/unit/ast/test_pre_parser.py +++ b/tests/unit/ast/test_pre_parser.py @@ -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 @@ -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 = [ @@ -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 = [ @@ -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): + lib_version = "~=0.3.10" + contract_version = "0.4.0" + + 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 From 6ed04365683ca876cde3372fb06d915932496f3f Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 8 Apr 2024 21:56:26 +0800 Subject: [PATCH 04/13] add wrapper --- vyper/ast/pre_parser.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/vyper/ast/pre_parser.py b/vyper/ast/pre_parser.py index f81ae68ddd..3e4c6580e9 100644 --- a/vyper/ast/pre_parser.py +++ b/vyper/ast/pre_parser.py @@ -11,19 +11,18 @@ # seems a bit early to be importing this but we want it to validate the # evm-version pragma from vyper.evm.opcodes import EVM_VERSIONS -from vyper.exceptions import StructureException, SyntaxException, VersionException +from vyper.exceptions import StructureException, SyntaxException, VersionException, VyperException from vyper.typing import ModificationOffsets, ParserPosition -def validate_version_pragma(version_str: str, code: str, start: ParserPosition) -> None: +def _validate_version_pragma(version_str: str) -> None: """ Validates a version pragma directive against the current compiler version. """ from vyper import __version__ - lineno, col_offset = start if len(version_str) == 0: - raise VersionException("Version specification cannot be empty", code, lineno, col_offset) + raise VyperException("Version specification cannot be empty") # X.Y.Z or vX.Y.Z => ==X.Y.Z, ==vX.Y.Z if re.match("[v0-9]", version_str): @@ -34,23 +33,24 @@ def validate_version_pragma(version_str: str, code: str, start: ParserPosition) try: spec = SpecifierSet(version_str) except InvalidSpecifier: - raise VersionException( - f'Version specification "{version_str}" is not a valid PEP440 specifier', - code, - lineno, - col_offset, + raise VyperException( + f'Version specification "{version_str}" is not a valid PEP440 specifier' ) if not spec.contains(__version__, prereleases=True): - raise VersionException( + raise VyperException( f'Version specification "{version_str}" is not compatible ' - f'with compiler version "{__version__}"', - code, - lineno, - col_offset, + f'with compiler version "{__version__}"' ) +def validate_version(version_str: str, code: str, start: ParserPosition) -> None: + try: + _validate_version_pragma(version_str) + except VyperException as e: + raise VersionException(e.message, code, *start) + + class ForParserState(enum.Enum): NOT_RUNNING = enum.auto() START_SOON = enum.auto() @@ -182,7 +182,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, line, start) + validate_version(compiler_version, line, start) settings.compiler_version = compiler_version if contents.startswith("pragma "): @@ -191,7 +191,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, line, start) + validate_version(compiler_version, line, start) settings.compiler_version = compiler_version elif pragma.startswith("optimize "): From fa9ca1eafa643af7fc053c9cca001325b81ae1ee Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 8 Apr 2024 22:24:23 +0800 Subject: [PATCH 05/13] Revert "add wrapper" This reverts commit 6ed04365683ca876cde3372fb06d915932496f3f. --- vyper/ast/pre_parser.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/vyper/ast/pre_parser.py b/vyper/ast/pre_parser.py index 3e4c6580e9..f81ae68ddd 100644 --- a/vyper/ast/pre_parser.py +++ b/vyper/ast/pre_parser.py @@ -11,18 +11,19 @@ # seems a bit early to be importing this but we want it to validate the # evm-version pragma from vyper.evm.opcodes import EVM_VERSIONS -from vyper.exceptions import StructureException, SyntaxException, VersionException, VyperException +from vyper.exceptions import StructureException, SyntaxException, VersionException from vyper.typing import ModificationOffsets, ParserPosition -def _validate_version_pragma(version_str: str) -> 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__ + lineno, col_offset = start if len(version_str) == 0: - raise VyperException("Version specification cannot be empty") + raise VersionException("Version specification cannot be empty", code, lineno, col_offset) # X.Y.Z or vX.Y.Z => ==X.Y.Z, ==vX.Y.Z if re.match("[v0-9]", version_str): @@ -33,24 +34,23 @@ def _validate_version_pragma(version_str: str) -> None: try: spec = SpecifierSet(version_str) except InvalidSpecifier: - raise VyperException( - f'Version specification "{version_str}" is not a valid PEP440 specifier' + raise VersionException( + f'Version specification "{version_str}" is not a valid PEP440 specifier', + code, + lineno, + col_offset, ) if not spec.contains(__version__, prereleases=True): - raise VyperException( + raise VersionException( f'Version specification "{version_str}" is not compatible ' - f'with compiler version "{__version__}"' + f'with compiler version "{__version__}"', + code, + lineno, + col_offset, ) -def validate_version(version_str: str, code: str, start: ParserPosition) -> None: - try: - _validate_version_pragma(version_str) - except VyperException as e: - raise VersionException(e.message, code, *start) - - class ForParserState(enum.Enum): NOT_RUNNING = enum.auto() START_SOON = enum.auto() @@ -182,7 +182,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(compiler_version, line, start) + validate_version_pragma(compiler_version, line, start) settings.compiler_version = compiler_version if contents.startswith("pragma "): @@ -191,7 +191,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(compiler_version, line, start) + validate_version_pragma(compiler_version, line, start) settings.compiler_version = compiler_version elif pragma.startswith("optimize "): From 7ccfd8deb15f3626f6f0f31fba3f41938f40e548 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 8 Apr 2024 22:24:57 +0800 Subject: [PATCH 06/13] undo destructuring --- vyper/ast/pre_parser.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/vyper/ast/pre_parser.py b/vyper/ast/pre_parser.py index f81ae68ddd..d52e9dd64e 100644 --- a/vyper/ast/pre_parser.py +++ b/vyper/ast/pre_parser.py @@ -21,9 +21,8 @@ def validate_version_pragma(version_str: str, code: str, start: ParserPosition) """ from vyper import __version__ - lineno, col_offset = start if len(version_str) == 0: - raise VersionException("Version specification cannot be empty", code, lineno, col_offset) + 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): @@ -37,8 +36,7 @@ def validate_version_pragma(version_str: str, code: str, start: ParserPosition) raise VersionException( f'Version specification "{version_str}" is not a valid PEP440 specifier', code, - lineno, - col_offset, + *start ) if not spec.contains(__version__, prereleases=True): @@ -46,8 +44,7 @@ def validate_version_pragma(version_str: str, code: str, start: ParserPosition) f'Version specification "{version_str}" is not compatible ' f'with compiler version "{__version__}"', code, - lineno, - col_offset, + *start ) From db395a2e17addaeee5821bd473e17b798a076863 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 8 Apr 2024 22:25:08 +0800 Subject: [PATCH 07/13] fix lint --- vyper/ast/pre_parser.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vyper/ast/pre_parser.py b/vyper/ast/pre_parser.py index d52e9dd64e..bd47729828 100644 --- a/vyper/ast/pre_parser.py +++ b/vyper/ast/pre_parser.py @@ -34,9 +34,7 @@ def validate_version_pragma(version_str: str, code: str, start: ParserPosition) spec = SpecifierSet(version_str) except InvalidSpecifier: raise VersionException( - f'Version specification "{version_str}" is not a valid PEP440 specifier', - code, - *start + f'Version specification "{version_str}" is not a valid PEP440 specifier', code, *start ) if not spec.contains(__version__, prereleases=True): @@ -44,7 +42,7 @@ def validate_version_pragma(version_str: str, code: str, start: ParserPosition) f'Version specification "{version_str}" is not compatible ' f'with compiler version "{__version__}"', code, - *start + *start, ) From 132b4c123c77c2fd9d39214fce8b652028ef5868 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 8 Apr 2024 23:26:46 +0800 Subject: [PATCH 08/13] fix cc comment --- vyper/ast/pre_parser.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/vyper/ast/pre_parser.py b/vyper/ast/pre_parser.py index bd47729828..645c857a2f 100644 --- a/vyper/ast/pre_parser.py +++ b/vyper/ast/pre_parser.py @@ -15,14 +15,14 @@ from vyper.typing import ModificationOffsets, ParserPosition -def validate_version_pragma(version_str: str, code: str, start: ParserPosition) -> None: +def validate_version_pragma(version_str: str, full_source_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", code, *start) + raise VersionException("Version specification cannot be empty", full_source_code, *start) # X.Y.Z or vX.Y.Z => ==X.Y.Z, ==vX.Y.Z if re.match("[v0-9]", version_str): @@ -34,14 +34,16 @@ def validate_version_pragma(version_str: str, code: str, start: ParserPosition) spec = SpecifierSet(version_str) except InvalidSpecifier: raise VersionException( - f'Version specification "{version_str}" is not a valid PEP440 specifier', code, *start + f'Version specification "{version_str}" is not a valid PEP440 specifier', + full_source_code, + *start, ) if not spec.contains(__version__, prereleases=True): raise VersionException( f'Version specification "{version_str}" is not compatible ' f'with compiler version "{__version__}"', - code, + full_source_code, *start, ) @@ -177,7 +179,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, line, start) + validate_version_pragma(compiler_version, code, start) settings.compiler_version = compiler_version if contents.startswith("pragma "): @@ -186,7 +188,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, line, start) + validate_version_pragma(compiler_version, code, start) settings.compiler_version = compiler_version elif pragma.startswith("optimize "): From 4cf8f4d1c10b98c7807b3bc33dfb3aa851e28f5e Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Mon, 8 Apr 2024 23:29:09 +0800 Subject: [PATCH 09/13] fix test --- tests/unit/ast/test_pre_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/ast/test_pre_parser.py b/tests/unit/ast/test_pre_parser.py index 2d554c9981..e4605a15a4 100644 --- a/tests/unit/ast/test_pre_parser.py +++ b/tests/unit/ast/test_pre_parser.py @@ -258,4 +258,4 @@ def bar(): annotation = excinfo.value.annotations[0] assert annotation.lineno == 2 assert annotation.col_offset == 0 - assert annotation.full_source_code == lib_pragma + assert annotation.full_source_code == lib From cde889f78f6e9ca9c6ba315e211f496179737ec9 Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 9 Apr 2024 11:44:35 +0800 Subject: [PATCH 10/13] apply bts suggestion --- tests/unit/ast/test_pre_parser.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/unit/ast/test_pre_parser.py b/tests/unit/ast/test_pre_parser.py index e4605a15a4..9c32b5a481 100644 --- a/tests/unit/ast/test_pre_parser.py +++ b/tests/unit/ast/test_pre_parser.py @@ -229,7 +229,6 @@ def test_invalid_pragma(code): def test_version_conflict_with_imports(make_input_bundle, mock_version): lib_version = "~=0.3.10" - contract_version = "0.4.0" lib_pragma = f"#pragma version {lib_version}\n" lib = f""" @@ -239,9 +238,7 @@ def foo(): pass """ - contract_pragma = f"#pragma version {contract_version}\n" code = f""" -{contract_pragma} import lib uses: lib @@ -252,7 +249,6 @@ def bar(): """ 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] From 2b0eba414c77f5d2d98dc1d91410c840fe7df9bd Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 9 Apr 2024 22:14:12 +0800 Subject: [PATCH 11/13] Update tests/unit/ast/test_pre_parser.py Co-authored-by: Charles Cooper --- tests/unit/ast/test_pre_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/ast/test_pre_parser.py b/tests/unit/ast/test_pre_parser.py index 9c32b5a481..5f996fd316 100644 --- a/tests/unit/ast/test_pre_parser.py +++ b/tests/unit/ast/test_pre_parser.py @@ -227,7 +227,7 @@ def test_invalid_pragma(code): pre_parse(code) -def test_version_conflict_with_imports(make_input_bundle, mock_version): +def test_version_exception_in_import(make_input_bundle): lib_version = "~=0.3.10" lib_pragma = f"#pragma version {lib_version}\n" From bfe3c8e2c217d6769959f7d150bed0be7596e8dc Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 9 Apr 2024 22:22:58 +0800 Subject: [PATCH 12/13] fix lint --- tests/unit/ast/test_pre_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/ast/test_pre_parser.py b/tests/unit/ast/test_pre_parser.py index 5f996fd316..465c432bd9 100644 --- a/tests/unit/ast/test_pre_parser.py +++ b/tests/unit/ast/test_pre_parser.py @@ -231,7 +231,7 @@ def test_version_exception_in_import(make_input_bundle): lib_version = "~=0.3.10" lib_pragma = f"#pragma version {lib_version}\n" - lib = f""" + lib = """ {lib_pragma} @external def foo(): From e8296772a75a4fe36ff059a3b15c51fa62caf75d Mon Sep 17 00:00:00 2001 From: tserg <8017125+tserg@users.noreply.github.com> Date: Tue, 9 Apr 2024 22:24:23 +0800 Subject: [PATCH 13/13] more clean up --- tests/unit/ast/test_pre_parser.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/unit/ast/test_pre_parser.py b/tests/unit/ast/test_pre_parser.py index 465c432bd9..cd1a91f210 100644 --- a/tests/unit/ast/test_pre_parser.py +++ b/tests/unit/ast/test_pre_parser.py @@ -229,16 +229,15 @@ def test_invalid_pragma(code): def test_version_exception_in_import(make_input_bundle): lib_version = "~=0.3.10" + lib = f""" +#pragma version {lib_version} - lib_pragma = f"#pragma version {lib_version}\n" - lib = """ -{lib_pragma} @external def foo(): pass """ - code = f""" + code = """ import lib uses: lib