language-upgrades.md 19 KB

Language Version Upgrades

Detailed upgrade paths for major programming language version transitions.


Python 3.9 to 3.13

Python 3.10 (from 3.9)

Key Features Gained:

  • Structural pattern matching (match/case)
  • Parenthesized context managers
  • Better error messages with precise line indicators
  • typing.TypeAlias for explicit type aliases
  • zip() gets strict parameter
  • bisect and statistics module improvements

Breaking Changes:

  • distutils deprecated (use setuptools instead)
  • loop parameter removed from most asyncio high-level APIs
  • int has a new bit_count() method (name collision risk)

Migration Commands:

# Update pyproject.toml / setup.cfg
python-requires = ">=3.10"

# Run pyupgrade for syntax modernization
pip install pyupgrade
pyupgrade --py310-plus $(fd -e py)

# Check for distutils usage
rg "from distutils" .
rg "import distutils" .

Python 3.11 (from 3.10)

Key Features Gained:

  • Exception groups and except* syntax
  • tomllib in standard library (TOML parsing)
  • Task groups in asyncio (asyncio.TaskGroup)
  • Fine-grained error locations in tracebacks
  • 10-60% faster CPython (Faster CPython project)
  • Self type in typing module
  • StrEnum class

Breaking Changes:

  • asyncio.coroutine decorator removed
  • unittest.TestCase.addModuleCleanup behavior change
  • locale.getdefaultlocale() deprecated
  • smtpd module removed (use aiosmtpd)

Migration Commands:

pyupgrade --py311-plus $(fd -e py)

# Replace manual TOML parsing
rg "import toml\b" .        # replace with: import tomllib
rg "toml\.loads?" .          # replace with: tomllib.loads / tomllib.load

# Check for removed modules
rg "import smtpd" .
rg "asyncio\.coroutine" .

Python 3.12 (from 3.11)

Key Features Gained:

  • Type parameter syntax (class Stack[T]:, def first[T](l: list[T]) -> T:)
  • type statement for type aliases (type Vector = list[float])
  • F-string improvements (nested quotes, backslashes, comments)
  • Per-interpreter GIL (subinterpreters)
  • pathlib.Path.walk() method
  • Improved asyncio.TaskGroup semantics
  • Buffer protocol accessible from Python (__buffer__)

Breaking Changes:

  • distutils package removed entirely (was deprecated in 3.10)
  • imp module removed (use importlib)
  • locale.getdefaultlocale() removed
  • unittest method aliases removed (assertEquals etc.)
  • asyncio legacy API removals
  • pkgutil.find_loader() / get_loader() removed
  • sqlite3 default adapters and converters no longer registered by default
  • os.popen() and os.spawn*() deprecated
  • Wstr representation removed from C API

Migration Commands:

pyupgrade --py312-plus $(fd -e py)

# Check for removed modules
rg "import imp\b" .          # replace with importlib
rg "from imp " .
rg "import distutils" .      # must use setuptools
rg "from distutils" .

# Check for removed unittest aliases
rg "assertEquals|assertNotEquals|assertRegexpMatches" .

# Adopt new type syntax (optional but recommended)
# Old: T = TypeVar('T')
# New: def func[T](x: T) -> T:

Python 3.13 (from 3.12)

Key Features Gained:

  • Free-threaded mode (experimental, --disable-gil build)
  • Improved interactive interpreter (REPL with colors, multiline editing)
  • locals() returns copy with defined semantics
  • Improved error messages (color, suggestions)
  • dbm.sqlite3 as default dbm backend
  • argparse deprecations enforced
  • JIT compiler (experimental, --enable-experimental-jit build)

Breaking Changes:

  • aifc, audioop, cgi, cgitb, chunk, crypt, imghdr, mailcap, msilib, nis, nntplib, ossaudiodev, pipes, sndhdr, spwd, sunau, telnetlib, uu, xdrlib modules removed
  • pathlib.PurePath.is_relative_to() and relative_to() semantics change
  • typing.io and typing.re namespaces removed
  • locale.resetlocale() removed
  • C API changes affecting extension modules

Migration Commands:

# Check for removed stdlib modules
rg "import (aifc|audioop|cgi|cgitb|chunk|crypt|imghdr|mailcap|nis|nntplib|ossaudiodev|pipes|sndhdr|spwd|sunau|telnetlib|uu|xdrlib)" .

# For cgi module replacement
rg "import cgi" .       # replace with: from urllib.parse import parse_qs
rg "cgi.FieldStorage" . # replace with: manual multipart parsing or framework

# Check for typing namespace changes
rg "typing\.io\." .
rg "typing\.re\." .

# Test free-threaded mode (experimental)
python3.13t script.py  # if built with --disable-gil

Python Version Upgrade Summary

From → To Key Action Biggest Risk
3.9 → 3.10 Fix distutils usage, adopt pattern matching asyncio loop parameter removal
3.10 → 3.11 Replace toml with tomllib, enjoy speed boost smtpd removal
3.11 → 3.12 Remove distutils/imp, adopt type syntax distutils full removal, sqlite3 adapter changes
3.12 → 3.13 Remove deprecated stdlib modules Large number of removed stdlib modules

Node.js 18 to 22

Node.js 20 (from 18)

Key Features Gained:

  • Permission model (--experimental-permission)
  • Stable test runner (node:test)
  • .env file support (--env-file=.env)
  • V8 11.3 (improved performance)
  • import.meta.resolve() unflagged
  • Single executable applications (SEA)
  • URL.canParse() static method
  • ArrayBuffer.transfer() and resizable option
  • WebSocket client (experimental)

Breaking Changes:

  • url.parse() may throw on invalid URLs (stricter parsing)
  • fs.read() parameter validation stricter
  • Custom ESM loader hooks (load, resolve) are off-thread
  • http.IncomingMessage connected socket timeout default change

Migration Commands:

# Update nvm / fnm
nvm install 20
nvm use 20

# Or update Docker
# FROM node:20-alpine

# Check for url.parse usage (may need URL constructor)
rg "url\.parse\(" .

# Adopt built-in test runner (optional)
# Replace: jest/mocha test files
# With: import { test, describe } from 'node:test';

# Use .env file support
node --env-file=.env app.js

Node.js 22 (from 20)

Key Features Gained:

  • require() for ESM modules (experimental --experimental-require-module)
  • WebSocket client stable
  • Built-in watch mode stable (node --watch)
  • glob and globSync in node:fs
  • V8 12.4 (Maglev compiler, Array.fromAsync)
  • node:sqlite built-in module (experimental)
  • --run flag for package.json scripts
  • Task runner integration
  • AbortSignal.any()
  • Stable permission model

Breaking Changes:

  • node:http stricter header validation
  • node:buffer Blob changes
  • Minimum glibc 2.28 on Linux
  • node:child_process IPC serialization changes
  • node:dns default resolver changes

Migration Commands:

nvm install 22
nvm use 22

# Or update Docker
# FROM node:22-alpine

# Check for incompatible native modules
npm rebuild

# Test ESM/CJS interop if using mixed modules
node --experimental-require-module app.js

# Adopt built-in features
# Replace: glob package → node:fs { glob, globSync }
# Replace: ws package → built-in WebSocket (for client usage)
# Replace: chokidar/nodemon → node --watch

Node.js Version Upgrade Summary

From → To Key Action Biggest Risk
18 → 20 Rebuild native modules, test URL parsing Stricter URL validation, loader hooks off-thread
20 → 22 Rebuild native modules, check glibc version Native module compatibility, header validation

TypeScript 4.x to 5.x

TypeScript 5.0 (from 4.9)

Key Features Gained:

  • ECMAScript decorators (stage 3 standard)
  • const type parameters
  • --moduleResolution bundler
  • extends on multiple config files
  • All enums become union enums
  • --verbatimModuleSyntax (replaces isolatedModules)
  • Speed and size improvements (TS migrated to modules internally)
  • satisfies operator (introduced in 4.9, now mature)

Breaking Changes:

  • --target ES3 removed
  • --out removed (use --outFile)
  • --noImplicitUseStrict removed
  • --suppressExcessPropertyErrors removed
  • --suppressImplicitAnyIndexErrors removed
  • --prepend in project references removed
  • Runtime behavior of decorators changed (now ECMAScript standard)
  • --moduleResolution node renamed to node10
  • --module value changes

Migration Commands:

npm install -D typescript@5

# Check for removed compiler options in tsconfig.json
rg '"target":\s*"ES3"' tsconfig.json
rg '"out":' tsconfig.json
rg '"suppressExcessPropertyErrors"' tsconfig.json

# If using legacy decorators, keep experimentalDecorators flag
# If adopting new decorators, remove experimentalDecorators

# Adopt bundler module resolution
# tsconfig.json: "moduleResolution": "bundler"

TypeScript 5.1-5.7 Highlights

Version Key Feature
5.1 Easier implicit return for undefined, unrelated getter/setter types
5.2 using declarations (explicit resource management), decorator metadata
5.3 import attribute support, resolution-mode in all module modes
5.4 NoInfer<T> utility type, Object.groupBy / Map.groupBy types
5.5 Inferred type predicates, regex syntax checking, isolatedDeclarations
5.6 Iterator helper methods, --noUncheckedSideEffectImports
5.7 --rewriteRelativeImportExtensions, --target es2024

Migration Strategy

TypeScript version upgrade approach:
│
├─ Minor version (5.x → 5.y)
│  └─ Generally safe, just update and fix new errors
│     npm install -D typescript@5.y
│     npx tsc --noEmit
│
└─ Major version (4.x → 5.x)
   ├─ 1. Update tsconfig.json (remove deleted options)
   ├─ 2. Install typescript@5
   ├─ 3. Run tsc --noEmit, fix errors
   ├─ 4. Decide on decorator strategy (legacy vs ECMAScript)
   └─ 5. Consider adopting moduleResolution: "bundler"

Go 1.20 to 1.23

Go 1.21 (from 1.20)

Key Features Gained:

  • log/slog structured logging (standard library)
  • slices and maps packages in standard library
  • min() and max() built-in functions
  • clear() built-in for maps and slices
  • PGO (Profile-Guided Optimization) generally available
  • go.mod toolchain directive
  • Forward compatibility (GOTOOLCHAIN environment variable)

Breaking Changes:

  • go.mod now tracks toolchain version
  • Panic on nil pointer dereference in more cases
  • net/http minor behavior changes

Migration Commands:

# Update go.mod
go mod edit -go=1.21
go mod tidy

# Adopt slog for structured logging
rg "log\.Printf|log\.Println" .  # candidates for slog migration

# Replace sort.Slice with slices.SortFunc
rg "sort\.Slice\b" .  # consider slices.SortFunc

Go 1.22 (from 1.21)

Key Features Gained:

  • for range over integers (for i := range 10)
  • Enhanced net/http routing (method + path patterns)
  • Loop variable fix (each iteration gets its own variable)
  • math/rand/v2 package
  • go/version package
  • slices.Concat

Breaking Changes:

  • Loop variable semantics change (each iteration gets a copy -- fixes the classic goroutine-in-loop bug)
  • math/rand global functions deterministic without seed

Migration Commands:

go mod edit -go=1.22
go mod tidy

# The loop variable change is backward compatible but may fix hidden bugs
# Review goroutine closures in loops that relied on shared variable

# Adopt enhanced routing
# Old: mux.HandleFunc("/users", handler) + manual method check
# New: mux.HandleFunc("GET /users/{id}", handler)
rg "r\.Method ==" .  # candidates for enhanced routing

Go 1.23 (from 1.22)

Key Features Gained:

  • Iterators (iter.Seq, iter.Seq2) and range over func
  • unique package (interning/canonicalization)
  • structs package (struct layout control)
  • Timer/Ticker changes (garbage collected when unreferenced)
  • slices and maps moved from golang.org/x/exp to standard library
  • OpenTelemetry-compatible log/slog handlers

Breaking Changes:

  • time.Timer and time.Ticker behavior change (channels drained on Stop/Reset)
  • os/exec LookPath behavior on Windows (security fix)

Migration Commands:

go mod edit -go=1.23
go mod tidy

# Replace x/exp/slices and x/exp/maps with standard library versions
rg "golang.org/x/exp/slices" .
rg "golang.org/x/exp/maps" .
# Replace with: "slices" and "maps"

# Check Timer/Ticker usage
rg "\.Stop\(\)" . --glob "*.go"  # Review timer stop behavior
rg "\.Reset\(" . --glob "*.go"   # Review timer reset behavior

Go Version Upgrade Summary

From → To Key Action Biggest Risk
1.20 → 1.21 Update go.mod toolchain, adopt slog Toolchain directive in go.mod
1.21 → 1.22 Enjoy loop variable fix, adopt enhanced routing Loop variable semantics (usually fixes bugs)
1.22 → 1.23 Replace x/exp packages, adopt iterators Timer/Ticker behavior change

Rust Edition 2021 to 2024

Key Features in Edition 2024

  • RPITIT (Return Position Impl Trait in Traits): use -> impl Trait in trait definitions
  • Async fn in traits: async fn directly in trait definitions (no need for async-trait crate)
  • let chains: if let Some(x) = a && let Some(y) = b { ... }
  • gen blocks (experimental): generator-based iterators
  • Lifetime capture rules: all in-scope lifetimes captured by default in -> impl Trait
  • unsafe_op_in_unsafe_fn lint: must use unsafe {} blocks inside unsafe fn
  • Precise capturing with use<> syntax
  • #[diagnostic] attribute namespace for custom diagnostics
  • Reserving gen keyword for generators
  • Temporary lifetime extension changes in match and if let

Breaking Changes

Change Impact Fix
unsafe_op_in_unsafe_fn is deny by default unsafe fn bodies need explicit unsafe {} blocks Wrap unsafe operations in unsafe {}
Lifetime capture rules change -> impl Trait captures all in-scope lifetimes Use use<'a> for precise control
gen is a reserved keyword Cannot use gen as identifier Rename gen variables/functions
never type fallback changes ! type fallback now ! instead of () May affect type inference in rare cases
Temporary lifetime changes Temporaries in match scrutinee have shorter lifetime Store temporaries in let bindings
unsafe extern blocks extern items implicitly unsafe to reference Add safe keyword to safe extern items
Disallow references to static mut &STATIC_MUT is forbidden Use addr_of!() / addr_of_mut!()

Migration Commands

# Automatic edition migration
cargo fix --edition

# Update Cargo.toml
# edition = "2024"

# Fix unsafe_op_in_unsafe_fn warnings
cargo clippy --fix -- -W unsafe_op_in_unsafe_fn

# Check for gen keyword conflicts
rg "\bgen\b" src/ --glob "*.rs"

# Remove async-trait crate if adopting native async traits
rg "async.trait" Cargo.toml
rg "#\[async_trait\]" src/

Verification Steps

cargo build
cargo test
cargo clippy -- -D warnings
cargo doc --no-deps  # check documentation builds

PHP 8.1 to 8.4

PHP 8.2 (from 8.1)

Key Features Gained:

  • Readonly classes
  • Disjunctive Normal Form (DNF) types
  • null, false, true as standalone types
  • Constants in traits
  • Enum improvements
  • Random extension (\Random\Randomizer)
  • SensitiveParameter attribute
  • Fibers improvements

Breaking Changes:

  • Dynamic properties deprecated (use #[AllowDynamicProperties] or __get/__set)
  • Implicit nullable parameter declarations deprecated
  • ${var} string interpolation deprecated (use {$var})
  • utf8_encode / utf8_decode deprecated
  • Various internal class changes

Migration Commands:

# Rector automated fixes
composer require rector/rector --dev
vendor/bin/rector process src --set php82

# Check for dynamic properties
rg "->(\w+)\s*=" src/ --glob "*.php"  # review for undeclared properties

# Check deprecated string interpolation
rg '"\$\{' src/ --glob "*.php"

PHP 8.3 (from 8.2)

Key Features Gained:

  • Typed class constants
  • json_validate() function
  • #[\Override] attribute
  • Deep cloning of readonly properties in __clone()
  • Dynamic class constant fetch ($class::{$constant})
  • Randomizer::getBytesFromString()
  • mb_str_pad() function
  • Improved unserialize() error handling

Breaking Changes:

  • array_sum() and array_product() behavior changes
  • proc_get_status() multiple calls return same result
  • range() type checking stricter
  • number_format() behavior change with negative zero

Migration Commands:

vendor/bin/rector process src --set php83

# Adopt #[Override] attribute on methods
# This catches parent method renames at compile time

# Adopt typed constants
# Old: const STATUS = 'active';
# New: const string STATUS = 'active';

# Use json_validate() instead of json_decode() for validation
rg "json_decode.*json_last_error" src/ --glob "*.php"

PHP 8.4 (from 8.3)

Key Features Gained:

  • Property hooks (get/set)
  • Asymmetric visibility (public private(set))
  • #[\Deprecated] attribute
  • new without parentheses in chained expressions
  • HTML5 DOM parser (\Dom\HTMLDocument)
  • Lazy objects (ReflectionClass::newLazyProxy())
  • array_find(), array_find_key(), array_any(), array_all()
  • Multibyte functions for trim, ltrim, rtrim
  • request_parse_body() for non-POST requests

Breaking Changes:

  • Implicitly nullable parameter types trigger deprecation notice
  • E_STRICT constant deprecated
  • session_set_save_handler() with open/close etc. deprecated
  • strtolower() and strtoupper() locale-insensitive
  • Various DOM API changes for HTML5 compliance

Migration Commands:

vendor/bin/rector process src --set php84

# Adopt property hooks (optional but recommended)
# Old:
# private string $name;
# public function getName(): string { return $this->name; }
# public function setName(string $name): void { $this->name = $name; }
# New:
# public string $name {
#     get => $this->name;
#     set(string $value) => $this->name = strtolower($value);
# }

# Adopt asymmetric visibility
# public private(set) string $name;

# Check for implicit nullable types
rg "function \w+\([^)]*\w+ \$\w+ = null" src/ --glob "*.php"

PHP Version Upgrade Summary

From → To Key Action Biggest Risk
8.1 → 8.2 Fix dynamic properties, deprecation warnings Dynamic properties deprecated
8.2 → 8.3 Adopt typed constants, #[Override] array_sum/array_product behavior
8.3 → 8.4 Adopt property hooks, asymmetric visibility Implicit nullable deprecation

Cross-Language Upgrade Checklist

Regardless of which language you are upgrading:

[ ] CI matrix includes both old and new versions during transition
[ ] Linter/formatter updated to support new syntax
[ ] IDE / editor language server updated
[ ] Docker base images updated
[ ] Deployment pipeline runtime version updated
[ ] New language features documented in team style guide
[ ] Deprecated API usage eliminated before upgrade
[ ] All tests pass on new version
[ ] Performance benchmarks compared pre/post upgrade
[ ] Third-party dependencies verified compatible