The SemanticLazyValComparator provides version-agnostic comparison of lazy val implementations. It answers the question: "Are the lazy val implementations identical?"
This comparison ignores all bytecode differences except lazy val implementation patterns, making it ideal for:
- Comparing the same source compiled with different Scala versions
- Detecting when lazy val implementations have changed
- Verifying transformation correctness
-
Same source + Scala 3.3 vs 3.7
- Both use object-based implementation with Unsafe
- Even if other bytecode differs (e.g., implicit resolution), lazy vals are identical
- Result: ✓ IDENTICAL
-
Same source + Scala 3.0 vs 3.1
- Both use bitmap-based inline implementation
- Result: ✓ IDENTICAL
-
No lazy vals in either class
- Trivially identical
- Result: ✓ IDENTICAL
-
Same source + Scala 3.2 vs 3.3
- 3.2 uses bitmap-based inline
- 3.3 uses object-based with lzyINIT
- Result: ✗ DIFFERENT
-
Same source + Scala 3.7 vs 3.8
- 3.7 uses Unsafe (objCAS)
- 3.8 uses VarHandle (compareAndSet)
- Result: ✗ DIFFERENT
-
Different lazy vals defined
- One class has lazy vals that the other doesn't
- Result: ✗ DIFFERENT
import sloth.classfile.{ClassfileParser, ClassInfo}
import sloth.lazyval.{SemanticLazyValComparator, LazyValFormatter}
// Parse two classfiles (same source, different Scala versions)
val bytes1: Array[Byte] = ??? // From Scala 3.3
val bytes2: Array[Byte] = ??? // From Scala 3.7
val class1 = ClassfileParser.parseClassfile(bytes1).toOption.get
val class2 = ClassfileParser.parseClassfile(bytes2).toOption.get
// Compare semantically
val result = SemanticLazyValComparator.compare(class1, class2)
// Check if identical
if result.areIdentical then
println("Lazy val implementations are identical!")
else
println("Lazy val implementations differ")
// Format for display
println(LazyValFormatter.formatSemanticComparisonResult(result))Sealed trait with the following cases:
-
Identical- All lazy val implementations match
areIdentical: Boolean = true
-
Different(reasons: Seq[String])- Lazy vals differ with specific reasons
areIdentical: Boolean = false
-
BothNoLazyVals- Neither class has lazy vals (trivially identical)
areIdentical: Boolean = true
-
OnlyOneHasLazyVals(firstHas: Boolean, count: Int)- Only one class has lazy vals
areIdentical: Boolean = false
def compare(class1: ClassInfo, class2: ClassInfo): SemanticLazyValComparisonResultCompares lazy val implementations by:
- Detecting lazy vals in both classes
- Matching them by name (primary identity)
- Extracting canonical pattern signatures
- Comparing patterns semantically
- Returning simple identical/different result
The comparator normalizes lazy vals into canonical patterns based on detected version:
-
BitmapBased (3.0-3.2)
- Has offset field (OFFSET$m)
- Has bitmap field (bitmap$)
- Storage type (primitive or object)
- No init method (inline implementation)
-
ObjectBasedUnsafe (3.3-3.7)
- Has offset field (OFFSET$m)
- Storage type (Object)
- Has init method (lzyINIT)
- Instruction counts for init and accessor
-
ObjectBasedVarHandle (3.8+)
- Has VarHandle field ($lzy$lzyHandle)
- Storage type (Object)
- Has init method (lzyINIT)
- Instruction counts for init and accessor
LazyValFormatter.formatSemanticComparisonResult(result)Example outputs:
Identical:
✓ Lazy val implementations are IDENTICAL
Different:
✗ Lazy val implementations DIFFER (2 reason(s)):
• Lazy val 'simpleLazy': different implementation versions (ObjectBasedUnsafe vs ObjectBasedVarHandle)
• Lazy val 'anotherLazy': storage type differs (I vs Ljava/lang/Object;)
LazyValFormatter.formatSemanticComparisonSummary(result)Examples:
"Identical""Identical (no lazy vals)""Different (2 reasons)"
| Feature | ClassfileComparator | SemanticLazyValComparator |
|---|---|---|
| Scope | Entire class | Lazy vals only |
| Granularity | All fields/methods | Lazy val patterns |
| Version-aware | No | Yes |
| Use case | General diff | Cross-version lazy val comparison |
| Result | Detailed differences | Simple identical/different |
// Same source: lazy val x: Int = 42
val class33 = parseClassfile(compiledWith33)
val class37 = parseClassfile(compiledWith37)
val result = SemanticLazyValComparator.compare(class33, class37)
// result: Identical
// Both use ObjectBasedUnsafe pattern// Same source: lazy val x: Int = 42
val class37 = parseClassfile(compiledWith37)
val class38 = parseClassfile(compiledWith38)
val result = SemanticLazyValComparator.compare(class37, class38)
// result: Different(Seq("Lazy val 'x': different implementation versions..."))
// 3.7 uses Unsafe, 3.8 uses VarHandle// class1: lazy val x: Int = 42
// class2: lazy val y: String = "hello"
val result = SemanticLazyValComparator.compare(class1, class2)
// result: Different(Seq("Lazy vals only in first: x", "Lazy vals only in second: y"))The semantic comparator integrates with:
LazyValDetector: For detecting lazy vals in classesLazyValFormatter: For formatting resultsClassfileParser: For parsing bytecode
It provides the foundation for:
- Bytecode transformation validation
- Cross-version compatibility testing
- Lazy val migration tooling