Skip to content

Instantly share code, notes, and snippets.

@keynmol
Last active May 11, 2022 11:44
Show Gist options
  • Save keynmol/4062482b5c06975df1f1b0607d472484 to your computer and use it in GitHub Desktop.
Save keynmol/4062482b5c06975df1f1b0607d472484 to your computer and use it in GitHub Desktop.
Scala 3 nightly bisector

This script does the following:

  1. Downloads the listing of Scala 3 nightly versions from https://repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3/
  2. Parses and sorts them in a really silly way
  3. Runs binary search, invoking scala-cli with a given file and different versions, until it hits the earliest version where the file fails to compile

E.g. if you have a file like this (from scala/scala3#15160):

example.sc

trait Eq[A] {
  def eqv(a1: A, a2: A): Boolean
}

given stringEq: Eq[String] with {
  def eqv(a1: String, a2: String) = a1 == a2
}

abstract class Newtype[Src] {
  opaque type Type = Src

  protected final def derive[F[_]](using ev: F[Src]): F[Type] = ev
}

object Sample extends Newtype[String] {
  given eq: Eq[Type] = derive
}

You can find the earliest known nightly version where this regressed by running the script directly from the gist:

$ scli https://gist.github.com/keynmol/4062482b5c06975df1f1b0607d472484 -- example.sc
Compiling project (Scala 3.1.2, JVM)
Warning: Flag -bootclasspath set repeatedly
Warning: Flag -classpath set repeatedly
Compiled project (Scala 3.1.2, JVM)
[0: 289, mid = 144] Evaluating 3.1.1-RC1-bin-20211006-40a1f44-NIGHTLY✅
[144: 289, mid = 216] Evaluating 3.1.3-RC1-bin-20220208-273ffc9-NIGHTLY❌
[144: 216, mid = 180] Evaluating 3.1.2-RC1-bin-20211210-c3f614b-NIGHTLY❌
[144: 180, mid = 162] Evaluating 3.1.2-RC1-bin-20211029-ad5c714-NIGHTLY✅
[162: 180, mid = 171] Evaluating 3.1.2-RC1-bin-20211123-ca483f8-NIGHTLY❌
[162: 171, mid = 166] Evaluating 3.1.2-RC1-bin-20211114-16f9b22-NIGHTLY❌
[162: 166, mid = 164] Evaluating 3.1.2-RC1-bin-20211102-82172ed-NIGHTLY✅
[164: 166, mid = 165] Evaluating 3.1.2-RC1-bin-20211112-4025951-NIGHTLY❌
3.1.2-RC1-bin-20211102-82172ed-NIGHTLY
//> using lib "com.lihaoyi::requests:0.7.0"
//> using lib "com.lihaoyi::os-lib:0.8.1"
import os.ProcessOutput
enum Version:
case Stable(v: String)
case RC(v: String, of: Stable)
case Nightly(v: String, of: RC)
def stable =
this match
case s: Stable => s.v
case s: RC => s.of.v
case s: Nightly => s.of.of.v
def render: String =
this match
case s: Stable => s.v
case s: RC => s.of.render + "-" + s.v
case s: Nightly => s.of.of.render + "-" + s.of.v + "-" + s.v
def fetch(url: String) =
val cache = os.pwd / ".page.cache"
if cache.toIO.exists() then os.read(cache)
else
val contents = requests.get(url).text()
os.write(cache, contents)
contents
@main def hello(file: String) =
val url = "https://repo1.maven.org/maven2/org/scala-lang/scala3-compiler_3/"
val content = fetch(url)
val rgx = """<a href="(.*?)/"[^>]*>""".r
val versions = rgx.findAllIn(content).matchData.toVector.map { m =>
val version = m.group(1)
val parts = version.split("-", 3).toList
parts match
case stable :: Nil => Version.Stable(stable)
case stable :: rc :: Nil => Version.RC(rc, Version.Stable(stable))
case stable :: rc :: nightly :: Nil =>
Version.Nightly(nightly, Version.RC(rc, Version.Stable(stable)))
case _ => ???
}
import Version.*
val grouped = versions
.groupBy(_.stable)
.mapValues { versions =>
versions
.collect[RC | Nightly] {
case v: RC => v
case v: Nightly => v
}
.groupBy {
case v: RC => v.v
case v: Nightly => v.of.v
}
.toList
.sortBy(_._1)
}
.toList
.sortBy(_._1)
val newVersions = Vector.newBuilder[Version]
grouped.foreach { case (stable, rcsAndNightlies) =>
rcsAndNightlies.map { case (rc, nightlies) =>
nightlies
.collect { case n: Nightly =>
n
}
.sortBy(_.render)
.foreach { n =>
newVersions += n
}
newVersions += RC(rc, Stable(stable))
}
newVersions += Stable(stable)
}
val sortedVersions = newVersions.result().filter(versions.contains)
def compile(version: String) =
os.proc(Seq("scala-cli", "-S", version, file))
.call(
os.pwd,
check = false,
stdout = ProcessOutput((_, _) => ()),
stderr = ProcessOutput((_, _) => ())
)
.exitCode != 0
binSearch(sortedVersions.map(_.render), compile)
.fold(println("Could not deduce the baddie"))(println)
def binSearch[T](a: Vector[T], predicate: T => Boolean): Option[T] =
def go(from: Int, to: Int): Option[T] =
if from == to then Option.when(predicate(a(from)))(a(from))
else if to - from == 1 then Option(a(from))
else if from > to then None
else
val midpoint = from + (to - from) / 2
val element = a(midpoint)
print(s"[$from: $to, mid = $midpoint] Evaluating $element")
val result = predicate(element)
if result then
println("❌")
go(from, midpoint)
else
println("✅")
go(midpoint, to)
go(0, a.length - 1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment