Skip to content

Upgrade to Scala 3.7.4 and Modernization Audit#10

Merged
hakimjonas merged 19 commits intomainfrom
modernization/scala-3.7.4-upgrade
Nov 22, 2025
Merged

Upgrade to Scala 3.7.4 and Modernization Audit#10
hakimjonas merged 19 commits intomainfrom
modernization/scala-3.7.4-upgrade

Conversation

@hakimjonas
Copy link
Copy Markdown
Owner

Summary

This PR upgrades Valar to Scala 3.7.4 and Scala Native 0.5.9, and includes a comprehensive modernization audit analyzing opportunities to leverage recent Scala 3 improvements.

Changes

Upgrades

  • ✅ Scala version: 3.7.13.7.4
  • ✅ Scala Native: 0.5.80.5.9
  • ✅ All tests passing (JVM + Native)
  • ✅ Full compatibility maintained

New Documentation

  • 📄 MODERNIZATION_AUDIT.md - Comprehensive analysis of modernization opportunities

Test Results

✅ JVM Tests: 88/88 passing
✅ Native Tests: 88/88 passing
✅ Compilation: Clean
✅ mdoc: Passing

Modernization Audit Highlights

Current State: Already Modern ✨

The audit confirms that Valar's metaprogramming architecture is already quite modern and well-designed:

  • ✅ Clean quotes reflection API usage
  • ✅ Strategic inline usage for zero-cost abstractions
  • ✅ Well-structured macro derivation
  • ✅ Excellent separation of concerns

Identified Opportunities

The opportunities are evolutionary, not revolutionary - focused on refinement rather than rewrites.

Phase 1: Quick Wins (v0.5.1) - ~8-10 hours

  1. Transparent Inline for Better Type Inference

    • Add transparent inline to MacroHelper.upcastTo
    • Effort: 30 minutes | Risk: Very Low
    • Benefit: Better type inference at call sites
  2. Enhanced Error Messages with Source Positions

    • Add source positions to macro error reports
    • Effort: 1 hour | Risk: Very Low
    • Benefit: Better developer experience and IDE integration
  3. Split Macro Implementation

    • Refactor deriveValidatorImpl into separate sync/async macros
    • Effort: 6-8 hours | Risk: Low
    • Benefit: Better maintainability and compile-time performance

Total Estimated Effort: 8-10 hours

Phase 2: Type-Level Enhancements (v0.6.0) - 1-2 months

  1. Inline Match Types for Field Analysis

    • More idiomatic Scala 3 style
    • Research branch required for validation
  2. Compile-Time Validator Validation

    • Catch configuration errors at compile time
    • Enhanced user experience
  3. Type-Level Field Path Construction

    • Zero runtime overhead
    • Better IDE integration
    • Major feature candidate

Phase 3: Future Exploration (v0.7.0+) - 6-12 months

  1. Explicit Nulls Support (Scala 3.8+)

    • Eliminate runtime null checks
    • Major safety improvement
  2. Capture Checking for Async Validation

    • Prevent reference escape bugs
    • Enhanced type safety

Scala 3.7.x Improvements Leveraged

The audit analyzed Scala 3.7.x release improvements:

  • Enhanced inline method handling
  • Improved metaprogramming and macro capabilities
  • Better type class derivation
  • More robust reflection API
  • Better error messages and tooling

Inline Metaprogramming Analysis

Current inline usage is optimal:

// Zero-cost abstraction - perfect use case ✓
inline given noOpObserver: ValidationObserver with {
  def onResult[A](result: ValidationResult[A]): Unit = ()
}

// Enables dead-code elimination ✓
inline def observe()(using observer: ValidationObserver): ValidationResult[A] = {
  observer.onResult(vr)
  vr
}

// Minimal necessary casting ✓
inline def upcastTo[T](x: Any): T = x.asInstanceOf[T]

Performance & Compatibility

  • ✅ Zero runtime performance impact
  • ✅ Binary compatibility maintained (MiMa checks)
  • ✅ TASTy compatibility maintained
  • ✅ All documentation builds successfully

Recommendations

Immediate Next Steps

  1. Merge this PR - Safe upgrade with no breaking changes
  2. Plan v0.5.1 release - Implement Phase 1 quick wins (~2 weeks)
  3. Create experimental branches - Validate Phase 2 enhancements

Long-term Strategy

The upgrade validates that Valar's architecture is forward-compatible and robust. Focus should be on:

  • Incremental refinements for optimal performance
  • Enhanced developer experience
  • Monitoring Scala ecosystem for stabilized features

Breaking Changes

None. This is a fully backward-compatible upgrade.

Migration Guide

No changes required for library users. Simply update your Scala version to 3.7.4+ if desired.

Documentation

The MODERNIZATION_AUDIT.md document includes:

  • Detailed analysis of current architecture
  • Scala 3.7.x improvements assessment
  • Prioritized modernization opportunities
  • Phased implementation roadmap
  • Testing strategy
  • Performance optimization analysis

Review Focus Areas

  1. ✅ Test results (all passing)
  2. ✅ Build configuration changes
  3. 📄 Modernization audit document completeness
  4. 🎯 Phase 1 quick wins feasibility

Assessment: The codebase is in excellent shape. This upgrade confirms the architecture is modern, performant, and ready for future Scala improvements.

- Upgrade Scala version from 3.7.1 to 3.7.4
- Upgrade Scala Native from 0.5.8 to 0.5.9
- Add comprehensive modernization audit document
- All tests passing (JVM + Native)

The audit analyzes opportunities to leverage Scala 3.7.x improvements
in inline metaprogramming, type class derivation, and macro capabilities.
Key finding: Current architecture is already modern and well-designed.

Includes phased modernization roadmap:
- Phase 1 (v0.5.1): Quick wins - transparent inline, better errors
- Phase 2 (v0.6.0): Type-level enhancements
- Phase 3 (v0.7.0+): Future Scala 3.8+ features
…safety

- Add `transparent inline` to MacroHelper.upcastTo for better type inference
- Split deriveValidatorImpl into typed deriveSyncValidatorImpl/deriveAsyncValidatorImpl
- Eliminate unnecessary .asExprOf casts by returning properly typed Expr
- Add Position.ofMacroExpansion to all error messages for better IDE integration

These changes follow the modernization roadmap from MODERNIZATION_AUDIT.md,
improving code maintainability and developer experience with no functional changes.
Phase 1 - Zero-cast field access:
- Case classes: Select.unique(a, "fieldName") - zero cast
- Regular tuples: Select.unique(a, "_1") - zero cast
- Named tuples: productElement with cast (matches stdlib pattern)
- Remove MacroHelper.scala (no longer needed)

Phase 2 - Type-level enhancements:
- Inline Option detection during field processing (single pass)
- Compile-time validator validation with comprehensive error messages
- Type-level label extraction using pattern matching
- Better error messages showing ALL missing validators at once

Key changes:
- Derivation.scala refactored with cleaner Scala 3 idioms
- Added Scala 3.7.4 stdlib sources for reference (Tuple.scala, NamedTuple.scala)
- Replace "macros" terminology with "inline metaprogramming"
- Update Scala version references to 3.7.4
- Update MODERNIZATION_AUDIT.md to reflect completed Phase 1 & 2 work
- Remove MacroHelper.scala references (file was deleted)
- Mark all implemented items as complete
- Update roadmap and conclusions
- Remove IMPLEMENTATION_SUMMARY.md (internal dev log)
- Remove MODERNIZATION_AUDIT.md (internal architecture audit)
- Fix "macros" → "inline metaprogramming" in MIGRATION.md
README.md:
- Remove emojis from headings and lists
- Simplify language, reduce marketing tone
- Consolidate redundant sections

CONTRIBUTING.md:
- Add basic contribution guidelines
- Include development setup instructions
- Document project structure
design.md:
- Scala 2 vs Scala 3 typeclass derivation comparison
- Architecture overview
- Field access strategy explanation
- Why inline metaprogramming

examples.md:
- Async validation with AsyncValidator
- Validator composition (sequential and parallel)
- Union and intersection type validation
- Nested case classes
- Collection validation
Named tuple field access requires asInstanceOf (matches Scala stdlib pattern).
Null checks are defensive validation for potentially null field values.
Derivation no longer checks for null field values. This keeps the
core library simple and fast for idiomatic Scala 3 code.

For Java interop or Spark, users can define null-aware validators:

  given Validator[String] with {
    def validate(s: String) =
      if (s == null) ValidationResult.invalid(...)
      else ...
  }

Changes:
- Remove null checks from sync and async field validation
- Remove unused isOption parameter from field generation
- Remove null-related tests (NullFieldTest, Team)
- Add documentation in TROUBLESHOOTING.md
Changes:
- Remove 15 pass-through tests from ValidatorSpec (e.g., "Int validator
  should return Valid(x) for x") - these just verify the identity function
- Update AsyncValidatorSpec Scaladoc to remove stale null references
- Add ValidationConfigSpec with 5 tests covering:
  - Default unlimited collection sizes
  - Strict config 10,000 limit enforcement
  - Custom collection size limits
  - Fail-fast behavior (no element validation when size exceeded)
- Make ValidationResultTestOps package-visible for test reuse
- Minor scalafmt reformatting in Derivation.scala
Built-in validators for Int, String, Float, and Double now accept all
values. Constraints are opt-in via ValidationHelpers.

Why: The opinionated defaults (Int must be non-negative, String must be
non-empty) limited Valar's use as a general-purpose foundation. Users
validating temperatures, legacy data, or scientific values had to fight
the library's defaults.

Changes:
- Validator[Int]: accepts all values (use nonNegativeInt for constraints)
- Validator[String]: accepts all values (use nonEmpty for constraints)
- Validator[Float]: accepts all values (use finiteFloat for constraints)
- Validator[Double]: accepts all values (use finiteDouble for constraints)
- AsyncValidator: updated to match sync validators
- Tests: updated to verify pass-through behavior
- Docs: updated Built-in Validators section, added migration guide
…traction

Addresses reviewer feedback on DRY violation - collection validation logic
was duplicated between Validator and AsyncValidator.

New internal abstractions (private[valar]):
- ValidationEffect[F[_]]: minimal monad-like trait for effect abstraction
- SyncEffect: F[X] = X (identity, for sync validators)
- FutureEffect: F = Future (for async validators)
- ValidationLogic: shared collection/map validation, written once

Changes:
- Validator: collection validators now call ValidationLogic
- AsyncValidator: collection validators now call ValidationLogic
- Removed ~70 lines of duplicated traversal/folding logic

Benefits:
- Single source of truth for collection validation
- Security logic (size checks) cannot drift between sync/async
- Foundation ready for future F[_] effect support if needed
@hakimjonas hakimjonas merged commit df7ed2d into main Nov 22, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant