effekt-lang / effekt

A research language with effect handlers and lightweight effect polymorphism
https://effekt-lang.org
MIT License
301 stars 14 forks source link

Convention for error reporting and aborting #502

Open dvdvgt opened 1 week ago

dvdvgt commented 1 week ago

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(...)
  }
}
b-studios commented 1 week ago

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.