Skip to content

Commit

Permalink
Improve duplicate imports (#1196)
Browse files Browse the repository at this point in the history
  • Loading branch information
johnynek committed May 11, 2024
1 parent 2a943bd commit 99afa56
Show file tree
Hide file tree
Showing 18 changed files with 457 additions and 237 deletions.
19 changes: 12 additions & 7 deletions cli/src/main/scala/org/bykn/bosatsu/TypedExprToProto.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1552,18 +1552,17 @@ object ProtoConverter {
// the Int is in index in the list of definedTypes:
val allDts
: SortedMap[(PackageName, TypeName), (DefinedType[Kind.Arg], Int)] =
cpack.program.types.definedTypes.mapWithIndex((dt, idx) => (dt, idx))
cpack.types.definedTypes.mapWithIndex((dt, idx) => (dt, idx))
val dtVect: Vector[DefinedType[Kind.Arg]] =
allDts.values.iterator.map(_._1).toVector
val tab =
for {
nmId <- getId(cpack.name.asString)
imps <- cpack.imports.traverse(importToProto(allDts, _))
exps <- cpack.exports.traverse(expNameToProto(allDts, _))
prog = cpack.program
lets <- prog.lets.traverse(letToProto)
exdefs <- prog.externalDefs.traverse { nm =>
extDefToProto(nm, prog.types.getValue(cpack.name, nm))
lets <- cpack.lets.traverse(letToProto)
exdefs <- cpack.externalDefs.traverse { nm =>
extDefToProto(nm, cpack.types.getValue(cpack.name, nm))
}
dts <- dtVect.traverse(definedTypeToProto)
} yield { (ss: SerState) =>
Expand Down Expand Up @@ -1774,7 +1773,7 @@ object ProtoConverter {
case Left((_, dt)) =>
dt.toDefinedType(tc)
case Right(comp) =>
comp.program.types.toDefinedType(tc)
comp.types.toDefinedType(tc)
}

res.flatMap {
Expand Down Expand Up @@ -1816,13 +1815,19 @@ object ProtoConverter {
imps <- pack.imports.toList.traverse(
importsFromProto(_, loadIface, loadDT)
)
impMap <- ReaderT.liftF(
ImportMap.fromImports(imps)((_, _) => ImportMap.Unify.Error) match {
case (Nil, im) => Success(im)
case (nel, _) =>
Failure(new Exception(s"duplicated imports in package: $nel"))
})
exps <- pack.exports.toList.traverse(
exportedNameFromProto(loadDT, _)
)
lets <- pack.lets.toList.traverse(letsFromProto)
eds <- pack.externalDefs.toList.traverse(externalDefsFromProto)
prog <- buildProgram(packageName, lets, eds)
} yield Package(packageName, imps, exps, prog)
} yield Package(packageName, imps, exps, (prog, impMap))

// build up the decoding state by decoding the tables in order
val tab1 = Scoped.run(
Expand Down
6 changes: 3 additions & 3 deletions cli/src/test/scala/org/bykn/bosatsu/TestProtoType.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,20 @@ class TestProtoType extends AnyFunSuite with ParTest {
.map(_._2)
.getOrElse(0)

val context = 100
val context = 1000
assert(
Eq[A].eqv(a, orig),
s"${a.toString.drop(diffIdx - context / 2).take(context)} != ${orig.toString.drop(diffIdx - context / 2).take(context)}"
)
// assert(Eq[A].eqv(a, orig), s"$a\n\n!=\n\n$orig")
//assert(Eq[A].eqv(a, orig), s"$a\n\n!=\n\n$orig")
}

def testWithTempFile(fn: Path => IO[Unit]): Unit = {
val tempRes = Resource.make(IO {
val f = File.createTempFile("proto_test", ".proto")
f.toPath
}) { path =>
IO {
IO.blocking {
val _ = path.toFile.delete
()
}
Expand Down
12 changes: 6 additions & 6 deletions core/src/main/scala/org/bykn/bosatsu/Evaluation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ case class Evaluation[T](pm: PackageMap.Typed[T], externals: Externals) {
MMap.empty

private def externalEnv(p: Package.Typed[T]): Map[Identifier, Eval[Value]] = {
val externalNames = p.program.externalDefs
val externalNames = p.externalDefs
externalNames.iterator.map { n =>
val tpe = p.program.types.getValue(p.name, n) match {
val tpe = p.types.getValue(p.name, n) match {
case Some(t) => t
case None =>
// $COVERAGE-OFF$
Expand Down Expand Up @@ -70,7 +70,7 @@ case class Evaluation[T](pm: PackageMap.Typed[T], externals: Externals) {
envCache.getOrElseUpdate(
packName, {
val pack = pm.toMap(packName)
externalEnv(pack) ++ evalLets(packName, pack.program.lets)
externalEnv(pack) ++ evalLets(packName, pack.lets)
}
)

Expand All @@ -81,7 +81,7 @@ case class Evaluation[T](pm: PackageMap.Typed[T], externals: Externals) {
): Option[(Eval[Value], Type)] =
for {
pack <- pm.toMap.get(p)
(_, _, tpe) <- pack.program.lets.filter { case (n, _, _) =>
(_, _, tpe) <- pack.lets.filter { case (n, _, _) =>
n == name
}.lastOption
value <- evaluate(p).get(name)
Expand Down Expand Up @@ -133,7 +133,7 @@ case class Evaluation[T](pm: PackageMap.Typed[T], externals: Externals) {
val valueToJson: ValueToJson = ValueToJson { case Type.Const.Defined(pn, t) =>
for {
pack <- pm.toMap.get(pn)
dt <- pack.program.types.getType(pn, t)
dt <- pack.types.getType(pn, t)
} yield dt
}

Expand All @@ -143,7 +143,7 @@ case class Evaluation[T](pm: PackageMap.Typed[T], externals: Externals) {
val valueToDoc: ValueToDoc = ValueToDoc { case Type.Const.Defined(pn, t) =>
for {
pack <- pm.toMap.get(pn)
dt <- pack.program.types.getType(pn, t)
dt <- pack.types.getType(pn, t)
} yield dt
}
}
93 changes: 2 additions & 91 deletions core/src/main/scala/org/bykn/bosatsu/Import.scala
Original file line number Diff line number Diff line change
@@ -1,71 +1,12 @@
package org.bykn.bosatsu

import cats.{Foldable, Functor}
import cats.Foldable
import cats.data.NonEmptyList
import cats.implicits._
import cats.parse.{Parser => P}
import cats.syntax.all._
import org.typelevel.paiges.{Doc, Document}

import Parser.{spaces, Combinators}

sealed abstract class ImportedName[+T] {
def originalName: Identifier
def localName: Identifier
def tag: T
def isRenamed: Boolean = originalName != localName
def withTag[U](tag: U): ImportedName[U]

def map[U](fn: T => U): ImportedName[U] =
this match {
case ImportedName.OriginalName(n, t) =>
ImportedName.OriginalName(n, fn(t))
case ImportedName.Renamed(o, l, t) =>
ImportedName.Renamed(o, l, fn(t))
}

def traverse[F[_], U](
fn: T => F[U]
)(implicit F: Functor[F]): F[ImportedName[U]] =
this match {
case ImportedName.OriginalName(n, t) =>
F.map(fn(t))(ImportedName.OriginalName(n, _))
case ImportedName.Renamed(o, l, t) =>
F.map(fn(t))(ImportedName.Renamed(o, l, _))
}
}

object ImportedName {
case class OriginalName[T](originalName: Identifier, tag: T)
extends ImportedName[T] {
def localName = originalName
def withTag[U](tag: U): ImportedName[U] = copy(tag = tag)
}
case class Renamed[T](originalName: Identifier, localName: Identifier, tag: T)
extends ImportedName[T] {
def withTag[U](tag: U): ImportedName[U] = copy(tag = tag)
}

implicit val document: Document[ImportedName[Unit]] =
Document.instance[ImportedName[Unit]] {
case ImportedName.OriginalName(nm, _) => Document[Identifier].document(nm)
case ImportedName.Renamed(from, to, _) =>
Document[Identifier].document(from) + Doc.text(" as ") +
Document[Identifier].document(to)
}

val parser: P[ImportedName[Unit]] = {
def basedOn(of: P[Identifier]): P[ImportedName[Unit]] =
(of ~ (spaces.soft *> P.string("as") *> spaces *> of).?)
.map {
case (from, Some(to)) => ImportedName.Renamed(from, to, ())
case (orig, None) => ImportedName.OriginalName(orig, ())
}

basedOn(Identifier.bindableParser)
.orElse(basedOn(Identifier.consParser))
}
}

case class Import[A, B](pack: A, items: NonEmptyList[ImportedName[B]]) {
def resolveToGlobal: Map[Identifier, (A, Identifier)] =
items.foldLeft(Map.empty[Identifier, (A, Identifier)]) {
Expand Down Expand Up @@ -121,33 +62,3 @@ object Import {
}
}
}

/** There are all the distinct imported names and the original ImportedName
*/
case class ImportMap[A, B](toMap: Map[Identifier, (A, ImportedName[B])]) {
def apply(name: Identifier): Option[(A, ImportedName[B])] =
toMap.get(name)

def +(that: (A, ImportedName[B])): ImportMap[A, B] =
ImportMap(toMap.updated(that._2.localName, that))
}

object ImportMap {
def empty[A, B]: ImportMap[A, B] = ImportMap(Map.empty)
// Return the list of collisions in local names along with a map
// with the last name overwriting the import
def fromImports[A, B](
is: List[Import[A, B]]
): (List[(A, ImportedName[B])], ImportMap[A, B]) =
is.iterator
.flatMap { case Import(p, is) => is.toList.iterator.map((p, _)) }
.foldLeft((List.empty[(A, ImportedName[B])], ImportMap.empty[A, B])) {
case ((dups, imap), pim @ (_, im)) =>
val dups1 = imap(im.localName) match {
case Some(nm) => nm :: dups
case None => dups
}

(dups1, imap + pim)
}
}
74 changes: 74 additions & 0 deletions core/src/main/scala/org/bykn/bosatsu/ImportMap.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.bykn.bosatsu

import cats.{Applicative, Functor, Order, Parallel}
import scala.collection.immutable.SortedMap

import cats.syntax.all._

/** There are all the distinct imported names and the original ImportedName
*/
case class ImportMap[A, B](toMap: SortedMap[Identifier, (A, ImportedName[B])]) {
def apply(name: Identifier): Option[(A, ImportedName[B])] =
toMap.get(name)

def +(that: (A, ImportedName[B])): ImportMap[A, B] =
ImportMap(toMap.updated(that._2.localName, that))

def toList(implicit ord: Order[A]): List[Import[A, B]] =
toMap.iterator
.map { case (_, ab) => ab }
.toList
.groupByNel(_._1)
.iterator
.map { case (a, bs) =>
Import(a, bs.map(_._2))
}
.toList

def traverse[F[_]: Applicative, C, D](fn: (A, ImportedName[B]) => F[(C, ImportedName[D])]): F[ImportMap[C, D]] =
toMap.traverse[F, (C, ImportedName[D])] { case (a, ib) => fn(a, ib) }
.map(ImportMap(_))

def parTraverse[F[_]: Parallel: Functor, C, D](fn: (A, ImportedName[B]) => F[(C, ImportedName[D])]): F[ImportMap[C, D]] =
toMap.parTraverse[F, (C, ImportedName[D])] { case (a, ib) => fn(a, ib) }
.map(ImportMap(_))
}

object ImportMap {
def empty[A, B]: ImportMap[A, B] = ImportMap(SortedMap.empty)

sealed abstract class Unify
object Unify {
case object Error extends Unify
case object Left extends Unify
case object Right extends Unify
}
// Return the list of collisions in local names along with a map
// with the last name overwriting the import
def fromImports[A, B](
is: List[Import[A, B]]
)(unify: ((A, ImportedName[B]), (A, ImportedName[B])) => Unify): (List[(A, ImportedName[B])], ImportMap[A, B]) =
is.iterator
.flatMap { case Import(p, is) => is.toList.iterator.map((p, _)) }
.foldLeft((List.empty[(A, ImportedName[B])], ImportMap.empty[A, B])) {
case (old @ (dups, imap), pim @ (_, im)) =>
val (dups1, imap1) = imap(im.localName) match {
case Some(nm) =>
unify(nm, pim) match {
case Unify.Error =>
// pim and nm are a collision, add both
(pim :: nm :: dups, imap + pim)
case Unify.Left => old
case Unify.Right => (dups, imap + pim)
}
case None => (dups, imap + pim)
}

(dups1.reverse.distinct, imap1)
}

// This is only safe after verifying there are not collisions
// which has been done on compiled packages
def fromImportsUnsafe[A, B](is: List[Import[A, B]]): ImportMap[A, B] =
fromImports(is)((a, b) => sys.error(s"collision in $a and $b: $is"))._2
}
64 changes: 64 additions & 0 deletions core/src/main/scala/org/bykn/bosatsu/ImportedName.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.bykn.bosatsu

import cats.Functor
import cats.parse.{Parser => P}
import org.typelevel.paiges.{Doc, Document}
import Parser.spaces

sealed abstract class ImportedName[+T] {
def originalName: Identifier
def localName: Identifier
def tag: T
def isRenamed: Boolean = originalName != localName
def withTag[U](tag: U): ImportedName[U]

def map[U](fn: T => U): ImportedName[U] =
this match {
case ImportedName.OriginalName(n, t) =>
ImportedName.OriginalName(n, fn(t))
case ImportedName.Renamed(o, l, t) =>
ImportedName.Renamed(o, l, fn(t))
}

def traverse[F[_], U](
fn: T => F[U]
)(implicit F: Functor[F]): F[ImportedName[U]] =
this match {
case ImportedName.OriginalName(n, t) =>
F.map(fn(t))(ImportedName.OriginalName(n, _))
case ImportedName.Renamed(o, l, t) =>
F.map(fn(t))(ImportedName.Renamed(o, l, _))
}
}

object ImportedName {
case class OriginalName[T](originalName: Identifier, tag: T)
extends ImportedName[T] {
def localName = originalName
def withTag[U](tag: U): ImportedName[U] = copy(tag = tag)
}
case class Renamed[T](originalName: Identifier, localName: Identifier, tag: T)
extends ImportedName[T] {
def withTag[U](tag: U): ImportedName[U] = copy(tag = tag)
}

implicit val document: Document[ImportedName[Unit]] =
Document.instance[ImportedName[Unit]] {
case ImportedName.OriginalName(nm, _) => Document[Identifier].document(nm)
case ImportedName.Renamed(from, to, _) =>
Document[Identifier].document(from) + Doc.text(" as ") +
Document[Identifier].document(to)
}

val parser: P[ImportedName[Unit]] = {
def basedOn(of: P[Identifier]): P[ImportedName[Unit]] =
(of ~ (spaces.soft *> P.string("as") *> spaces *> of).?)
.map {
case (from, Some(to)) => ImportedName.Renamed(from, to, ())
case (orig, None) => ImportedName.OriginalName(orig, ())
}

basedOn(Identifier.bindableParser)
.orElse(basedOn(Identifier.consParser))
}
}
2 changes: 1 addition & 1 deletion core/src/main/scala/org/bykn/bosatsu/MainModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -654,7 +654,7 @@ abstract class MainModule[IO[_]](implicit
).get

val evalMap = pm.toMap.iterator.flatMap { case (n, p) =>
val optEval = p.program.lets.findLast { case (_, _, te) =>
val optEval = p.lets.findLast { case (_, _, te) =>
typeEvalMap.contains(te.getType)
}
optEval.map { case (b, _, te) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object MatchlessFromTypedExpr {
val allItemsList = pm.toMap.toList
.traverse[Par.F, (PackageName, List[(Bindable, Matchless.Expr)])] {
case (pname, pack) =>
val lets = pack.program.lets
val lets = pack.lets

Par.start {
val exprs: List[(Bindable, Matchless.Expr)] =
Expand Down
Loading

0 comments on commit 99afa56

Please sign in to comment.