Skip to content

Commit

Permalink
Improve KindFormula.Error reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
johnynek committed Dec 17, 2023
1 parent 48ed6ab commit 8bcce7c
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 32 deletions.
25 changes: 17 additions & 8 deletions core/src/main/scala/org/bykn/bosatsu/KindFormula.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,22 @@ object KindFormula {
case object Type extends KindFormula
case class Cons(arg: Arg, result: KindFormula) extends KindFormula

sealed abstract class Error
sealed abstract class Error {
def dt: DefinedType[Option[Kind.Arg]]
}
object Error {
case class Unsatisfiable(
dte: DefinedType[Either[KnownShape, Kind.Arg]],
constraints: LongMap[NonEmptyList[Constraint]],
existing: LongMap[Variance],
unknowns: SortedSet[Long]
) extends Error
) extends Error {
def dt: DefinedType[Option[Kind.Arg]] = dte.map(_.toOption)
}

case class FromShapeError(shapeError: Shape.Error) extends Error
case class FromShapeError(shapeError: Shape.Error) extends Error {
def dt = shapeError.dt
}
}

sealed abstract class Sat
Expand Down Expand Up @@ -291,7 +298,7 @@ object KindFormula {
// we have allocated all variance variables now, we can see how many
varCount <- state.nextId
topo = Impl.combineTopo(varCount, constraints)
maybeRes = Impl.go(constraints, dirs, topo).map { vars =>
maybeRes = Impl.go(dt, constraints, dirs, topo).map { vars =>
dtFormula.map(Impl.unformula(_, vars))
}
} yield maybeRes).run.value
Expand Down Expand Up @@ -354,6 +361,7 @@ object KindFormula {
// invariant: all subgraph values must be valid keys in the result
// we can process the subgraph list in parallel. Those are all the indepentent next values
def allSolutionChunk(
dt: DefinedType[Either[Shape.KnownShape, Kind.Arg]],
cons: Cons,
existing: LongMap[Variance],
directions: LongMap[Direction],
Expand Down Expand Up @@ -414,30 +422,32 @@ object KindFormula {
NonEmptyLazyList.fromLazyList(validVariances) match {
case Some(nel) => Validated.valid(nel)
case None =>
Validated.invalidNec(Error.Unsatisfiable(cons, existing, subgraph))
Validated.invalidNec(Error.Unsatisfiable(dt, cons, existing, subgraph))
}
}

def allSolutions(
dt: DefinedType[Either[Shape.KnownShape, Kind.Arg]],
cons: Cons,
existing: LongMap[Variance],
directions: LongMap[Direction],
subgraph: NonEmptyList[SortedSet[Long]]
): EitherNec[Error, NonEmptyLazyList[LongMap[Variance]]] =
subgraph
.traverse(allSolutionChunk(cons, existing, directions, _))
.traverse(allSolutionChunk(dt, cons, existing, directions, _))
.map(KindFormula.mergeCrossProduct(_))
.toEither

def go[F[_]: Foldable](
dt: DefinedType[Either[Shape.KnownShape, Kind.Arg]],
cons: Cons,
directions: LongMap[Direction],
topo: F[NonEmptyList[SortedSet[Long]]]
): ValidatedNec[Error, LongMap[Variance]] =
topo.foldM(NonEmptyLazyList(LongMap.empty[Variance])) {
(sols, subgraph) =>
// if there is at least one good solution, we can keep going
val next = sols.map(allSolutions(cons, _, directions, subgraph))
val next = sols.map(allSolutions(dt, cons, _, directions, subgraph))
val nextPaths: LazyList[LongMap[Variance]] =
next.toLazyList.flatMap {
case Right(ll) => ll.toLazyList
Expand Down Expand Up @@ -855,7 +865,6 @@ object KindFormula {
)
} yield ()
}

}
}

Expand Down
34 changes: 11 additions & 23 deletions core/src/main/scala/org/bykn/bosatsu/PackageError.scala
Original file line number Diff line number Diff line change
Expand Up @@ -534,26 +534,26 @@ object PackageError {
case class KindInferenceError(pack: PackageName, kindError: KindFormula.Error, regions: Map[Type.Const.Defined, Region]) extends PackageError {
def message(sourceMap: Map[PackageName, (LocationMap, String)], errColor: Colorize) = {
val (lm, _) = sourceMap.getMapSrc(pack)
val region = regions(kindError.dt.toTypeConst)
val ctx = lm.showRegion(region, 2, errColor)
.getOrElse(Doc.str(region)) // we should highlight the whole region
kindError match {
case KindFormula.Error.Unsatisfiable(_, _, _) =>
val prefix = sourceMap.headLine(pack, None)
(prefix + Doc.text(s": $kindError")).render(80)
case KindFormula.Error.Unsatisfiable(_, _, _, _) =>
val prefix = sourceMap.headLine(pack, Some(region))
// TODO: would be good to give a more precise problem, e.g. which
// type parameters are the problem.
val message = Doc.text("could not solve for valid variances")
(prefix + Doc.hardLine + message + Doc.hardLine + ctx).render(80)
case KindFormula.Error.FromShapeError(se) =>
se match {
case Shape.UnificationError(dt, cons, left, right) =>
val region = regions(dt.toTypeConst)
case Shape.UnificationError(_, cons, left, right) =>
val prefix = sourceMap.headLine(pack, Some(region))
val ctx = lm.showRegion(region, 2, errColor)
.getOrElse(Doc.str(region)) // we should highlight the whole region
(prefix + Doc.hardLine + Doc.text("shape error: expected ") + Shape.shapeDoc(left) + Doc.text(" and ") + Shape.shapeDoc(right) +
Doc.text(s" to match in the constructor ${cons.name.sourceCodeRepr}") + Doc.hardLine + Doc.hardLine +
ctx).render(80)
case Shape.ShapeMismatch(dt, cons, outer, tyApp, right) =>
case Shape.ShapeMismatch(_, cons, outer, tyApp, right) =>
val tmap = showTypes(pack, outer :: tyApp :: Nil)
val region = regions(dt.toTypeConst)
val prefix = sourceMap.headLine(pack, Some(region))
val ctx = lm.showRegion(region, 2, errColor)
.getOrElse(Doc.str(region)) // we should highlight the whole region
val typeDoc =
if (outer != tyApp) (tmap(outer) + Doc.text(" at application ") + tmap(tyApp))
else tmap(outer)
Expand All @@ -564,16 +564,12 @@ object PackageError {
Doc.hardLine + Doc.hardLine +
ctx).render(80)
case Shape.FinishFailure(dt, left, right) =>
val region = regions(dt.toTypeConst)
val tdoc = showTypes(pack, dt.toTypeTyConst :: Nil)(dt.toTypeTyConst)
val prefix = sourceMap.headLine(pack, Some(region))
val message = Doc.text("in type ") + tdoc + Doc.text(" could not unify shapes: ") + Shape.shapeDoc(left) + Doc.text(" and ") +
Shape.shapeDoc(right)
val ctx = lm.showRegion(region, 2, errColor)
.getOrElse(Doc.str(region)) // we should highlight the whole region
(prefix + Doc.hardLine + message + Doc.hardLine + ctx).render(80)
case Shape.ShapeLoop(dt, tpe, _) =>
val region = regions(dt.toTypeConst)
val tpe2 = tpe match {
case Left(ap) => ap
case Right(v) => Type.TyVar(v)
Expand All @@ -583,11 +579,8 @@ object PackageError {
val prefix = sourceMap.headLine(pack, Some(region))
val message = Doc.text("in type ") + tdocs(dt.toTypeTyConst) + Doc.text(" cyclic dependency encountered in ") +
tdocs(tpe2)
val ctx = lm.showRegion(region, 2, errColor)
.getOrElse(Doc.str(region)) // we should highlight the whole region
(prefix + Doc.hardLine + message + Doc.hardLine + ctx).render(80)
case Shape.UnboundVar(dt, cfn, v) =>
val region = regions(dt.toTypeConst)
val tpe2 = Type.TyVar(v)
val tdocs = showTypes(pack, dt.toTypeTyConst :: tpe2 :: Nil)

Expand All @@ -598,11 +591,8 @@ object PackageError {
val message = Doc.text("in type ") + tdocs(dt.toTypeTyConst) +
Doc.text(" unbound type variable ") + tdocs(tpe2) + cfnMsg

val ctx = lm.showRegion(region, 2, errColor)
.getOrElse(Doc.str(region)) // we should highlight the whole region
(prefix + Doc.hardLine + message + Doc.hardLine + ctx).render(80)
case Shape.UnknownConst(dt, cfn, c) =>
val region = regions(dt.toTypeConst)
val tpe2 = Type.TyConst(c)
val tdocs = showTypes(pack, dt.toTypeTyConst :: tpe2 :: Nil)

Expand All @@ -613,8 +603,6 @@ object PackageError {
val message = Doc.text("in type ") + tdocs(dt.toTypeTyConst) +
Doc.text(" unknown type ") + tdocs(tpe2) + cfnMsg

val ctx = lm.showRegion(region, 2, errColor)
.getOrElse(Doc.str(region)) // we should highlight the whole region
(prefix + Doc.hardLine + message + Doc.hardLine + ctx).render(80)
}
}
Expand Down
5 changes: 4 additions & 1 deletion core/src/main/scala/org/bykn/bosatsu/Shape.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ object Shape {
Doc.text("kind(") + tdoc + Doc.char(')')
}

sealed abstract class Error
sealed abstract class Error {
def dt: DefinedType[Option[Kind.Arg]]
}

case class ShapeLoop(
dt: DefinedType[Option[Kind.Arg]],
bound: Either[rankn.Type.TyApply, rankn.Type.Var.Bound],
Expand Down
15 changes: 15 additions & 0 deletions core/src/test/scala/org/bykn/bosatsu/EvaluationTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3270,4 +3270,19 @@ res = z matches (1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
()
}
}

test("kind errors show the region of the type") {
val testCode = """
package ErrorCheck
struct Foo[a: -*](get: a)
"""
evalFail(List(testCode)) { case kie@PackageError.KindInferenceError(_, _, _) =>
val message = kie.message(Map.empty, Colorize.None)
assert(message.contains("Region(21,46)"))
assert(testCode.substring(21, 46) == "struct Foo[a: -*](get: a)")
()
}
}
}

0 comments on commit 8bcce7c

Please sign in to comment.