feat(compiler): Fail on internal error [fixes LNG-229] (#905)

* Refactor utils

* Add errors project

* logger.error -> internalError

* Add comment

* Suppress stack trace

---------

Co-authored-by: Dima <dmitry.shakhtarin@fluence.ai>
This commit is contained in:
InversionSpaces 2023-09-19 17:01:42 +02:00 committed by GitHub
parent fb75bc267e
commit 8741c910be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 115 additions and 86 deletions

View File

@ -7,6 +7,7 @@ val catsV = "2.10.0"
val catsParseV = "0.3.10"
val monocleV = "3.1.0"
val scalaTestV = "3.2.17"
val sourcecodeV = "0.3.0"
val fs2V = "3.9.2"
val catsEffectV = "3.6-1f95fd7"
val declineV = "2.3.0"
@ -163,7 +164,7 @@ lazy val model = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.settings(commons)
.dependsOn(types, tree, raw, helpers)
.dependsOn(types, tree, raw, helpers, errors)
lazy val res = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
@ -196,7 +197,7 @@ lazy val semantics = crossProject(JVMPlatform, JSPlatform)
"dev.optics" %%% "monocle-macro" % monocleV
)
)
.dependsOn(raw, parser)
.dependsOn(raw, parser, errors)
lazy val compiler = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
@ -266,6 +267,17 @@ lazy val helpers = crossProject(JVMPlatform, JSPlatform)
)
)
lazy val errors = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("utils/errors"))
.settings(commons)
.settings(
libraryDependencies ++= Seq(
"com.lihaoyi" %%% "sourcecode" % sourcecodeV
)
)
lazy val `backend-air` = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)

View File

@ -1,5 +1,6 @@
package aqua.model.inline
import aqua.errors.Errors.internalError
import aqua.model
import aqua.model.*
import aqua.model.inline.state.{Arrows, Exports, Mangler}
@ -172,8 +173,7 @@ object ArrowInliner extends Logging {
case arrow @ (_, vm: VarModel) =>
arrow.some
case (_, m) =>
logger.error(s"Unexpected: '$m' cannot be an arrow")
None
internalError(s"($m) cannot be an arrow")
}
}
@ -205,8 +205,7 @@ object ArrowInliner extends Logging {
case (_, VarModel(name, _, _)) =>
arrows.get(name).map(name -> _)
case (_, m) =>
logger.error(s"Unexpected: '$m' cannot be an arrow")
None
internalError(s"($m) cannot be an arrow")
}
}

View File

@ -1,5 +1,6 @@
package aqua.model.inline
import aqua.errors.Errors.internalError
import aqua.model.inline.state.{Arrows, Exports, Mangler}
import aqua.model.*
import aqua.model.inline.RawValueInliner.collectionToModel
@ -277,12 +278,10 @@ object TagInliner extends Logging {
n <- Mangler[S].findAndForbidName(item)
elementType = iterable.`type` match {
case b: BoxType => b.element
// TODO: it is unexpected, should we handle this?
case _ =>
logger.error(
s"Unexpected behaviour: non-box type variable '$iterable' in 'for' expression."
internalError(
s"non-box type variable '$iterable' in 'for' expression."
)
iterable.`type`
}
_ <- Exports[S].resolved(item, VarModel(n, elementType))
m = mode.map {
@ -308,11 +307,10 @@ object TagInliner extends Logging {
prefix = p
)
case (_, (vm, prefix)) =>
logger.error(
s"Unexpected: stream (${exportTo}) resolved " +
internalError(
s"stream (${exportTo}) resolved " +
s"to ($vm) with prefix ($prefix)"
)
TagInlined.Empty()
}
case CanonicalizeTag(operand, exportTo) =>

View File

@ -1,5 +1,6 @@
package aqua.model.inline.raw
import aqua.errors.Errors.internalError
import aqua.model.*
import aqua.model.ValueModel.Ability
import aqua.model.inline.Inline
@ -8,6 +9,7 @@ import aqua.model.inline.RawValueInliner.unfold
import aqua.model.inline.state.{Arrows, Exports, Mangler}
import aqua.raw.value.*
import aqua.types.*
import cats.Eval
import cats.data.{Chain, IndexedStateT, State}
import cats.instances.list.*
@ -69,8 +71,9 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
at
}
.getOrElse {
logger.error(s"Inlining, cannot find arrow $arrowName in $varModel")
ArrowType(NilType, NilType)
internalError(
s"Inlining, cannot find arrow ($arrowName) in ($varModel)"
)
}
for {
callArrow <- CallArrowRawInliner(
@ -101,11 +104,10 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
flatLiteralWithProperties(lm, Inline.empty, Chain.empty)
case _ =>
Exports[S].getKeys.flatMap { keys =>
logger.error(
s"Inlining, cannot find field ${AbilityType
.fullName(varModel.name, fieldName)} in ability $varModel. Available: $keys"
val field = AbilityType.fullName(varModel.name, fieldName)
internalError(
s"Inlining, cannot find field ($field) in ability $varModel. Available: $keys"
)
flatLiteralWithProperties(LiteralModel.quote(""), Inline.empty, Chain.empty)
}
}
@ -265,12 +267,14 @@ object ApplyPropertiesRawInliner extends RawInliner[ApplyPropertyRaw] with Loggi
}
case (v, i) =>
// what if pass nil as stream argument?
logger.error("Unreachable. Unfolded stream cannot be a literal")
State.pure(v -> i)
internalError(
s"Unfolded stream ($gateRaw) cannot be a literal"
)
}
case l =>
logger.error("Unreachable. Unfolded stream cannot be a literal")
State.pure(l)
internalError(
s"Unfolded stream ($vr) cannot be a literal"
)
}
case (_, _) =>

View File

@ -1,5 +1,6 @@
package aqua.model.inline.raw
import aqua.errors.Errors.internalError
import aqua.model.inline.Inline.parDesugarPrefixOpt
import aqua.model.{CallServiceModel, FuncArrow, MetaModel, SeqModel, ValueModel, VarModel}
import aqua.model.inline.{ArrowInliner, Inline, TagInliner}
@ -9,6 +10,7 @@ import aqua.raw.ops.Call
import aqua.types.ArrowType
import aqua.raw.value.CallArrowRaw
import cats.syntax.traverse.*
import cats.data.{Chain, State}
import scribe.Logging
@ -88,14 +90,15 @@ object CallArrowRawInliner extends RawInliner[CallArrowRaw] with Logging {
// if there is no arrow, check if it is stored in Exports as variable and try to resolve it
lastArrow.flatMap(arrows.get)
)
result <- arrow.fold {
logger.error(
s"Inlining, cannot find arrow $funcName, available: ${arrows.keys
.mkString(", ")} and vars: ${exports.keys.mkString(", ")}"
)
State.pure(Nil -> Inline.empty)
}(resolveFuncArrow(_, call))
result <- arrow
.traverse(resolveFuncArrow(_, call))
.map(_.getOrElse {
val arrs = arrows.keys.mkString(", ")
val vars = exports.keys.mkString(", ")
internalError(
s"Inlining, cannot find arrow ($funcName), available: ($arrs) and vars: ($vars)"
)
})
} yield result
override def apply[S: Mangler: Exports: Arrows](

View File

@ -1,5 +1,6 @@
package aqua.model
import aqua.errors.Errors.internalError
import aqua.raw.value.*
import aqua.types.*
@ -213,11 +214,12 @@ case class VarModel(name: String, baseType: Type, properties: Chain[PropertyMode
case nvm: VarModel =>
deriveFrom(vv.deriveFrom(nvm))
case valueModel =>
if (properties.nonEmpty)
logger.error(
s"Var $name derived from literal $valueModel, but property is lost: $properties"
if (properties.isEmpty) valueModel
else
internalError(
s"Var ($name) derived from literal ($valueModel), " +
s"but properties ($properties) are lost"
)
valueModel
}
}
case _ =>
@ -225,11 +227,13 @@ case class VarModel(name: String, baseType: Type, properties: Chain[PropertyMode
}
case Some(vv) =>
if (properties.nonEmpty)
logger.error(
s"Var $name derived from literal $vv, but property is lost: $properties"
if (properties.isEmpty) vv
else
internalError(
s"Var ($name) derived from literal ($vv), " +
s"but properties ($properties) are lost: "
)
vv
case None =>
this // Should not happen
}

View File

@ -1,5 +1,6 @@
package aqua.model.transform.topology
import aqua.errors.Errors.internalError
import aqua.model.transform.topology.TopologyPath
import aqua.model.transform.cursor.ChainZipper
import aqua.model.transform.topology.strategy.*
@ -81,12 +82,11 @@ case class Topology private (
capturedTopologies.flatMap(
_.get(name)
.traverse(_.pathOn)
.flatTap(p =>
Eval.later(
if (p.isEmpty) logger.error(s"Captured topology `$name` not found")
.map(_.getOrElse {
internalError(
s"Captured topology ($name) not found"
)
)
.map(_.orEmpty)
})
)
case _ => parent.traverse(_.pathOn).map(_.orEmpty)
}
@ -337,8 +337,9 @@ object Topology extends Logging {
resolvedCofree.map(NonEmptyChain.fromChain(_).map(_.uncons)).map {
case None =>
logger.error("Topology emitted nothing")
SeqRes.leaf
internalError(
s"Topology emitted nothing on (${op.show})"
)
case Some((el, `nil`)) => el
case Some((el, tail)) =>
logger.warn("Topology emitted many nodes, that's unusual")

View File

@ -1,5 +1,6 @@
package aqua.semantics
import aqua.errors.Errors.internalError
import aqua.parser.head.{HeadExpr, HeaderExpr, ImportExpr, ImportFromExpr}
import aqua.parser.lexer.{LiteralToken, Token}
import aqua.parser.{Ast, Expr}
@ -361,12 +362,9 @@ object RawSemantics extends Logging {
) { case (ctx, p) =>
ctx.copy(parts = ctx.parts :+ (ctx -> p))
}
case (state: CompilerState[S], m) =>
logger.error("Got unexpected " + m)
state.copy(errors = state.errors :+ WrongAST(ast)) -> RawContext.blank.copy(
init = Some(init.copy(module = init.module.map(_ + "|init")))
.filter(_ != RawContext.blank)
case (_, m) =>
internalError(
s"Unexpected Raw ($m)"
)
}
}

View File

@ -1,12 +1,13 @@
package aqua.constants
import scala.util.Left
import aqua.parser.expr.ConstantExpr
import aqua.raw.ConstantRaw
import aqua.raw.value.LiteralRaw
import cats.data.{NonEmptyList, Validated, ValidatedNel}
object Constants {
def parse(strs: List[String]): ValidatedNel[String, List[ConstantRaw]] = {
val parsed = strs.map(s => ConstantExpr.onlyLiteral.parseAll(s))

View File

@ -0,0 +1,22 @@
package aqua.errors
import sourcecode.{Enclosing, FileName, Line}
import scala.util.control.NoStackTrace
object Errors {
/**
* Internal error that should never happen.
* Use in case of broken invariants.
*/
def internalError(
msg: String
)(using file: FileName, line: Line, enclosing: Enclosing): Nothing = {
throw new RuntimeException(
s"Internal aqua compiler error:\n$msg" +
s"\nat ${file.value}:${line.value} in ${enclosing.value}.\n" +
s"Please report this issue to https://github.com/fluencelabs/aqua."
) with NoStackTrace // Stack trace is rather useless here
}
}

View File

@ -1,5 +1,8 @@
package aqua.logging
import cats.syntax.option.*
import cats.syntax.either.*
import cats.syntax.foldable.*
import cats.data.Validated.{invalidNel, validNel}
import cats.data.{NonEmptyList, Validated, ValidatedNel}
import scribe.Level
@ -12,20 +15,15 @@ case class LogLevels(
object LogLevels {
val logHelpMessage = "Format: '<level> OR <segment>=<level>[,]', where <level> is one of these strings: 'all', 'trace', 'debug', 'info', 'warn', 'error', 'off'. <segment> can be 'compiler', 'fluencejs' or 'aquavm'"
val logHelpMessage =
"Format: '<level> OR <segment>=<level>[,]', where <level> is one of these strings: 'all', 'trace', 'debug', 'info', 'warn', 'error', 'off'. <segment> can be 'compiler', 'fluencejs' or 'aquavm'"
def apply(level: Level): LogLevels = LogLevels(level, level, level)
def levelFromString(s: String): ValidatedNel[String, Level] = {
def levelFromString(s: String): ValidatedNel[String, Level] =
LogLevel.stringToLogLevel
.get(s.toLowerCase)
.map(validNel)
.getOrElse(
invalidNel(
s"Invalid log-level '$s'. $logHelpMessage"
)
)
}
.get(s.toLowerCase.trim())
.toValidNel(s"Invalid log-level '$s'. $logHelpMessage")
lazy val error =
s"Invalid log-level format. $logHelpMessage"
@ -36,7 +34,7 @@ object LogLevels {
logLevels: LogLevels
): Validated[NonEmptyList[String], LogLevels] = {
levelFromString(level).andThen { level =>
name match {
name.trim().toLowerCase() match {
case "compiler" =>
validNel(logLevels.copy(compiler = level))
case "fluencejs" =>
@ -44,7 +42,7 @@ object LogLevels {
case "aquavm" =>
validNel(logLevels.copy(aquavm = level))
case s =>
invalidNel[String, LogLevels](
invalidNel(
s"Unknown component '$s' in log-level. Please use one of these: 'aquavm', 'compiler' and 'fluencejs'"
)
}
@ -53,26 +51,15 @@ object LogLevels {
// Format: '<log-level>' or 'compiler=<log-level>,fluencejs=<log-level>,aquavm=<log-level>',
// where <log-level> is one of these strings: 'all', 'trace', 'debug', 'info', 'warn', 'error', 'off'
def fromString(s: String): ValidatedNel[String, LogLevels] = {
s.split(",").toList match {
case l :: Nil =>
l.split("=").toList match {
case n :: ll :: Nil => fromStrings(n, ll, LogLevels())
case ll :: Nil => levelFromString(ll).map(apply)
case _ => invalidNel(error)
def fromString(s: String): ValidatedNel[String, LogLevels] =
s.split(",")
.toList
.foldLeftM(LogLevels()) { case (levels, level) =>
level.split("=").toList match {
case n :: l :: Nil => fromStrings(n, l, levels).toEither
case l :: Nil => levelFromString(l).map(apply).toEither
case _ => invalidNel(error).toEither
}
case arr =>
arr.foldLeft(validNel[String, LogLevels](LogLevels())) { case (logLevelV, ss) =>
logLevelV.andThen { logLevels =>
ss.split("=").toList match {
case n :: ll :: Nil => fromStrings(n, ll, logLevels)
case n :: Nil => levelFromString(n).map(apply)
case _ => invalidNel[String, LogLevels](error)
}
}
}
}
}
}
.toValidated
}