Convention for error reporting and aborting
It would be nice to have some agreed upon convention on when to use which kind of error reporting:
https://github.com/effekt-lang/effekt/blob/430369f402d78abdc70486ad8d38d54a8cb24060/effekt/shared/src/main/scala/effekt/util/Messages.scala#L115-L134
When should something abort? When should something panic? When to just report an error?
Related to this, what is the contract of calling a run function of phase? Should the caller check whether the previous phase reported an error by check if it returned None and then abort/panic, or rather should each phase (callee) handle their own errors appropriately and in turn do not return Option[A] but just A?
class SomePhase extends Phase[A, B] {
def run(...)(using ctx: Context): Option[B] = {
// ...
ctx.report(...)
None
}
}
// or rather
class SomePhase1 extends Phase[A, B] {
def run(...)(using ctx: Context): B = {
// ...
ctx.abort(...)
}
}
Given that in Phase, we have
def apply(input: In)(using C: Context): Option[Out] = try {
run(input)
} catch {
case FatalPhaseError(msg) =>
C.report(msg)
None
}
what about we change
-def run(input: In)(using C: Context): Option[Out]
+def run(input: In)(using C: Context): Out // can throw FatalPhaseError using `abort`.
?
The convention then would be to always use abort to immediately abort execution of a phase without returning a value.
error is typically used if we eventually abort a phase, but want to collect multiple errors. This mode of usage could also be "formalized" somewhere. In Typer for instance we are using the idiom
if (Context.messaging.hasErrors) {
None
} else {
Some(Typechecked(source, tree, mod))
}
that could be used elsewhere as well.
Namer always seems to use abort and return Some(NameResolved(source, tree, mod)), which doesn't give multiple resolved errors. Many of these aborts could become errors.