Skip to content

Commit

Permalink
Fix polymorphism boxing
Browse files Browse the repository at this point in the history
Co-authored-by: Marcial Gaißert <github@gaisseml.de>
  • Loading branch information
b-studios and marzipankaiser committed Dec 11, 2023
1 parent 7cb7807 commit b4f856a
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 26 deletions.
1 change: 1 addition & 0 deletions effekt/jvm/src/test/scala/effekt/MLTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class MLTests extends EffektTests {
examplesDir / "casestudies" / "lexer.effekt.md",
examplesDir / "casestudies" / "parser.effekt.md",
examplesDir / "casestudies" / "prettyprinter.effekt.md",
examplesDir / "benchmarks" / "pretty.effekt",
examplesDir / "pos" / "simpleparser.effekt",

// cont
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ abstract class AbstractPolymorphismBoxingTests extends CorePhaseTests(Polymorphi
)
}
class PolymorphismBoxingTests extends AbstractPolymorphismBoxingTests {

test("simple non-polymorphic code should stay the same"){
val code =
"""module main
Expand Down Expand Up @@ -87,7 +86,7 @@ class PolymorphismBoxingTests extends AbstractPolymorphismBoxingTests {
"""module main
|
|def id = { ['A](a: 'A) => return a: 'A }
|def idInt = { (x: Int) => return (id: ['A]('A) => 'A @ {})[BoxedInt]((MkBoxedInt: (Int) => BoxedInt @ {})(x: Int)).unboxInt: Int }
|def idInt = { (x: Int) => return (id: ['A]('A) => 'A @ {})[BoxedInt](make BoxedInt MkBoxedInt(x: Int)).unboxInt: Int }
|""".stripMargin
assertTransformsTo(from, to)
}
Expand All @@ -103,7 +102,7 @@ class PolymorphismBoxingTests extends AbstractPolymorphismBoxingTests {
"""module main
|
|def id = { ['A](a: 'A) => return a: 'A }
|def idInt = { (x: Int) => val tmp = (id: ['A]('A) => 'A @ {})[BoxedInt]((MkBoxedInt: (Int) => BoxedInt @ {})(x: Int)) ; return tmp:BoxedInt.unboxInt: Int }
|def idInt = { (x: Int) => val tmp = (id: ['A]('A) => 'A @ {})[BoxedInt](make BoxedInt MkBoxedInt(x: Int)) ; return tmp:BoxedInt.unboxInt: Int }
|""".stripMargin
assertTransformsTo(from, to)
}
Expand All @@ -127,7 +126,7 @@ class PolymorphismBoxingTests extends AbstractPolymorphismBoxingTests {
|def idInt = { (x: Int) =>
| {
| let res = run {
| let boxedRes = !(id: ['A]('A) => 'A @ {})[BoxedInt]((MkBoxedInt: (Int) => BoxedInt @ {})(x: Int))
| let boxedRes = !(id: ['A]('A) => 'A @ {})[BoxedInt](make BoxedInt MkBoxedInt(x: Int))
| return boxedRes:BoxedInt.unboxInt: Int
| }
| return res: Int
Expand All @@ -151,7 +150,7 @@ class PolymorphismBoxingTests extends AbstractPolymorphismBoxingTests {
| {
| def originalFn = { (x: Int) => return x: Int }
| val result = (originalFn: (Int) => Int @ {})(boxedX: BoxedInt.unboxInt: Int);
| return (MkBoxedInt: (Int) => BoxedInt @ {})(result: Int)
| return make BoxedInt MkBoxedInt(result: Int)
| }
| };
| return r:BoxedInt.unboxInt: Int
Expand Down Expand Up @@ -182,11 +181,11 @@ class PolymorphismBoxingTests extends AbstractPolymorphismBoxingTests {
| (hhofargarg: Int) =>
| {
| def tmp = hhofargB: ('A) => 'A @ {}
| val rres = (tmp: ('A) => 'A @ {})((MkBoxedInt: (Int) => BoxedInt @ {})(hhofargarg: Int));
| val rres = (tmp: ('A) => 'A @ {})(make BoxedInt MkBoxedInt(hhofargarg: Int));
| return rres:BoxedInt.unboxInt: Int
| }
| };
| return (MkBoxedInt: (Int) => BoxedInt @ {})(res:Int)
| return make BoxedInt MkBoxedInt(res:Int)
| }
| };
| return result:BoxedInt.unboxInt: Int
Expand Down
11 changes: 8 additions & 3 deletions effekt/shared/src/main/scala/effekt/core/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ class CoreParsers(positions: Positions, names: Names) extends EffektLexers(posit
None
}

lazy val `run` = keyword("run")
lazy val `;` = super.literal(";")
lazy val `run` = keyword("run")
lazy val `;` = super.literal(";")
lazy val `make` = keyword("make")

/**
* Literals
Expand Down Expand Up @@ -154,6 +155,7 @@ class CoreParsers(positions: Positions, names: Names) extends EffektLexers(posit
( literal
| id ~ (`:` ~> valueType) ^^ Pure.ValueVar.apply
| `box` ~> captures ~ block ^^ { case capt ~ block => Pure.Box(block, capt) }
| `make` ~> dataType ~ id ~ valueArgs ^^ Pure.Make.apply
| block ~ maybeTypeArgs ~ valueArgs ^^ Pure.PureApp.apply
| failure("Expected a pure expression.")
)
Expand Down Expand Up @@ -261,11 +263,14 @@ class CoreParsers(positions: Positions, names: Names) extends EffektLexers(posit

lazy val primValueType: P[ValueType] =
( typeParam ^^ ValueType.Var.apply
| id ~ maybeTypeArgs ^^ ValueType.Data.apply
| dataType
| `(` ~> valueType <~ `)`
| failure("Expected a value type")
)

lazy val dataType: P[ValueType.Data] =
id ~ maybeTypeArgs ^^ { case id ~ targs => ValueType.Data(id, targs) : ValueType.Data }

lazy val blockType: P[BlockType] =
( maybeTypeParams ~ maybeValueTypes ~ many(blockTypeParam) ~ (`=>` ~/> primValueType) ^^ {
case tparams ~ vparams ~ bcparams ~ result =>
Expand Down
29 changes: 23 additions & 6 deletions effekt/shared/src/main/scala/effekt/core/PolymorphismBoxing.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package effekt.core
package effekt
package core

import effekt.PhaseResult.CoreTransformed
import effekt.context.Context
Expand Down Expand Up @@ -28,7 +29,7 @@ object PolymorphismBoxing extends Phase[CoreTransformed, CoreTransformed] {
* @param constructor The constructor to box the values with
* @param field The field to access for unboxing
*/
case class Boxer(tpe: ValueType, constructor: Id, field: Id)
case class Boxer(tpe: ValueType.Data, constructor: Id, field: Id)

/**
* Partial function to describe which values to box and how.
Expand Down Expand Up @@ -205,7 +206,20 @@ object PolymorphismBoxing extends Phase[CoreTransformed, CoreTransformed] {
case Pure.ValueVar(id, annotatedType) => Pure.ValueVar(id, transform(annotatedType))
case Pure.Literal(value, annotatedType) => Pure.Literal(value, transform(annotatedType))
case Pure.PureApp(b, targs, vargs) => instantiate(b, targs).callPure(b, vargs map transform)
case m : Pure.Make => ???
case Pure.Make(data, tag, vargs) =>
val dataDecl = PContext.getDataLikeDeclaration(data.name)
val ctorDecl = dataDecl.constructors.find(_.id == tag).getOrElse {
Context.panic(s"No constructor found for tag ${tag} in data type: ${data}")
}

val argTypes = vargs.map(_.tpe)
val paramTypes = ctorDecl.fields.map(_.tpe)

val coercedArgs = (paramTypes zip (argTypes zip vargs)).map { case (param, (targ, arg)) =>
coercer(targ, param)(transform(arg))
}
Pure.Make(transform(data), tag, coercedArgs)

case Pure.Select(target, field, annotatedType) => {
val (symbol, targs) = target.tpe match {
case ValueType.Data(symbol, targs) => (symbol, targs)
Expand All @@ -230,6 +244,10 @@ object PolymorphismBoxing extends Phase[CoreTransformed, CoreTransformed] {
case ValueType.Boxed(tpe, capt) => ValueType.Boxed(transform(tpe), capt)
}

def transform(valueType: ValueType.Data)(using PContext): ValueType.Data = valueType match {
case ValueType.Data(symbol, targs) => ValueType.Data(symbol, targs map transformArg)
}

def transform(blockType: BlockType)(using PContext): BlockType = blockType match {
case BlockType.Function(tparams, cparams, vparams, bparams, result) =>
BlockType.Function(tparams, cparams, vparams map transform, bparams map transform, transform(result))
Expand Down Expand Up @@ -274,8 +292,7 @@ object PolymorphismBoxing extends Phase[CoreTransformed, CoreTransformed] {

override def apply(t: Pure): Pure = {
val boxer = box(valueType)
val blockTpe = BlockType.Function(List(), List(), List(from), List(), to)
Pure.PureApp(Block.BlockVar(boxer.constructor, blockTpe, Set()), List(), List(t))
Pure.Make(boxer.tpe, boxer.constructor, List(t))
}
}
case class UnboxCoercer(valueType: ValueType)(using PContext) extends Coercer[ValueType, Pure] {
Expand Down Expand Up @@ -352,7 +369,7 @@ object PolymorphismBoxing extends Phase[CoreTransformed, CoreTransformed] {
}

override def callPure(block: B, vargs: List[Pure])(using PContext): Pure = {
rcoercer(Pure.PureApp(block, targs map transformArg, (vcoercers zip vargs).map {case (c,v)=> c(v)}))
rcoercer(Pure.PureApp(block, targs map transformArg, (vcoercers zip vargs).map { case (c,v) => c(v) }))
}

override def callDirect(block: B, vargs: List[Pure], bargs: List[Block])(using PContext): Expr = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ object PrettyPrinter extends ParenPrettyPrinter {
def format(defs: List[Definition]): String =
pretty(toDoc(defs), 60).layout

val show: PartialFunction[Any, String] = {
case m: ModuleDecl => format(m).layout
case d: Definition => format(List(d))
case s: Stmt => format(s)
case x: Id => x.show
}

val emptyline: Doc = line <> line

def toDoc(m: ModuleDecl): Doc = {
Expand Down
14 changes: 5 additions & 9 deletions effekt/shared/src/main/scala/effekt/machine/Transformer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ object Transformer {

def transform(main: CoreTransformed, mainSymbol: TermSymbol)(using C: Context): Program = {
val Some(CoreLifted(_, _, _, liftedMain)) = LiftInference(main) : @unchecked

C.using(module = main.mod) {
transform(mainSymbol, liftedMain);
}
Expand Down Expand Up @@ -394,7 +393,7 @@ object Transformer {
}

case lifted.PureApp(lifted.BlockVar(blockName: symbols.ExternFunction, tpe: lifted.BlockType.Function), targs, args) =>
if(targs.exists(requiresBoxing)){ ErrorReporter.abort(s"Types ${targs} are used as type parameters but would require boxing.") }
if (targs.exists(requiresBoxing)) { ErrorReporter.abort(s"Types ${targs} are used as type parameters but would require boxing.") }

val variable = Variable(freshName("x"), transform(tpe.result))
transform(args).flatMap { values =>
Expand All @@ -403,21 +402,18 @@ object Transformer {
}
}

case lifted.PureApp(lifted.BlockVar(blockName, tpe: lifted.BlockType.Function), targs, args)
if DeclarationContext.findConstructor(blockName).isDefined =>
if(targs.exists(requiresBoxing)){ ErrorReporter.abort(s"Types ${targs} are used as type parameters but would require boxing.") }

val variable = Variable(freshName("x"), transform(tpe.result));
val tag = DeclarationContext.getConstructorTag(blockName)
case lifted.Make(data, constructor, args) =>
val variable = Variable(freshName("x"), transform(data));
val tag = DeclarationContext.getConstructorTag(constructor)

transform(args).flatMap { values =>
Binding { k =>
Construct(variable, tag, values, k(variable))
}
}

case lifted.Select(target, field, tpe)
if DeclarationContext.findField(field).isDefined =>
case lifted.Select(target, field, tpe) if DeclarationContext.findField(field).isDefined =>
// TODO all of this can go away, if we desugar records in the translation to core!
val fields = DeclarationContext.getField(field).constructor.fields
val fieldIndex = fields.indexWhere(_.id == field)
Expand Down
2 changes: 1 addition & 1 deletion effekt/shared/src/main/scala/effekt/util/Debug.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ val showGeneric: PartialFunction[Any, String] = {
}

val show: PartialFunction[Any, String] =
TypePrinter.show orElse core.PrettyPrinter.show orElse showGeneric
TypePrinter.show orElse core.PrettyPrinter.show orElse lifted.PrettyPrinter.show orElse showGeneric

inline def debug[A](inline value: A): A =
${ debugMacros.debugCode('value) }
Expand Down

0 comments on commit b4f856a

Please sign in to comment.