Skip to content

Commit

Permalink
Merge pull request #48 from KacperFKorban/undecidable-generics
Browse files Browse the repository at this point in the history
Undecidable generics
  • Loading branch information
KacperFKorban authored Apr 15, 2024
2 parents 936f9e6 + 25cbb50 commit b6736ba
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 16 deletions.
39 changes: 34 additions & 5 deletions guinep/src/main/scala/macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ private[guinep] object macros {
val form = Form(inputs, usedFormDecls)
Expr(form)

private val tpeGenericFallback = TypeRepr.of[String]

private def appliedChild(childSym: Symbol, parentSym: Symbol, parentArgs: List[TypeRepr]): TypeRepr = childSym.tree match {
case classDef @ ClassDef(_, _, parents, _, _) =>
parents
Expand All @@ -209,21 +211,48 @@ private[guinep] object macros {
.collectFirst {
case AppliedType(tpe, args) if tpe.typeSymbol == parentSym => args
case tpe if tpe.typeSymbol == parentSym => Nil
}.match
}.match {
case None =>
report.errorAndAbort(s"""PANIC: Could not find applied parent for ${childSym.name}, parents: ${parents.map(_.show).mkString(",")}""", classDef.pos)
case Some(parentExtendsArgs) =>
val childDefArgs = classDef.symbol.primaryConstructor.paramSymss.flatten.filter(_.isTypeParam).map(_.typeRef)
val childArgTpes = childDefArgs.map { arg =>
arg.substituteTypes(parentExtendsArgs.map(_.typeSymbol), parentArgs)
// We want to traverse parentExtendsArgs and parentArgs together and create a map of mappings from elements of childDefArgs to their counterparts
// We also need a method of merging two types when they are mapped twice e.g. `Child[T] <: Parent[List[T], T => ()]` and `Parent[List[ScalaNumber], Int => ()]`
// this might map to some king of type bounds (and will also depend on the variance of Parent)
// For every element form childDefArgs, that doesn't have a found counterpart, we want to give it a default type (appropriate bound based on variance or String?)
// Also, some cases might not be reachable for a given parentArgs e.g. `Child <: Parent[Int]` and `Parent[String]`
val childDefArgsSymbols = classDef.symbol.primaryConstructor.paramSymss.flatten.filter(_.isTypeParam)
val childParamsMap = collectMappingsFromAppliedParent(childDefArgsSymbols, parentExtendsArgs, parentArgs)
val childArgTpes = childDefArgsSymbols.map { arg =>
childParamsMap.getOrElse(arg, tpeGenericFallback)
}
// TODO(kπ) might want to handle the case when there are unsubstituted type parameters left
val childTpe = childSym.typeRef.appliedTo(childArgTpes)
childTpe
}
case _ =>
childSym.typeRef
}

// TODO(kπ) this is still missing support for a lot of things (e.g. see comment in @appliedChild)
private def collectMappingsFromAppliedParent(
childDefArgs: List[Symbol],
parentExtendsArgs: List[TypeRepr],
parentArgs: List[TypeRepr]
): Map[Symbol, TypeRepr] = {
val mappings = mutable.Map.empty[Symbol, TypeRepr]
def go(parentExtendsArg: TypeRepr, parentArg: TypeRepr): Unit = (parentExtendsArg, parentArg) match {
case (tpe1: TypeRef, tpe2) if childDefArgs.contains(tpe1.typeSymbol) =>
mappings.update(tpe1.typeSymbol, tpe2)
case (AppliedType(tpe1, args1), AppliedType(tpe2, args2)) if childDefArgs.contains(tpe1.typeSymbol) && args1.length == args2.length =>
mappings.update(tpe1.typeSymbol, tpe2)
args1.zip(args2).foreach { case (arg1, arg2) => go(arg1, arg2) }
case (AppliedType(tpe1, args1), AppliedType(tpe2, args2)) if tpe1.typeSymbol == tpe2.typeSymbol =>
args1.zip(args2).foreach { case (arg1, arg2) => go(arg1, arg2) }
case _ =>
}
parentExtendsArgs.zip(parentArgs).foreach { case (arg1, arg2) => go(arg1, arg2) }
mappings.toMap
}

private case class ConstrEntry(definition: Option[Statement], ref: Term)
private case class ConstrContext(constrMap: mutable.Map[String, ConstrEntry])
private def constrCtx(using ConstrContext) = summon[ConstrContext]
Expand Down
33 changes: 33 additions & 0 deletions guinep/src/test/scala/formgentests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,39 @@ class FormGenTests extends munit.FunSuite {
)
)

checkGeneratedFormEquals(
"printsWeirdGADT",
printsWeirdGADT,
Form(
Seq(
FormElement.Dropdown(
"g",
List(
"SomeValue" -> FormElement.FieldSet("value", List(FormElement.TextInput("value"))),
"SomeOtherValue" -> FormElement.FieldSet(
"value",
List(FormElement.NamedRef("value", "java.lang.String"), FormElement.NamedRef("value2", "java.lang.String"))
)
)
)
),
Map(
"java.lang.String" -> FormElement.TextInput("value")
)
)
)

checkGeneratedFormEquals(
"genericShow[String]",
genericShow[String],
Form(
Seq(
FormElement.TextInput("a")
),
Map.empty
)
)

// TODO(kπ) Add test for WeirdGADT

// TODO(kπ) Add test for isInTreeExt
Expand Down
21 changes: 21 additions & 0 deletions guinep/src/test/scala/rungentests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,27 @@ class RunGenTests extends munit.FunSuite {
"false"
)

checkGeneratedRunResultEquals(
"printsWeirdGADT",
printsWeirdGADT,
List(Map("name" -> "SomeValue", "value" -> Map("value" -> "hello"))),
"SomeValue(hello)"
)

checkGeneratedRunResultEquals(
"printsWeirdGADT",
printsWeirdGADT,
List(Map("name" -> "SomeOtherValue", "value" -> Map("value" -> "hello", "value2" -> "world"))),
"SomeOtherValue(hello, world)"
)

checkGeneratedRunResultEquals(
"genericShow[String]",
genericShow[String],
List("hello"),
"hello"
)

// TODO(kπ) Add test for WeirdGADT

// TODO(kπ) Add test for isInTreeExt
Expand Down
13 changes: 7 additions & 6 deletions guinep/src/test/scala/testsdata.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,6 @@ object TestsData {
def roll6(): Int =
scala.util.Random.nextInt(6) + 1

sealed trait WeirdGADT[+A]
case class IntValue(value: Int) extends WeirdGADT[Int]
case class SomeValue[+A](value: A) extends WeirdGADT[A]
case class SomeOtherValue[+A, +B](value: A, value2: B) extends WeirdGADT[A]

def concatAll(elems: List[String]): String =
elems.mkString

Expand Down Expand Up @@ -103,11 +98,17 @@ object TestsData {
case IntTree.Node(left, value, right) =>
value == elem || isInTree(elem, left) || isInTree(elem, right)

// This fails on unknown type params
sealed trait WeirdGADT[+A]
case class SomeValue[+A](value: A) extends WeirdGADT[A]
case class SomeOtherValue[+A, +B](value: A, value2: B) extends WeirdGADT[A]

def printsWeirdGADT(g: WeirdGADT[String]): String = g match
case SomeValue(value) => s"SomeValue($value)"
case SomeOtherValue(value, value2) => s"SomeOtherValue($value, $value2)"

def genericShow[A](a: A): String =
a.toString

// Can't be handled right now
extension (elem: Int)
def isInTreeExt(tree: IntTree): Boolean = tree match
Expand Down
19 changes: 16 additions & 3 deletions testcases/src/main/scala/main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,9 @@ def roll6(): Int =
scala.util.Random.nextInt(6) + 1

sealed trait WeirdGADT[+A]
case class IntValue(value: Int) extends WeirdGADT[Int]
case class SomeValue[+A](value: A) extends WeirdGADT[A]
case class SomeOtherValue[+A, +B](value: A, value2: B) extends WeirdGADT[A]

// This fails on unknown type params
def printsWeirdGADT(g: WeirdGADT[String]): String = g match
case SomeValue(value) => s"SomeValue($value)"
case SomeOtherValue(value, value2) => s"SomeOtherValue($value, $value2)"
Expand Down Expand Up @@ -119,6 +117,20 @@ case class TakesUnit(unit: Unit)
def runTakesUnit(takesUnit: TakesUnit): Unit =
()

def genericShow[A](a: A): String =
a.toString

sealed trait WeirdGADT1[+A]
case class IntValue1(value: Int) extends WeirdGADT1[Int]
case class SomeValue1[+A](value: A) extends WeirdGADT1[A]
case class SomeOtherValue1[+A, +B](value: A, value2: B) extends WeirdGADT1[A]
case class SomeListValue1[+A](value: List[A]) extends WeirdGADT1[List[A]]

def printsWeirdGADT1(g: WeirdGADT1[Int]): String = g match
case IntValue1(value) => s"IntValue1($value)"
case SomeValue1(value) => s"SomeValue($value)"
case SomeOtherValue1(value, value2) => s"SomeOtherValue($value, $value2)"

@main
def run: Unit =
guinep.web
Expand Down Expand Up @@ -147,7 +159,8 @@ def run: Unit =
inverseBigDecimal,
sayBye,
runTakesUnit,
printsWeirdGADT,
genericShow[String],
// isInTreeExt
// addManyParamLists
// printsWeirdGADT
)
6 changes: 4 additions & 2 deletions web/src/main/scala/htmlgen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ private[guinep] trait HtmlGen {
"""
body { font-family: Arial, sans-serif; }
.sidebar { position: fixed; left: 0; top: 0; width: 200px; height: 100vh; background-color: #f0f0f0; padding: 20px; overflow-y: auto; }
.sidebar a { display: block; padding: 10px; margin-bottom: 10px; background-color: #007bff; color: white; text-decoration: none; text-align: center; border-radius: 5px; }
.sidebar a { display: block; padding: 10px; margin-bottom: 10px; background-color: #007bff; color: white; text-decoration: none; text-align: center; border-radius: 5px; overflow-x: hidden; text-overflow: ellipsis; }
.sidebar a:hover { background-color: #0056b3; }
.main-content { margin-left: 232px; padding: 40px; display: flex; justify-content: center; padding-top: 20px; }
.form-container { width: 600px; }
Expand Down Expand Up @@ -196,7 +196,9 @@ private[guinep] trait HtmlGen {
const input = document.createElement('input');
input.type = formElem.type;
input.name = formElem.name;
input.value = formElem.value;
if (formElem.value) {
input.value = formElem.value;
}
input.id = formElem.name;
input.placeholder = formElem.name;
if (formElem.nullable) {
Expand Down

0 comments on commit b6736ba

Please sign in to comment.