fix(compiler): Return error if SemanticError occures [LNG-356] (#1126)

This commit is contained in:
Dima 2024-04-17 15:40:19 +03:00 committed by GitHub
parent f29e44e52a
commit e6c5d0039f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 216 additions and 107 deletions

View File

@ -1,10 +1,29 @@
aqua Main
aqua Job declares *
export main
export aaa, bbb
func reward(amount: ?u32) -> u32:
result = ?[amount! / 10, 0]
<- result!
data Peer:
id: string
func main(a: u32) -> u32:
<- reward(?[a])
func aaa() -> Peer:
peer1 = Peer(id = "123")
peer2 = Peer(id = peer1.id)
<- peer2
data BrokenStruct:
fff: UnknownType
alias BrokenAlias: str3ng
ability BrokenAbility:
fff: str4ng
const BROKEN_CONST = UNKNOWN_CONST
func bbb() -> string:
<- 323
func ccc() -> Peer:
peer1 = Peer(id = "123")
peer2 = Peer(id = peer1.id)
<- peer2

View File

@ -33,9 +33,9 @@ object LspContext {
def blank[S[_]]: LspContext[S] = LspContext[S](raw = RawContext())
given [S[_]]: Monoid[LspContext[S]] with {
override def empty = blank[S]
override def empty: LspContext[S] = blank[S]
override def combine(x: LspContext[S], y: LspContext[S]) =
override def combine(x: LspContext[S], y: LspContext[S]): LspContext[S] =
LspContext[S](
raw = x.raw |+| y.raw,
abDefinitions = x.abDefinitions ++ y.abDefinitions,
@ -52,7 +52,7 @@ object LspContext {
given [S[_]]: Picker[LspContext[S]] with {
import aqua.semantics.header.Picker.*
override def blank: LspContext[S] = LspContext[S](Picker[RawContext].blank, Map.empty)
override def blank: LspContext[S] = LspContext.blank[S]
override def exports(ctx: LspContext[S]): Map[String, Option[String]] = ctx.raw.exports
override def isAbility(ctx: LspContext[S], name: String): Boolean =

View File

@ -2,21 +2,15 @@ package aqua.lsp
import aqua.parser.Ast
import aqua.parser.head.{ImportExpr, ImportFromExpr, UseExpr, UseFromExpr}
import aqua.parser.lexer.{LiteralToken, Token}
import aqua.parser.lexer.LiteralToken
import aqua.raw.ConstantRaw
import aqua.semantics.*
import aqua.semantics.header.Picker.*
import aqua.semantics.rules.locations.LocationsState
import aqua.semantics.{CompilerState, RawSemantics, SemanticError, SemanticWarning, Semantics}
import cats.data.Validated.{Invalid, Valid}
import cats.data.{NonEmptyChain, ValidatedNec}
import cats.syntax.applicative.*
import cats.syntax.apply.*
import cats.syntax.either.*
import cats.syntax.flatMap.*
import cats.syntax.foldable.*
import cats.data.{EitherT, NonEmptyChain, Writer}
import cats.syntax.functor.*
import cats.syntax.reducible.*
import cats.syntax.applicative.*
import monocle.Lens
import monocle.macros.GenLens
@ -74,7 +68,6 @@ class LspSemantics[S[_]](
warnings = state.warnings.toList
).pure[Result]
}
// TODO: return as Eval
.value
}
}

View File

@ -3,10 +3,12 @@ package aqua.lsp
import aqua.compiler.FileIdString.given_FileId_String
import aqua.compiler.{AquaCompilerConf, AquaError, AquaSources}
import aqua.parser.Parser
import aqua.parser.lexer.Token
import aqua.parser.lift.Span
import aqua.parser.lift.Span.S
import aqua.raw.ConstantRaw
import aqua.semantics.rules.locations.{DefinitionInfo, TokenLocation, VariableInfo}
import aqua.semantics.{RulesViolated, SemanticError}
import aqua.types.*
import cats.Id
@ -131,6 +133,16 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
ee
}
def insideError(err: SemanticError[S], str: String, pos: Int, code: String) = {
inside(err) { case RulesViolated(token, _) =>
val span = token.unit._1
val locatedOp = getByPosition(code, str, pos)
locatedOp shouldBe defined
val located = locatedOp.get
(span.startIndex, span.endIndex) shouldBe (located._1, located._2)
}
}
it should "return right tokens" in {
val main =
"""aqua Import declares foo_wrapper, Ab, Str, useAbAndStruct, SOME_CONST
@ -153,6 +165,7 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
| num(someVar)
| OneMore fooResult
| OneMore.more_call()
| <- "123"
|
|ability Ab:
| someField: u32
@ -362,26 +375,26 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
| a: SomeAlias
|
|data SomeStruct:
| al: SomeAlias
| al11: SomeAlias
| nested: NestedStruct
|
|ability SomeAbility:
| someStr: SomeStruct
| nested: NestedStruct
| al: SomeAlias
| someFunc(ss: SomeStruct, nest: NestedStruct, al: SomeAlias) -> NestedStruct, SomeStruct, SomeAlias
| al11: SomeAlias
| someFunc(ss: SomeStruct, nest: NestedStruct, al11: SomeAlias) -> NestedStruct, SomeStruct, SomeAlias
|
|service Srv("a"):
| check(ss: SomeStruct, nest: NestedStruct, al: SomeAlias) -> NestedStruct
| check(ss: SomeStruct, nest: NestedStruct, al11: SomeAlias) -> NestedStruct
| check2() -> SomeStruct
| check3() -> SomeAlias
|
|func withAb{SomeAbility}() -> SomeStruct:
| Srv.check()
| Srv.check(SomeAbility.someStr, SomeAbility.nested, SomeAbility.al11)
| Srv.check2()
| <- SomeAbility.someStr
|
|func main(ss: SomeStruct, nest: NestedStruct, al: SomeAlias) -> string:
|func main(ss: SomeStruct, nest: NestedStruct, al11: SomeAlias) -> string:
| Srv.check3()
| <- ""
|""".stripMargin
@ -394,11 +407,11 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
val nestedType = StructType("NestedStruct", NonEmptyMap.of(("a", ScalarType.string)))
val someStr =
StructType("SomeStruct", NonEmptyMap.of(("nested", nestedType), ("al", ScalarType.string)))
StructType("SomeStruct", NonEmptyMap.of(("nested", nestedType), ("al11", ScalarType.string)))
val abFuncType = ArrowType(
ProductType.labelled(
("ss", someStr) :: ("nest", nestedType) :: ("al", ScalarType.string) :: Nil
("ss", someStr) :: ("nest", nestedType) :: ("al11", ScalarType.string) :: Nil
),
ProductType(nestedType :: someStr :: ScalarType.string :: Nil)
)
@ -407,7 +420,7 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
NonEmptyMap.of(
("someStr", someStr),
("nested", nestedType),
("al", ScalarType.string),
("al11", ScalarType.string),
("someFunc", abFuncType)
)
)
@ -419,7 +432,7 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
"check",
ArrowType(
ProductType.labelled(
("ss", someStr) :: ("nest", nestedType) :: ("al", ScalarType.string) :: Nil
("ss", someStr) :: ("nest", nestedType) :: ("al11", ScalarType.string) :: Nil
),
ProductType(nestedType :: Nil)
)
@ -445,10 +458,14 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
}
res.checkTokenLoc(main, "SomeAbility", 0, someAb) shouldBe true
// from {SomeAbility} to 'ability SomeAbility'
res.checkLocations("SomeAbility", 0, 1, main) shouldBe true
// from 'SomeAbility.someStr' to {SomeAbility}
res.checkLocations("SomeAbility", 0, 2, main) shouldBe true
Range.inclusive(1, 5).foreach { n =>
res.checkLocations("SomeAbility", 0, n, main) shouldBe true
}
res.checkLocations("someStr", 0, 1, main) shouldBe true
res.checkLocations("someStr", 0, 2, main) shouldBe true
res.checkLocations("nested", 1, 2, main) shouldBe true
res.checkLocations("al11", 1, 4, main) shouldBe true
res.checkTokenLoc(main, "Srv", 0, srvType) shouldBe true
Range.inclusive(1, 3).foreach { n =>
@ -538,4 +555,99 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
res.checkLocations("Abilyy", 0, 2, main) shouldBe true
res.checkLocations("Abilyy", 0, 3, main) shouldBe true
}
it should "return correct locations of errors on exported functions (LNG-356)" in {
val main =
"""aqua Job declares *
|
|export aaa
|
|data Peer:
| id: string
|
|func aaa() -> string:
| peer = Pe2er(id = "123")
| <- peer.id""".stripMargin
val src = Map(
"index.aqua" -> main
)
val imports = Map.empty[String, String]
val res = compile(src, imports).toEither.toOption.get.values.head
val errors = res.errors
insideError(errors.head, "Pe2er", 0, main)
insideError(errors(1), "peer", 1, main)
insideError(errors(2), "string", 1, main)
}
it should "return correct locations in functions even if there is errors in other parts of a code" in {
val main =
"""aqua Job declares *
|
|export aaa, bbb
|
|data Peer:
| id: string
|
|func aaa() -> Peer:
| peer1 = Peer(id = "123")
| peer2 = Peer(id = peer1.id)
| <- peer2
|
|data BrokenStruct:
| fff: UnknownType1
|
|alias BrokenAlias: UnknownType2
|
|ability BrokenAbility:
| fff: UnknownType3
|
|const BROKEN_CONST = UNKNOWN_CONST
|
|func bbb() -> string:
| <- 323
|
|func ccc() -> Peer:
| peer1 = Peer(id = "123")
| peer2 = Peer(id = peer1.id)
| <- peer2
|
|""".stripMargin
val src = Map(
"index.aqua" -> main
)
val imports = Map.empty[String, String]
val res = compile(src, imports).toOption.get.values.head
val errors = res.errors
// 'aaa' function
res.checkLocations("Peer", 0, 1, main) shouldBe true
res.checkLocations("Peer", 0, 2, main) shouldBe true
res.checkLocations("Peer", 0, 3, main) shouldBe true
res.checkLocations("peer1", 0, 1, main) shouldBe true
res.checkLocations("peer1", 0, 1, main) shouldBe true
// 'ccc' function
res.checkLocations("Peer", 0, 4, main) shouldBe true
res.checkLocations("Peer", 0, 5, main) shouldBe true
res.checkLocations("Peer", 0, 6, main) shouldBe true
res.checkLocations("peer1", 2, 3, main) shouldBe true
res.checkLocations("peer1", 2, 3, main) shouldBe true
// errors
insideError(errors.head, "UnknownType1", 0, main)
insideError(errors(1), "BrokenStruct", 0, main)
insideError(errors(2), "UnknownType2", 0, main)
insideError(errors(3), "UnknownType3", 0, main)
insideError(errors(4), "BrokenAbility", 0, main)
insideError(errors(5), "UNKNOWN_CONST", 0, main)
insideError(errors(6), "323", 0, main)
insideError(errors(7), "string", 1, main)
}
}

View File

@ -0,0 +1,10 @@
package aqua.raw
import aqua.types.{BottomType, Type}
// Part for structures that have errors inside but must be registered in context
case class ErroredPart(name: String) extends RawPart {
override def rawPartType: Type = BottomType
override def rename(s: String): RawPart = copy(name = s)
}

View File

@ -90,7 +90,7 @@ case class RawContext(
parts.map { case (_, p) => p.name }.toList.toSet
lazy val declaredNames: Set[String] =
allNames.filter(declares.contains)
allNames.intersect(declares)
override def toString: String =
s"""|module: ${module.getOrElse("unnamed")}
@ -109,7 +109,7 @@ object RawContext {
override def empty: RawContext = blank
override def combine(x: RawContext, y: RawContext) =
override def combine(x: RawContext, y: RawContext): RawContext =
RawContext(
x.module orElse y.module,
x.declares ++ y.declares,

View File

@ -56,7 +56,6 @@ class RawSemantics[S[_]](
)
)
}
// TODO: return as Eval
.value
}
}

View File

@ -1,24 +1,15 @@
package aqua.semantics.expr
import aqua.parser.expr.AbilityExpr
import aqua.raw.{Raw, TypeRaw}
import aqua.parser.lexer.{Name, NamedTypeToken}
import aqua.raw.{ErroredPart, Raw, TypeRaw}
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.abilities.AbilitiesAlgebra
import aqua.semantics.rules.definitions.DefinitionsAlgebra
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.{AbilityType, ArrowType, Type}
import cats.Monad
import cats.syntax.apply.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.applicative.*
import cats.syntax.semigroupal.*
import cats.syntax.traverse.*
import cats.Monad
import cats.data.{NonEmptyList, NonEmptyMap}
class AbilitySem[S[_]](val expr: AbilityExpr[S]) extends AnyVal {
@ -32,7 +23,7 @@ class AbilitySem[S[_]](val expr: AbilityExpr[S]) extends AnyVal {
fields = defs.view.mapValues(d => d.name -> d.`type`).toMap
abilityType <- T.defineAbilityType(expr.name, fields)
result = abilityType.map(st => TypeRaw(expr.name.value, st))
} yield result.getOrElse(Raw.error("Ability types unresolved"))
} yield result.getOrElse(ErroredPart(expr.name.value))
)
}
}

View File

@ -1,10 +1,9 @@
package aqua.semantics.expr
import aqua.parser.expr.AliasExpr
import aqua.raw.{Raw, TypeRaw}
import aqua.raw.{ErroredPart, Raw, TypeRaw}
import aqua.semantics.Prog
import aqua.semantics.rules.types.TypesAlgebra
import cats.Applicative
import cats.Monad
import cats.syntax.flatMap.*
@ -15,6 +14,6 @@ class AliasSem[S[_]](val expr: AliasExpr[S]) extends AnyVal {
def program[Alg[_]: Monad](using T: TypesAlgebra[S, Alg]): Prog[Alg, Raw] =
T.resolveType(expr.target).flatMap {
case Some(t) => T.defineAlias(expr.name, t) as (TypeRaw(expr.name.value, t): Raw)
case None => Applicative[Alg].pure(Raw.error("Alias type unresolved"))
case None => Applicative[Alg].pure(ErroredPart(expr.name.value))
}
}

View File

@ -1,7 +1,7 @@
package aqua.semantics.expr
import aqua.parser.expr.ConstantExpr
import aqua.raw.{ConstantRaw, Raw}
import aqua.raw.{ConstantRaw, ErroredPart, Raw}
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.names.NamesAlgebra
@ -27,12 +27,12 @@ class ConstantSem[S[_]](val expr: ConstantExpr[S]) extends AnyVal {
case true =>
Raw.empty(s"Constant with name ${expr.name} was already defined, skipping")
case false =>
Raw.error(s"Constant with name ${expr.name} was defined with different type")
ErroredPart(expr.name.value)
}
case (Some(_), _, _) =>
Raw.error(s"Name '${expr.name.value}' was already defined").pure[Alg]
case (_, None, _) =>
Raw.error(s"There is no such variable ${expr.value}").pure[Alg]
ErroredPart(expr.name.value).pure[Alg]
case (_, Some(t), _) =>
N.defineConstant(expr.name, t._2) as (ConstantRaw(
expr.name.value,

View File

@ -1,13 +1,12 @@
package aqua.semantics.expr
import aqua.parser.expr.DataStructExpr
import aqua.raw.{Raw, TypeRaw}
import aqua.raw.{ErroredPart, Raw, TypeRaw}
import aqua.semantics.Prog
import aqua.semantics.rules.definitions.DefinitionsAlgebra
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import aqua.types.StructType
import cats.syntax.functor.*
import cats.syntax.applicative.*
import cats.syntax.traverse.*
@ -26,7 +25,7 @@ class DataStructSem[S[_]](val expr: DataStructExpr[S]) extends AnyVal {
fields = defs.view.mapValues(d => d.name -> d.`type`).toMap
structType <- T.defineStructType(expr.name, fields)
result = structType.map(st => TypeRaw(expr.name.value, st))
} yield result.getOrElse(Raw.error("Data struct types unresolved"))
} yield result.getOrElse(ErroredPart(expr.name.value))
)
}

View File

@ -1,7 +1,8 @@
package aqua.semantics.expr
import aqua.helpers.syntax.optiont.withFilterF
import aqua.parser.expr.ServiceExpr
import aqua.raw.{Raw, ServiceRaw}
import aqua.raw.{ErroredPart, Raw, ServiceRaw}
import aqua.semantics.Prog
import aqua.semantics.rules.ValuesAlgebra
import aqua.semantics.rules.abilities.AbilitiesAlgebra
@ -9,16 +10,16 @@ import aqua.semantics.rules.definitions.DefinitionsAlgebra
import aqua.semantics.rules.names.NamesAlgebra
import aqua.semantics.rules.types.TypesAlgebra
import cats.data.EitherT
import cats.syntax.either.*
import cats.syntax.option.*
import cats.syntax.apply.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.traverse.*
import cats.syntax.foldable.*
import cats.syntax.applicative.*
import cats.Monad
import cats.data.{EitherT, OptionT}
import cats.syntax.applicative.*
import cats.syntax.apply.*
import cats.syntax.either.*
import cats.syntax.flatMap.*
import cats.syntax.foldable.*
import cats.syntax.functor.*
import cats.syntax.option.*
import cats.syntax.traverse.*
class ServiceSem[S[_]](val expr: ServiceExpr[S]) extends AnyVal {
@ -27,44 +28,30 @@ class ServiceSem[S[_]](val expr: ServiceExpr[S]) extends AnyVal {
T: TypesAlgebra[S, Alg],
V: ValuesAlgebra[S, Alg],
D: DefinitionsAlgebra[S, Alg]
): EitherT[Alg, Raw, ServiceRaw] = for {
arrows <- EitherT.fromOptionF(
// TODO: Move to purgeDefs here, allow not only arrows
// from parsing, throw errors here
D.purgeArrows(expr.name),
Raw.error("Service has no arrows")
)
arrowsByName = arrows.map { case (name, arrow) =>
name.value -> (name, arrow)
}.toNem
defaultId <- expr.id.traverse(id =>
EitherT.fromOptionF(
V.valueToStringRaw(id),
Raw.error("Failed to resolve default service id")
)
)
serviceType <- EitherT.fromOptionF(
T.defineServiceType(expr.name, arrowsByName.toSortedMap),
Raw.error("Failed to define service type")
)
arrowsDefs = arrows.map { case (name, _) => name.value -> name }.toNem
_ <- EitherT(
A.defineService(
expr.name,
arrowsDefs,
): EitherT[Alg, Raw, ServiceRaw] = {
(
for {
arrows <- OptionT(D.purgeArrows(expr.name))
arrowsByName = arrows.map { case (name, arrow) =>
name.value -> (name, arrow)
}.toNem
defaultId <- expr.id.traverse(id => OptionT(V.valueToStringRaw(id)))
serviceType <- OptionT(T.defineServiceType(expr.name, arrowsByName.toSortedMap))
arrowsDefs = arrows.map { case (name, _) => name.value -> name }.toNem
_ <- OptionT.withFilterF(
A.defineService(
expr.name,
arrowsDefs,
defaultId
)
)
} yield ServiceRaw(
expr.name.value,
serviceType,
defaultId
).map(defined =>
Raw
.error("Service not created due to validation errors")
.asLeft
.whenA(!defined)
)
)
} yield ServiceRaw(
expr.name.value,
serviceType,
defaultId
)
).toRight(ErroredPart(expr.name.value))
}
def program[Alg[_]: Monad](using
A: AbilitiesAlgebra[S, Alg],

View File

@ -1,6 +1,6 @@
package aqua.semantics.expr.func
import aqua.raw.Raw
import aqua.raw.{ErroredPart, Raw}
import aqua.raw.arrow.{ArrowRaw, FuncRaw}
import aqua.parser.expr.FuncExpr
import aqua.parser.lexer.Arg
@ -22,7 +22,7 @@ class FuncSem[S[_]](val expr: FuncExpr[S]) extends AnyVal {
N.defineArrow(expr.name, arrow.`type`, isRoot = true) as FuncRaw(expr.name.value, arrow)
case _ =>
Raw.error("Func must continue with an arrow definition").pure[Alg]
ErroredPart(expr.name.value).pure[Alg]
}
}