From 000a76885d1a1d0004a41422ca35a87362582503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonathan=20Immanuel=20Brachth=C3=A4user?= Date: Thu, 7 Nov 2024 17:20:17 +0100 Subject: [PATCH] Improve the error when an effect is not fully known (#678) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses #663 (even though it doesn't remove the root cause). We improve the message by 1. mentioning the…… unknown types and 2. providing a hint what to do. image --- .../shared/src/main/scala/effekt/Typer.scala | 8 ++- .../scala/effekt/typer/ConcreteEffects.scala | 52 ++++++++++++------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/effekt/shared/src/main/scala/effekt/Typer.scala b/effekt/shared/src/main/scala/effekt/Typer.scala index 77558ef38..48849181c 100644 --- a/effekt/shared/src/main/scala/effekt/Typer.scala +++ b/effekt/shared/src/main/scala/effekt/Typer.scala @@ -786,9 +786,7 @@ object Typer extends Phase[NameResolved, Typechecked] { } // we bind the function type outside of the unification scope to solve for variables. val substituted = Context.unification(funTpe) - if (!isConcreteBlockType(substituted)) { - Context.abort(pretty"Cannot fully infer type for ${id}: ${substituted}") - } + assertConcreteFunction(id, substituted) Context.bind(sym, substituted) Result((), unhandledEffects) @@ -1510,7 +1508,7 @@ trait TyperOps extends ContextOps { self: Context => private [typer] def bindCapabilities[R](binder: source.Tree, caps: List[symbols.BlockParam]): Unit = val capabilities = caps map { cap => - assertConcrete(cap.tpe.getOrElse { INTERNAL_ERROR("Capability type needs to be know.") }.asInterfaceType) + assertConcreteEffect(cap.tpe.getOrElse { INTERNAL_ERROR("Capability type needs to be know.") }.asInterfaceType) positions.dupPos(binder, cap) cap } @@ -1528,7 +1526,7 @@ trait TyperOps extends ContextOps { self: Context => * Has the potential side-effect of creating a fresh capability. Also see [[BindAll.capabilityFor()]] */ private [typer] def capabilityFor(tpe: InterfaceType): symbols.BlockParam = - assertConcrete(tpe) + assertConcreteEffect(tpe) val cap = capabilityScope.capabilityFor(tpe) annotations.update(Annotations.Captures, cap, CaptureSet(cap.capture)) cap diff --git a/effekt/shared/src/main/scala/effekt/typer/ConcreteEffects.scala b/effekt/shared/src/main/scala/effekt/typer/ConcreteEffects.scala index 6b2f4b931..34e374094 100644 --- a/effekt/shared/src/main/scala/effekt/typer/ConcreteEffects.scala +++ b/effekt/shared/src/main/scala/effekt/typer/ConcreteEffects.scala @@ -44,7 +44,7 @@ object ConcreteEffects { * [[Typer.asConcrete]] should be used instead, since it performs substitution and dealiasing. */ def apply(eff: List[InterfaceType])(using Context): ConcreteEffects = - eff foreach assertConcrete + eff foreach assertConcreteEffect fromList(eff) def apply(effs: Effects)(using Context): ConcreteEffects = apply(effs.toList) @@ -73,33 +73,45 @@ implicit def asConcrete(effs: Effects)(using C: Context): ConcreteEffects = * * TODO Question: should we ALWAYS require effects to be concrete, also when compared with [[TypeUnifier]]? */ -private[typer] def assertConcrete(effs: Effects)(using C: Context): Unit = - if (!isConcreteEffects(effs)) C.abort(pretty"Effects need to be fully known: ${effs}") +private[typer] def assertConcreteEffects(effs: Effects)(using C: Context): Unit = + effs.effects.foreach(assertConcreteEffect) + +private[typer] def assertConcreteEffect(eff: InterfaceType)(using C: Context): Unit = + unknowns(eff) match { + case us if us.nonEmpty => + C.abort(pretty"Effects need to be fully known, but effect ${eff}'s type parameter(s) ${us.mkString(", ")} could not be inferred.\n\nMaybe try annotating them?") + case _ => () + } -private[typer] def assertConcrete(eff: InterfaceType)(using C: Context): Unit = - if (!isConcreteInterfaceType(eff)) { - C.abort(pretty"Effects need to be fully known: ${eff}") +private[typer] def assertConcreteFunction(id: source.Id, tpe: BlockType)(using C: Context): Unit = + unknowns(tpe) match { + case us if us.nonEmpty => + C.abort(pretty"Cannot fully infer type for ${id}: ${tpe}") + case _ => () } -private def isConcreteValueType(tpe: ValueType): Boolean = tpe match { - case ValueTypeRef(x) => isConcreteValueType(x) - case ValueTypeApp(tpe, args) => args.forall(isConcreteValueType) - case BoxedType(tpe, capture) => isConcreteBlockType(tpe) && isConcreteCaptureSet(capture) +private type Unknown = CaptUnificationVar | UnificationVar + +private def unknowns(tpe: ValueType): Set[Unknown] = tpe match { + case ValueTypeRef(x) => unknowns(x) + case ValueTypeApp(tpe, args) => args.flatMap(unknowns).toSet + case BoxedType(tpe, capture) => unknowns(tpe) ++ unknowns(capture) } -private def isConcreteValueType(tpe: TypeVar): Boolean = tpe match { - case x: UnificationVar => false - case x: TypeVar => true +private def unknowns(tpe: TypeVar): Set[Unknown] = tpe match { + case x: UnificationVar => Set(x) + case x: TypeVar => Set.empty } -private def isConcreteBlockType(tpe: BlockType): Boolean = tpe match { +private def unknowns(tpe: BlockType): Set[Unknown] = tpe match { case FunctionType(tparams, cparams, vparams, bparams, result, effects) => - vparams.forall(isConcreteValueType) && bparams.forall(isConcreteBlockType) && isConcreteValueType(result) && isConcreteEffects(effects) - case InterfaceType(tpe, args) => args.forall(isConcreteValueType) + vparams.flatMap(unknowns).toSet ++ bparams.flatMap(unknowns).toSet ++ unknowns(result) ++ unknowns(effects) + case InterfaceType(tpe, args) => args.flatMap(unknowns).toSet } -private def isConcreteCaptureSet(capt: Captures): Boolean = capt.isInstanceOf[CaptureSet] -private def isConcreteInterfaceType(eff: InterfaceType): Boolean = eff match { - case InterfaceType(tpe, args) => args.forall(isConcreteValueType) +private def unknowns(capt: Captures): Set[Unknown] = capt match { + case x @ CaptUnificationVar(role) => Set(x) + case CaptureSet(captures) => Set.empty } -private def isConcreteEffects(effs: Effects): Boolean = effs.toList.forall(isConcreteInterfaceType) + +private def unknowns(effs: Effects): Set[Unknown] = effs.toList.flatMap(unknowns).toSet