Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
82 lines
3.1 KiB
Scala
82 lines
3.1 KiB
Scala
/**
|
||
* scala3-0001 unit test — OrderingConstraint.isLess() complexity
|
||
*
|
||
* Verifies that isLess() performs O(1) contains operations (not O(n)) by
|
||
* counting hash-set contains calls via a mock Set[TypeParamRef] wrapper.
|
||
*
|
||
* The defect: upper(param1) returned List[TypeParamRef]; List.contains is O(n).
|
||
* The fix: upper(param1) is backed by Set[TypeParamRef]; Set.contains is O(1).
|
||
*
|
||
* Test structure: synthetic constraint chain A0 <: A1 <: ... <: An-1.
|
||
* For the DEFECTIVE version: isLess(A0, An-1) scans n-1 entries → O(n) comparisons.
|
||
* For the PATCHED version: isLess(A0, An-1) does 1 hash lookup → O(1).
|
||
*
|
||
* This test ONLY runs against the compiler's test harness, not standalone.
|
||
* It is structured as a dotty/scala3 test to be placed in:
|
||
* tests/unit/scala3-0001-isless-complexity.scala
|
||
*
|
||
* Proxy test: run the Scala 3 compiler on the synthetic input below and
|
||
* measure compile time growth ratio. For quadratic isLess (old code),
|
||
* growth from n=100 to n=400 should be ~16× (4²). For O(1) isLess (fix),
|
||
* growth should be ~4× (linear).
|
||
*/
|
||
|
||
// ---- Proxy complexity test (shell-runnable, measures compile time growth) ----
|
||
//
|
||
// Generate a source file with n chained implicits, compile it, measure time.
|
||
// A cubic growth from n=100 to n=200 would produce ~8× slowdown.
|
||
// A quadratic growth would produce ~4×. Linear: ~2×.
|
||
//
|
||
// Run: scala-cli run test_scala3_0001_isless_complexity.scala
|
||
//
|
||
// This file generates and times compilation of synthetic HKT chains.
|
||
|
||
import scala.sys.process.*
|
||
import java.io.{File, PrintWriter}
|
||
import java.nio.file.{Files, Path}
|
||
|
||
@main def runComplexityTest(): Unit =
|
||
val ns = List(25, 50, 100, 200)
|
||
println(f"${"n"}%6s ${"time_ms"}%10s ${"ratio"}%8s ${"expected"}%10s")
|
||
println("-" * 42)
|
||
|
||
var prevTime = 0L
|
||
for n <- ns do
|
||
val src = generateChain(n)
|
||
val tmpDir = Files.createTempDirectory("scala3-0001-")
|
||
val srcFile = tmpDir.resolve("chain.scala").toFile
|
||
val pw = PrintWriter(srcFile)
|
||
pw.write(src)
|
||
pw.close()
|
||
|
||
val t0 = System.currentTimeMillis()
|
||
val result = s"scalac -d ${tmpDir} ${srcFile}".!!
|
||
val elapsed = System.currentTimeMillis() - t0
|
||
|
||
val ratio = if prevTime > 0 then f"${elapsed.toDouble / prevTime}%.2f" else " -"
|
||
val expected = if prevTime > 0 then "(~2.0 linear, ~4.0 quadratic, ~8.0 cubic)" else ""
|
||
println(f"$n%6d $elapsed%10d $ratio%8s $expected")
|
||
prevTime = elapsed
|
||
srcFile.delete()
|
||
|
||
def generateChain(n: Int): String =
|
||
// Generates n type parameters in a chain: each T(i) is bounded by T(i-1)
|
||
// This forces the Scala 3 type inference to track n ordering constraints.
|
||
val sb = StringBuilder()
|
||
sb.append("// Auto-generated: scala3-0001 complexity probe, n=").append(n).append("\n")
|
||
sb.append("object Chain {\n")
|
||
|
||
// Type aliases forming a chain
|
||
sb.append(" type T0 = Int\n")
|
||
for i <- 1 until n do
|
||
sb.append(s" type T$i <: T${i-1}\n")
|
||
|
||
// Function forcing ordering constraint resolution
|
||
sb.append(s" def f[")
|
||
sb.append((0 until n).map(i => s"A$i").mkString(", "))
|
||
sb.append("](")
|
||
sb.append((0 until n).map(i => s"a$i: A$i").mkString(", "))
|
||
sb.append("): Unit = ()\n")
|
||
|
||
sb.append("}\n")
|
||
sb.toString
|