feat(compiler): Enhance message of type error [LNG-313] (#1033)

This commit is contained in:
InversionSpaces 2024-01-09 13:15:27 +01:00 committed by GitHub
parent ae32f80277
commit d5cd77bb86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 74 additions and 19 deletions

View File

@ -17,6 +17,7 @@ import cats.data.{NonEmptyList, OptionT}
import cats.instances.list.* import cats.instances.list.*
import cats.syntax.applicative.* import cats.syntax.applicative.*
import cats.syntax.apply.* import cats.syntax.apply.*
import cats.syntax.bifunctor.*
import cats.syntax.flatMap.* import cats.syntax.flatMap.*
import cats.syntax.foldable.* import cats.syntax.foldable.*
import cats.syntax.functor.* import cats.syntax.functor.*
@ -136,27 +137,23 @@ class ValuesAlgebra[S[_], Alg[_]: Monad](using
reportNamedArgsDuplicates(fields) reportNamedArgsDuplicates(fields)
) )
fieldsGiven <- fields fieldsGiven <- fields
.traverse(arg => OptionT(valueToRaw(arg.argValue)).map(arg.argName.value -> _)) .traverse(arg =>
.map(_.toNem) // Take only last value for a field OptionT(valueToRaw(arg.argValue)).map(valueRaw =>
fieldsGivenTypes = fieldsGiven.map(_.`type`) arg.argName.value -> (arg, valueRaw)
generated <- OptionT.fromOption(
resolvedType match {
case struct: StructType =>
(
struct.copy(fields = fieldsGivenTypes),
MakeStructRaw(fieldsGiven, struct)
).some
case ability: AbilityType =>
(
ability.copy(fields = fieldsGivenTypes),
AbilityRaw(fieldsGiven, ability)
).some
}
) )
(genType, genData) = generated )
.map(_.toNem) // Take only last value for a field
fieldsGivenRaws = fieldsGiven.map { case (_, raw) => raw }
fieldsGivenTypes = fieldsGiven.map(_.map(_.`type`))
generated = resolvedType match {
case struct: StructType =>
MakeStructRaw(fieldsGivenRaws, struct)
case ability: AbilityType =>
AbilityRaw(fieldsGivenRaws, ability)
}
data <- OptionT.whenM( data <- OptionT.whenM(
T.ensureTypeMatches(dvt, resolvedType, genType) T.ensureTypeConstructibleFrom(dvt, resolvedType, fieldsGivenTypes)
)(genData.pure) )(generated.pure)
} yield data).value } yield data).value
case ct @ CollectionToken(_, values) => case ct @ CollectionToken(_, values) =>

View File

@ -60,6 +60,22 @@ trait TypesAlgebra[S[_], Alg[_]] {
def ensureTypeMatches(token: Token[S], expected: Type, givenType: Type): Alg[Boolean] def ensureTypeMatches(token: Token[S], expected: Type, givenType: Type): Alg[Boolean]
/**
* Check if given type (ability or struct)
* can be constructed from given arguments
*
* @param token token of construction expression (for error reporting)
* @param expected type to construct
* @param arguments arguments to construct with (name -> (named arg, type))
* @return true if type can be constructed from given arguments
* reports error and warnings if necessary
*/
def ensureTypeConstructibleFrom(
token: Token[S],
expected: AbilityType | StructType,
arguments: NonEmptyMap[String, (NamedArg[S], Type)]
): Alg[Boolean]
def typeToCollectible(token: Token[S], givenType: Type): OptionT[Alg, CollectibleType] def typeToCollectible(token: Token[S], givenType: Type): OptionT[Alg, CollectibleType]
def typeToStream(token: Token[S], givenType: Type): OptionT[Alg, StreamType] def typeToStream(token: Token[S], givenType: Type): OptionT[Alg, StreamType]

View File

@ -2,6 +2,7 @@ package aqua.semantics.rules.types
import aqua.parser.lexer.* import aqua.parser.lexer.*
import aqua.raw.value.* import aqua.raw.value.*
import aqua.semantics.Levenshtein
import aqua.semantics.rules.StackInterpreter import aqua.semantics.rules.StackInterpreter
import aqua.semantics.rules.locations.{DefinitionInfo, LocationsAlgebra} import aqua.semantics.rules.locations.{DefinitionInfo, LocationsAlgebra}
import aqua.semantics.rules.report.ReportAlgebra import aqua.semantics.rules.report.ReportAlgebra
@ -415,6 +416,47 @@ class TypesInterpreter[S[_], X](using
} }
} }
override def ensureTypeConstructibleFrom(
token: Token[S],
expected: AbilityType | StructType,
arguments: NonEmptyMap[String, (NamedArg[S], Type)]
): State[X, Boolean] = for {
/* Check that required fields are present
among arguments and have correct types */
enough <- expected.fields.toNel.traverse { case (name, typ) =>
arguments.lookup(name) match {
case Some(arg -> givenType) =>
ensureTypeMatches(arg.argValue, typ, givenType)
case None =>
report
.error(
token,
s"Missing argument '$name' of type '$typ'"
)
.as(false)
}
}.map(_.forall(identity))
expectedKeys = expected.fields.keys.toNonEmptyList
/* Report unexpected arguments */
_ <- arguments.toNel.traverse_ { case (name, arg -> typ) =>
expected.fields.lookup(name) match {
case Some(_) => State.pure(())
case None =>
lazy val similar = Levenshtein
.mostSimilar(name, expectedKeys, 3)
.map(s => s"'$s'")
.mkString(", ")
val message =
if (enough)
s"Unexpected argument '$name'"
else
s"Unexpected argument '$name', did you mean $similar?"
report.warning(arg.argName, message)
}
}
} yield enough
private def typeTo[T <: Type]( private def typeTo[T <: Type](
token: Token[S], token: Token[S],
givenType: Type, givenType: Type,