Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ feature, not an optional smoke check.
source material for migrations.
- `docs/migration-coverage.md` — mapping from each syntax-history entry to an
automated rule, diagnostic, explicit no-op, or validation-only behavior.
- [`DEVELOPMENT.md`](DEVELOPMENT.md) — maintainer setup, architecture,
rule-writing, corpus, and release workflow reference.
- `scripts/corpus.py` — corpus import, dedupe, and compiler-backed smoke tooling.
- `scripts/smoke-wheel.sh` — package smoke test used by CI and publish workflows.

Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

- Added opt-in support for targeting Vyper `0.5.0a3`; custom error syntax is
treated as newly accepted source and broad alpha pragmas compile with `a3`
when needed.

## 0.4.1 - 2026-06-06

- Fixed standard-json corpus imports so only declared compilation targets are
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ rewrite is safe by compiling the source and the result and comparing their ABI,
method identifiers, and storage layout.

It covers installable Vyper `0.1.0b*` prereleases through `0.4.3`, plus opt-in
`0.5.0a1` and `0.5.0a2` alpha targets. Rules are version-gated: a given rewrite
only fires when the migration from the source version to the target version
actually crosses the compiler release that introduced the change.
`0.5.0a1` through `0.5.0a3` alpha targets. Rules are version-gated: a given
rewrite only fires when the migration from the source version to the target
version actually crosses the compiler release that introduced the change.

## Install

Expand Down Expand Up @@ -46,7 +46,7 @@ vyupgrade contracts/ --check
```

The target defaults to `0.4.3`; pass `--target-version` to migrate to a
different release, including an explicit alpha target such as `0.5.0a2`.
different release, including an explicit alpha target such as `0.5.0a3`.

Paths may be files or directories; directories are searched recursively for
`.vy` and `.vyi` sources. The source version is inferred per file from its
Expand Down Expand Up @@ -135,7 +135,7 @@ diagnostic instead of being applied.
change mapped to a rule, diagnostic, explicit no-op, or validation-only
behavior.
- [docs/vyper-syntax-history.md](docs/vyper-syntax-history.md) — the versioned
Vyper syntax history from `0.5.0a2` back through the `0.1.0b*` prereleases,
Vyper syntax history from `0.5.0a3` back through the `0.1.0b*` prereleases,
with PR links and before/after examples.
- [CHANGELOG.md](CHANGELOG.md) — release notes.
- [DEVELOPMENT.md](DEVELOPMENT.md) — maintainer validation and release workflow.
5 changes: 5 additions & 0 deletions docs/migration-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ modern Python interpreter.

## v0.5.x prereleases

### v0.5.0a3

- Custom errors added: no-op. This is newly accepted revert declaration and
raising syntax; existing string revert reasons remain valid.

### v0.5.0a2

- Docstring-only function bodies rejected: `VY131` inserts an explicit `pass`
Expand Down
24 changes: 23 additions & 1 deletion docs/vyper-syntax-history.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Vyper Syntax History

This document tracks Vyper source syntax changes from `v0.5.0a2` back through
This document tracks Vyper source syntax changes from `v0.5.0a3` back through
the installable `v0.1.0-beta.*` prereleases. It is intended as upgrade source
material for `vyupgrade`.

Expand All @@ -12,6 +12,28 @@ runtime semantic changes unless they require source text to change.

## v0.5.x prereleases

### v0.5.0a3

Release: <https://github.com/vyperlang/vyper/releases/tag/v0.5.0a3>

- Custom errors added. [#4791](https://github.com/vyperlang/vyper/pull/4791)

Before:

```vyper
# No contract-defined custom error declaration syntax.
raise "unauthorized"
```

After:

```vyper
error Unauthorized:
caller: address

raise Unauthorized(caller=msg.sender)
```

### v0.5.0a2

Release: <https://github.com/vyperlang/vyper/releases/tag/v0.5.0a2>
Expand Down
4 changes: 3 additions & 1 deletion src/vyupgrade/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# PyPI has no final 0.1.0 release; these are the installable Vyper releases before 0.2.1.
LEGACY_PRERELEASE_VERSIONS = tuple(f"0.1.0b{number}" for number in range(1, 18))
LEGACY_PRERELEASES = frozenset(Version(version) for version in LEGACY_PRERELEASE_VERSIONS)
ALPHA_RELEASE_VERSIONS = ("0.5.0a1", "0.5.0a2")
ALPHA_RELEASE_VERSIONS = ("0.5.0a1", "0.5.0a2", "0.5.0a3")
ALPHA_RELEASES = frozenset(Version(version) for version in ALPHA_RELEASE_VERSIONS)


Expand Down Expand Up @@ -142,6 +142,8 @@ def _source_syntax_floor(source: str) -> VyperVersion | None:
floors.append(Version("0.3.7"))
if re.search(r"\bsend\s*\([^)]*\bgas\s*=", source):
floors.append(Version("0.3.8"))
if re.search(r"(?m)^\s*error\s+[A-Za-z_][A-Za-z0-9_]*\s*:", source):
floors.append(Version("0.5.0a3"))
return max(floors) if floors else None


Expand Down
9 changes: 9 additions & 0 deletions tests/test_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def test_known_versions_cover_full_supported_range() -> None:
assert is_supported_source_version("0.4.3")
assert is_supported_source_version("0.5.0a1")
assert is_supported_source_version("0.5.0a2")
assert is_supported_source_version("0.5.0a3")
assert not is_supported_source_version("0.1.0")
assert not is_supported_source_version("0.1.0b18")
assert not is_supported_source_version("0.1.0b99")
Expand All @@ -36,6 +37,7 @@ def test_known_versions_cover_full_supported_range() -> None:
assert VyperVersion("0.2.1") in known_versions_satisfying(">=0.2.1,<0.2.3")
assert VyperVersion("0.4.3") in known_versions_satisfying(">=0.4.0")
assert VyperVersion("0.5.0a2") in known_versions_satisfying(">=0.5.0a1,<0.5.0")
assert VyperVersion("0.5.0a3") in known_versions_satisfying(">=0.5.0a1,<0.5.0")


def test_version_specs_pick_lowest_satisfying_source_floor() -> None:
Expand All @@ -48,6 +50,7 @@ def test_version_specs_pick_lowest_satisfying_source_floor() -> None:
assert compiler_version_for_spec("<=0.3.10") == "0.3.10"
assert compiler_version_for_spec(">=0.5.0a1,<0.5.0") == "0.5.0a1"
assert compiler_version_for_spec("<=0.5.0a2") == "0.5.0a2"
assert compiler_version_for_spec("<=0.5.0a3") == "0.5.0a3"


def test_source_syntax_hints_raise_broad_pragma_compiler_floor() -> None:
Expand All @@ -60,6 +63,12 @@ def test_source_syntax_hints_raise_broad_pragma_compiler_floor() -> None:
assert compiler_version_for_source("^0.3.0", "send(self.owner, fee, gas=msg.gas)") == "0.3.8"
assert compiler_version_for_source(">=0.3.8,<0.4.0", "TOKEN: immutable(address)") == "0.3.8"
assert compiler_version_for_source(">=0.3.0,<0.3.4", "enum Side:\n BUY\n") == "0.3.0"
assert (
compiler_version_for_source(
">=0.5.0a1,<0.5.0", "error Unauthorized:\n caller: address\n"
)
== "0.5.0a3"
)


def test_migration_context_tracks_patch_level_crossings() -> None:
Expand Down