8000 `docs` module with named tuple pretty-printing by arainko · Pull Request #3 · arainko/chanterelle · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 0 additions & 36 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,39 +155,3 @@ jobs:
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SONATYPE_CREDENTIAL_HOST: ${{ secrets.SONATYPE_CREDENTIAL_HOST }}
run: sbt --client 'tlCiRelease'

dependency-submission:
name: Submit Dependencies
if: github.event.repository.fork == false && github.event_name != 'pull_request'
strategy:
matrix:
os: [ubuntu-22.04]
java: [temurin@17]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout current branch (full)
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup sbt
uses: sbt/setup-sbt@v1

- name: Setup Java (temurin@17)
id: setup-java-temurin-17
if: matrix.java == 'temurin@17'
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
cache: sbt

- name: sbt update
if: matrix.java == 'temurin@17' && steps.setup-java-temurin-17.outputs.cache-hit == 'false'
run: sbt --client '+update'

- name: Submit Dependencies
uses: scalacenter/sbt-dependency-submission@v2
with:
modules-ignore: root_3
configs-ignore: test scala-tool scala-doc-tool test-internal
12 changes: 10 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ ThisBuild / githubWorkflowUseSbtThinClient := true
ThisBuild / semanticdbEnabled := true
ThisBuild / semanticdbVersion := scalafixSemanticdb.revision
ThisBuild / tlVersionIntroduced := Map("3" -> "0.0.0")
ThisBuild / tlCiDependencyGraphJob := false

lazy val root =
project
Expand All @@ -32,13 +33,20 @@ lazy val chanterelle =
project
.in(file("chanterelle"))
.settings(
scalaVersion := "3.7.1",
scalacOptions ++= Seq(
"-preview",
"-Wunused:all",
"-WunstableInlineAccessors",
"-Xcheck-macros",
"-Wconf:msg=(infix named):s" // TODO: report errors reported without this to dotty (when adding stuff with '+' and the -> syntax into a SortedMap)
),
libraryDependencies += "org.scalameta" %% "munit" % "1.1.1" % Test
libraryDependencies ++= Seq(
"org.scalameta" %% "munit" % "1.1.1" % Test
)
)

lazy val docs =
project
.in(file("docs"))
.enablePlugins(NoPublishPlugin)
.dependsOn(chanterelle)
39 changes: 30 additions & 9 deletions chanterelle/src/main/scala/chanterelle/internal/Debug.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import scala.reflect.ClassTag
private[chanterelle] trait Debug[-A] {
def astify(self: A)(using Quotes): Debug.AST

extension (self: A) final def show(using Quotes): String = astify(self).dropEmpty.show
extension (self: A) final def show(using Quotes): String = astify(self).dropEmpty.show(using Debug.UseColors.Yes)
}

@scala.annotation.publicInBinary
Expand Down Expand Up @@ -111,11 +111,12 @@ private[chanterelle] object Debug extends LowPriorityDebug {
}
}

private[chanterelle] class ForProduct[A](tpeName: String, _instances: => IArray[Debug[Any]]) extends Debug[A] {
private[chanterelle] class ForProduct[A](tpeName: String, fieldNames: List[String], _instances: => IArray[Debug[Any]])
extends Debug[A] {
private lazy val instances = _instances
def astify(self: A)(using Quotes): AST = {
val prod = self.asInstanceOf[scala.Product]
val fields = prod.productElementNames
val fields = fieldNames
.zip(instances)
.zip(prod.productIterator)
.map {
Expand All @@ -129,9 +130,16 @@ private[chanterelle] object Debug extends LowPriorityDebug {
}

private inline def product[A](using A: Mirror.ProductOf[A]): Debug[A] = {
val tpeName = constValue[A.MirroredLabel].toString
val tpeName =
inline erasedValue[A] match {
case _: NamedTuple.AnyNamedTuple => ""
case _ => constValue[A.MirroredLabel].toString
}
val fieldNames = inline constValueTuple[A.MirroredElemLabels].toList match {
case fields: List[String] => fields
}
def instances = summonAll[Tuple.Map[A.MirroredElemTypes, Debug]].toIArray.map(_.asInstanceOf[Debug[Any]])
ForProduct(tpeName, instances)
ForProduct(tpeName, fieldNames, instances)
}

private[chanterelle] class ForCoproduct[A](instances: Vector[Debug[Any]])(using A: Mirror.SumOf[A]) extends Debug[A] {
Expand Down Expand Up @@ -171,7 +179,7 @@ private[chanterelle] object Debug extends LowPriorityDebug {
case Product(name, fields) => name.length + fields.map((name, ast) => name.length + ast.length).sum
case Collection(name, values) => name.length + values.map(_.length).sum

final def show: String = {
final def show(using UseColors): String = {
def ident(n: Int) = " " * n

// if you think this is over then you're wrong
Expand Down Expand Up @@ -206,14 +214,27 @@ private[chanterelle] object Debug extends LowPriorityDebug {
recurse(this, 0)
}

extension (self: String) {
private def bold: String = s"${Console.BOLD}$self${Console.RESET}"
private def yellow: String = s"${Console.YELLOW}$self${Console.RESET}"
extension (self: String)(using useColors: UseColors) {
private def bold: String = {
if useColors == UseColors.Yes then s"${Console.BOLD}$self${Console.RESET}"
else self
}
private def yellow: String =
if useColors == UseColors.Yes then s"${Console.YELLOW}$self${Console.RESET}"
else self
}

}

enum UseColors {
case Yes, No
}
}

private[chanterelle] transparent trait LowPriorityDebug {
inline given namedTuple[A <: NamedTuple.AnyNamedTuple](using Mirror.ProductOf[A]): Debug[A] =
Debug.derived[A]

given Debug[Nothing => Any] = Debug.nonShowable

given intOrString: Debug[Int | String] with {
Expand Down
4 changes: 3 additions & 1 deletion chanterelle/src/main/scala/chanterelle/syntax.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import scala.NamedTuple.*
extension [Tup <: AnyNamedTuple](self: Tup) {

/**
* Transforms a named tuple with one of the supported modifiers (see [[chanterelle.hidden.TupledModifier.Builder]])
* Transforms a named tuple with one of the supported modifiers
*
* {{{
* val value = (field1 = 1, nestedField = (field = 2, optionalField = Some(3)))
Expand All @@ -21,6 +21,8 @@ extension [Tup <: AnyNamedTuple](self: Tup) {
* // evaluates to: (nestedField = (field = 2, optionalField = Some(4), newField = 4))
* // and is typed as: (nestedField : (field : Int, optionalField : Option[Int], newField : Int))
* }}}
*
* @see chanterelle.hidden.TupledModifier.Builder
*/
transparent inline def transform(inline modifications: TupleModifier.Builder[Tup] => TupleModifier[Tup]*) =
EntryPoint.run[Tup](self, modifications*)
Expand Down
1 change: 0 additions & 1 deletion chanterelle/src/test/scala/chanterelle/LoggerSpec.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package chanterelle.internal

import munit.FunSuite

class LoggerSpec extends chanterelle.ChanterelleSuite {
test("Logger is off") {
assertEquals(Logger.level, Logger.Level.Off)
Expand Down
1 change: 0 additions & 1 deletion chanterelle/src/test/scala/chanterelle/ModifiersSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -171,5 +171,4 @@ class ModifiersSpec extends ChanterelleSuite {
"""
}("""Couldn't parse '_ => (field = "not really")' as a valid path selector""")
}: @nowarn

}
118 changes: 118 additions & 0 deletions docs/src/main/scala/chanterelle/internal/Show.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package chanterelle.internal

import scala.collection.immutable.VectorMap
import scala.compiletime.*
import scala.deriving.Mirror
import scala.quoted.*
import scala.reflect.ClassTag

trait Show[-A] {
def astify(self: A): Debug.AST

extension (self: A) final def show: String = astify(self).dropEmpty.show(using Debug.UseColors.No)
}

object Show extends LowPriorityShow {
import Debug.AST.*
import Debug.AST

def show[A: Show](value: A) = value.show

inline given namedTuple[A <: NamedTuple.AnyNamedTuple](using Mirror.ProductOf[A]): Show[A] =
Show.derived[A]

given string: Show[String] with {
override def astify(self: String): AST = Text(s""""${self}"""")
}

given int: Show[Int] with {
override def astify(self: Int): AST = Text(self.toString)
}

given bool: Show[Boolean] with {
override def astify(self: Boolean): AST = Text(self.toString)
}

given collection[A: Show as A, Coll[a] <: Iterable[a]](using tag: ClassTag[Coll[A]]): Show[Coll[A]] with {
def astify(self: Coll[A]): AST = {
val name = tag.runtimeClass.getSimpleName()
Collection(name, self.map(A.astify).toVector)
}
}

given map[K: Show as K, V: Show as V]: Show[Map[K, V]] with {

def astify(self: Map[K, V]): AST = {
Collection(
"Map",
self
.map((key, value) => Product("Entry", VectorMap("key" -> K.astify(key), "value" -> V.astify(value))))
.toVector
)
}
}

given option[A: Show as A]: Show[Option[A]] with {
def astify(self: Option[A]): AST =
self match
case None => Text("None")
case Some(value) => Collection("Some", Vector(A.astify(value)))
}

inline def derived[A: Mirror.Of as A]: Show[A] =
inline A match {
case given Mirror.ProductOf[A] => product
case given Mirror.SumOf[A] => coproduct
}

private[chanterelle] class ForProduct[A](tpeName: String, fieldNames: List[String], _instances: => IArray[Show[Any]])
extends Show[A] {
private lazy val instances = _instances
def astify(self: A): AST = {
val prod = self.asInstanceOf[scala.Product]
val fields = fieldNames
.zip(instances)
.zip(prod.productIterator)
.map {
case label -> show -> value =>
label -> show.astify(value)
}
.to(VectorMap)

Product(tpeName, fields)
}
}

private inline def product[A: Mirror.ProductOf as A]: Show[A] = {
val tpeName =
inline erasedValue[A] match {
case _: NamedTuple.AnyNamedTuple => ""
case _: Tuple => ""
case _ => constValue[A.MirroredLabel].toString
}
val fieldNames = inline constValueTuple[A.MirroredElemLabels].toList match { case fields: List[String] => fields }
def instances = summonAll[Tuple.Map[A.MirroredElemTypes, Show]].toIArray.map(_.asInstanceOf[Show[Any]])
ForProduct(tpeName, fieldNames, instances)
}

private[chanterelle] class ForCoproduct[A: Mirror.SumOf as A](instances: Vector[Show[Any]]) extends Show[A] {
def astify(self: A): AST = {
val ordinal = A.ordinal(self)
instances(ordinal).astify(self)
}
}

private inline def coproduct[A: Mirror.SumOf as A]: Show[A] = ForCoproduct(deriveForAll[A.MirroredElemTypes].toVector)

private inline def deriveForAll[Tup <: Tuple]: List[Show[Any]] =
inline erasedValue[Tup] match {
case _: (head *: tail) =>
// TODO: Doesn't take into account existing instances, getting stack overflows when trying to do that for some reason
derived[head](using summonInline[Mirror.Of[head]]).asInstanceOf[Show[Any]] :: deriveForAll[tail]
case _: EmptyTuple => Nil
}
}

private[chanterelle] transparent trait LowPriorityShow {
inline given tuple[A <: Tuple](using Mirror.ProductOf[A]): Show[A] = Show.derived[A]
}
0