mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-04 22:50:18 +00:00
Typechecker typechecks
This commit is contained in:
parent
2276f27389
commit
4356620579
@ -1,5 +1,5 @@
|
||||
data Inside:
|
||||
foo: i32
|
||||
foo: u32
|
||||
bar: bool
|
||||
|
||||
data Wrapping:
|
||||
@ -11,7 +11,7 @@ data Enclosing:
|
||||
arr: []Wrapping
|
||||
flag: bool
|
||||
|
||||
func call(enc: Enclosing, oni32: i32 -> (), onString: string -> (), onBool: bool -> (), onInside: Inside -> Inside) -> Inside:
|
||||
func call(enc: Enclosing, oni32: u32 -> (), onString: string -> (), onBool: bool -> (), onInside: Inside -> Inside) -> Inside:
|
||||
onBool(enc.flag)
|
||||
onBool(enc.wrap)
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
package aqua
|
||||
|
||||
import aqua.context.{Abilities, AbilitiesResolve, ArgsAndVars, Arrows, Types}
|
||||
import aqua.context.{Abilities, AbilitiesResolve, ArgsAndVars, Arrows, Types, VarTypes}
|
||||
import aqua.context.scope.ScopeWalker
|
||||
import aqua.context.walker.Walker
|
||||
import aqua.parser.Block
|
||||
@ -13,7 +13,7 @@ import shapeless.HNil
|
||||
object Aqua {
|
||||
private val parser: P0[List[Block[Span.F, HNil]]] = Block.blocks[Span.F]
|
||||
|
||||
val walker =
|
||||
val step1 =
|
||||
Walker
|
||||
.hnil[Span.F]
|
||||
.andThen(new ScopeWalker(_))
|
||||
@ -23,7 +23,10 @@ object Aqua {
|
||||
.andThen(new Abilities.ExpDef(_))
|
||||
.andThen(new AbilitiesResolve.ExpDef(_))
|
||||
|
||||
def parse(input: String): ValidatedNel[AquaError, List[Block[Span.F, walker.Out]]] =
|
||||
val step2 =
|
||||
new VarTypes.Checker[Span.F, step1.Out, step1.Out](Walker.noopFrom(step1))
|
||||
|
||||
def parse(input: String): ValidatedNel[AquaError, List[Block[Span.F, step2.Out]]] =
|
||||
Validated
|
||||
.fromEither(
|
||||
parser
|
||||
@ -32,6 +35,9 @@ object Aqua {
|
||||
.map(pe => NonEmptyList.one[AquaError](SyntaxError(pe.failedAtOffset, pe.expected)))
|
||||
)
|
||||
.andThen { blocks =>
|
||||
walker.walkValidate(blocks).leftMap(_.map(_.toStringF).map(sv => WalkerError(sv._1, sv._2)))
|
||||
step1.walkValidate(blocks).leftMap(_.map(_.toStringF).map(sv => WalkerError(sv._1, sv._2)))
|
||||
}
|
||||
.andThen { blocks =>
|
||||
step2.walkValidate(blocks).leftMap(_.map(_.toStringF).map(sv => WalkerError(sv._1, sv._2)))
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import aqua.context.walker.Walker.{DupError, UnresolvedError}
|
||||
import aqua.context.walker.{Acc, ExpectAndDefine, Walker}
|
||||
import aqua.interim.{ArrayType, ArrowType, DataType, ProductType, Type}
|
||||
import aqua.parser.{Block, DefAlias, DefFunc, DefService, DefType, FuncExpr}
|
||||
import aqua.parser.lexer.{ArrayTypeToken, ArrowTypeToken, BasicTypeToken, CustomTypeToken, TypeToken}
|
||||
import aqua.parser.lexer.{ArrayTypeToken, ArrowDef, ArrowTypeToken, BasicTypeToken, CustomTypeToken, TypeToken}
|
||||
import cats.data.NonEmptyMap
|
||||
import cats.{Comonad, Functor}
|
||||
import shapeless._
|
||||
@ -20,6 +20,9 @@ case class Types[F[_]](
|
||||
|
||||
def resolveTypeToken(tt: TypeToken[F])(implicit F: Comonad[F]): Option[Type] =
|
||||
Types.resolveTypeToken(strict, tt)
|
||||
|
||||
def resolveArrowDef(ad: ArrowDef[F])(implicit F: Comonad[F]): Option[ArrowType] =
|
||||
Types.resolveArrowDef(strict, ad)
|
||||
}
|
||||
|
||||
object Types {
|
||||
@ -44,6 +47,17 @@ object Types {
|
||||
)
|
||||
}
|
||||
|
||||
def resolveArrowDef[F[_]: Comonad](strict: Map[String, Type], ad: ArrowDef[F]): Option[ArrowType] =
|
||||
ad.resType.flatMap(resolveTypeToken(strict, _)) match {
|
||||
case resType if resType.isDefined == ad.resType.isDefined =>
|
||||
ad.argTypes.flatMap(resolveTypeToken(strict, _)) match {
|
||||
case argTypes if argTypes.length == ad.argTypes.length => Some(ArrowType(argTypes, resType))
|
||||
case _ => None
|
||||
}
|
||||
|
||||
case _ => None
|
||||
}
|
||||
|
||||
type Acc[F[_]] = ExpectAndDefine[CustomTypeToken[F], TypeMarker[F]]
|
||||
def emptyAcc[F[_]]: Acc[F] = ExpectAndDefine.empty[F, CustomTypeToken[F], TypeMarker[F]]
|
||||
def empty[F[_]]: Types[F] = Types[F](emptyAcc[F], Map.empty)
|
||||
|
@ -1,10 +1,11 @@
|
||||
package aqua.context
|
||||
|
||||
import aqua.context.marker.FuncArgMarker
|
||||
import aqua.context.walker.Walker
|
||||
import aqua.context.walker.Walker.UnresolvedError
|
||||
import aqua.interim.{ScalarType, Type}
|
||||
import aqua.parser.lexer.{ArrowDef, ArrowTypeToken, BasicTypeToken, DataTypeToken, Literal, TypeToken, VarLambda}
|
||||
import aqua.parser.{Block, Extract, FuncExpr}
|
||||
import aqua.interim.{ArrayType, ArrowType, ProductType, ScalarType, Type}
|
||||
import aqua.parser.lexer.{ArrowDef, DataTypeToken, IntoArray, IntoField, LambdaOp, Literal, TypeToken, Value, VarLambda}
|
||||
import aqua.parser.{Block, CallExpr, Extract, FuncExpr}
|
||||
import cats.{Comonad, Functor}
|
||||
import shapeless._
|
||||
import shapeless.ops.hlist.Selector
|
||||
@ -28,12 +29,38 @@ case class VarTypes[F[_]](
|
||||
object VarTypes {
|
||||
sealed trait Err[F[_]] extends UnresolvedError[F]
|
||||
|
||||
case class TypeMismatch[F[_]](point: F[Unit], expected: TypeToken[F], given: TypeToken[F]) extends Err[F] {
|
||||
case class TypeMismatch[F[_]](point: F[Unit], expected: Type, given: Type) extends Err[F] {
|
||||
|
||||
override def toStringF(implicit F: Functor[F]): F[String] =
|
||||
point.as(s"Type mismatch, expected: `$expected`, given: `$given`")
|
||||
}
|
||||
|
||||
case class TypeUndefined[F[_]](point: F[String]) extends Err[F] {
|
||||
|
||||
override def toStringF(implicit F: Functor[F]): F[String] =
|
||||
point.map(v => s"Undefined: $v")
|
||||
}
|
||||
|
||||
case class NotAnArray[F[_]](point: F[Unit], t: Type) extends Err[F] {
|
||||
|
||||
override def toStringF(implicit F: Functor[F]): F[String] =
|
||||
point.as(s"Expected array, but type is $t")
|
||||
}
|
||||
|
||||
case class ExpectedProduct[F[_]](point: F[String], t: Type) extends Err[F] {
|
||||
|
||||
override def toStringF(implicit F: Functor[F]): F[String] =
|
||||
point.map(f => s"Expected product with field `$f`, but type is $t")
|
||||
}
|
||||
|
||||
case class FieldNotFound[F[_]](point: F[String], t: ProductType) extends Err[F] {
|
||||
|
||||
override def toStringF(implicit F: Functor[F]): F[String] =
|
||||
point.map(f =>
|
||||
s"Expected product with field `$f`, but type `${t.name}` has only `${t.fields.keys.toNonEmptyList.toList.mkString("`, `")}`"
|
||||
)
|
||||
}
|
||||
|
||||
case class LiteralTypeMismatch[F[_]: Comonad](
|
||||
point: F[Unit],
|
||||
expected: TypeToken[F],
|
||||
@ -74,77 +101,114 @@ object VarTypes {
|
||||
|
||||
class Checker[F[_]: Comonad, I <: HList, O <: HList](extend: Walker[F, I, O])(implicit
|
||||
getArrows: Selector[I, Arrows[F]],
|
||||
getTypes: Selector[I, Types[F]]
|
||||
getTypes: Selector[O, Types[F]],
|
||||
getArgsAndVars: Selector[I, ArgsAndVars[F]]
|
||||
) extends Walker[F, I, VarTypes[F] :: O] {
|
||||
type Ctx = VarTypes[F] :: O
|
||||
|
||||
override def exitFuncExprGroup(group: FuncExpr[F, I], last: Ctx): Ctx =
|
||||
last.head :: extend.exitFuncExprGroup(group, last.tail)
|
||||
|
||||
def getArrowDef(name: String, ctx: I): Option[ArrowDef[F]] =
|
||||
getArrows(ctx).expDef.defineAcc.get(name).map(_.arrowDef)
|
||||
def getArrowDef(name: String, inCtx: I, ctx: Ctx): Option[ArrowDef[F]] =
|
||||
getArrows(inCtx).expDef.defineAcc.get(name).map(_.arrowDef)
|
||||
|
||||
def getArrowType(name: String, inCtx: I, ctx: Ctx): Option[ArrowType] =
|
||||
getArrowDef(name, inCtx, ctx).flatMap(getTypes(ctx.tail).resolveArrowDef(_))
|
||||
|
||||
def resolveIdent(name: F[String], inCtx: I, prev: Ctx): Either[Err[F], Type] =
|
||||
prev.head.vars
|
||||
.get(name.extract)
|
||||
.orElse(
|
||||
getArgsAndVars(inCtx).expDef.defineAcc
|
||||
.get(name.extract)
|
||||
.collect {
|
||||
case FuncArgMarker(_, dt) =>
|
||||
getTypes(prev.tail).resolveTypeToken(dt)
|
||||
}
|
||||
.flatten
|
||||
)
|
||||
.orElse(
|
||||
getArrowType(name.extract, inCtx, prev)
|
||||
)
|
||||
.toRight(TypeUndefined(name))
|
||||
|
||||
def resolveOp(rootT: Type, ops: List[LambdaOp[F]]): Either[Err[F], Type] =
|
||||
ops.headOption.fold[Either[Err[F], Type]](Right(rootT)) {
|
||||
case IntoArray(f) =>
|
||||
rootT match {
|
||||
case ArrayType(intern) => resolveOp(intern, ops.tail).map[Type](ArrayType)
|
||||
case _ => Left(NotAnArray(f, rootT))
|
||||
}
|
||||
case IntoField(name) =>
|
||||
rootT match {
|
||||
case pt @ ProductType(_, fields) =>
|
||||
fields(name.extract)
|
||||
.toRight(FieldNotFound(name, pt))
|
||||
.flatMap(resolveOp(_, ops.tail))
|
||||
case _ => Left(ExpectedProduct(name, rootT))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def resolveValueType(v: Value[F], inCtx: I, prev: Ctx): Either[Err[F], Type] =
|
||||
v match {
|
||||
case Literal(_, ts) => Right(ts) // We want to collect errors with pointers!
|
||||
case VarLambda(name, lambda) =>
|
||||
resolveIdent(name, inCtx, prev).flatMap(resolveOp(_, lambda))
|
||||
|
||||
}
|
||||
|
||||
def funcCall(
|
||||
fc: CallExpr[F, I],
|
||||
arrowDef: ArrowDef[F],
|
||||
prev: Ctx
|
||||
): VarTypes[F] = {
|
||||
val funcType = getTypes(prev.tail).resolveArrowDef(arrowDef)
|
||||
val (valueErrs, valueTypes) = fc.args
|
||||
.map(v => resolveValueType(v, fc.context, prev).map(_ -> v))
|
||||
.foldLeft[(Queue[Err[F]], Queue[(Type, Value[F])])](Queue.empty -> Queue.empty) {
|
||||
case ((errs, args), Right(t)) => (errs, args.appended(t))
|
||||
case ((errs, args), Left(t)) => (errs.appended(t), args)
|
||||
}
|
||||
|
||||
if (valueErrs.nonEmpty) valueErrs.foldLeft(prev.head)(_.error(_))
|
||||
else {
|
||||
funcType.fold(prev.head) {
|
||||
case ft if ft.args.length != valueTypes.length =>
|
||||
prev.head.error(ArgNumMismatch(fc.arrow.unit, ft.args.length, valueTypes.length))
|
||||
case ft =>
|
||||
ft.args.zip(valueTypes).foldLeft(prev.head) {
|
||||
case (acc, (expectedType, (givenType, _))) if expectedType.acceptsValueOf(givenType) => acc
|
||||
case (acc, (expectedType, (givenType, v))) =>
|
||||
acc.error(TypeMismatch(v.unit, expectedType, givenType))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def funcOpCtx(op: FuncExpr[F, I], prev: Ctx): Ctx =
|
||||
(op match {
|
||||
case Extract(vr, c, ectx) =>
|
||||
getArrowDef(c.arrow.name.extract, ectx)
|
||||
.fold(prev.head.error(ArrowUntyped(c.arrow.unit, c.arrow.name.extract))) { arrowDef =>
|
||||
val types = getTypes(ectx)
|
||||
case Extract(vr, fc, _) =>
|
||||
getArrowDef(fc.arrow.name.extract, fc.context, prev)
|
||||
.fold(prev.head.error(ArrowUntyped(fc.arrow.unit, fc.arrow.name.extract))) { arrowDef =>
|
||||
val withFC = funcCall(fc, arrowDef, prev)
|
||||
|
||||
val withResultType =
|
||||
arrowDef.resType.flatMap(types.resolveTypeToken).fold(prev.head)(prev.head.resolve(vr.name.extract, _))
|
||||
|
||||
val valueTypes = c.args.map {
|
||||
case Literal(_, ts) => ts // We want to collect errors with pointers!
|
||||
case VarLambda(name, Nil) =>
|
||||
// variable
|
||||
// or argument
|
||||
// or arrow
|
||||
case VarLambda(name, lambda) =>
|
||||
// variable
|
||||
// or argument
|
||||
}
|
||||
|
||||
val args = arrowDef.argTypes
|
||||
|
||||
// args.zip(c.args).foldLeft(checkArgsNum) {
|
||||
// case (acc, (BasicTypeToken(v), Literal(_, ts))) if ts.contains(v.extract) => acc
|
||||
// case (acc, (t, v @ Literal(_, _))) => acc.error(LiteralTypeMismatch(v.unit, t, v.ts))
|
||||
// case (acc, (t: ArrowTypeToken[F], VarLambda(name, Nil))) =>
|
||||
// getArrowDef(name.extract, ectx).fold(
|
||||
// acc.error(ArrowUntyped(name.void, name.extract))
|
||||
// )(vat =>
|
||||
// types.resolveTypeToken(t).map(_.acceptsValueOf(vat.argTypes))
|
||||
// // TODO matcher.isArrowSubtype(t, vat)
|
||||
// (t.resType, vat.resType) match {
|
||||
// case (None, None) => acc
|
||||
// case (Some(tr), Some(vr)) if isSubtype(ectx, tr, vr) => acc
|
||||
// case _ => acc.error(ArrowResultMismatch(name.void, t.resType, vat.resType))
|
||||
// }
|
||||
// )
|
||||
//
|
||||
// case (acc, (t, VarLambda(name, lambda))) =>
|
||||
// // TODO find var type
|
||||
// acc.derived
|
||||
// .get(name.extract)
|
||||
// .fold(
|
||||
// // TODO undefined variable
|
||||
// acc
|
||||
// )(_ =>
|
||||
// // TODO traverse lambda, find subtypes
|
||||
// // TODO finally, check if resulting type is a subtype of expected type
|
||||
// acc
|
||||
// )
|
||||
// }
|
||||
???
|
||||
arrowDef.resType
|
||||
.flatMap(getTypes(prev.tail).resolveTypeToken)
|
||||
.fold(withFC)(withFC.resolve(vr.name.extract, _))
|
||||
}
|
||||
|
||||
case fc: CallExpr[F, I] =>
|
||||
getArrowDef(fc.arrow.name.extract, fc.context, prev)
|
||||
.fold(prev.head.error(ArrowUntyped(fc.arrow.unit, fc.arrow.name.extract))) { arrowDef =>
|
||||
funcCall(fc, arrowDef, prev)
|
||||
}
|
||||
prev.head
|
||||
|
||||
case _ =>
|
||||
prev.head
|
||||
}) :: extend.funcOpCtx(op, prev.tail)
|
||||
|
||||
// TODO fetch argument types
|
||||
override def blockCtx(block: Block[F, I]): Ctx =
|
||||
VarTypes[F]() :: extend.blockCtx(block)
|
||||
|
||||
|
@ -12,7 +12,3 @@ case class FuncArgMarker[F[_]](v: Var[F], dt: DataTypeToken[F]) extends ArgVarMa
|
||||
case class ExtractedVarMarker[F[_], L](v: Var[F], extract: Extract[F, L]) extends ArgVarMarker[F] {
|
||||
override def pointer: Token[F] = v
|
||||
}
|
||||
|
||||
case class TypedVarMarker[F[_], L](v: Var[F], dt: DataTypeToken[F]) extends ArgVarMarker[F] {
|
||||
override def pointer: Token[F] = v
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ trait Walker[F[_], I <: HList, O <: HList] {
|
||||
|
||||
def mapBlock(block: Block[F, I], prevCtx: Out): (List[Walker.Error[F]], Block[F, Out]) = {
|
||||
val ctx = blockCtx(block)
|
||||
|
||||
val dupErr = duplicates(prevCtx, ctx)
|
||||
val (unresolvedErrs, combinedBlock) = block match {
|
||||
case df @ DefFunc(_, body, _) =>
|
||||
@ -70,8 +71,6 @@ trait Walker[F[_], I <: HList, O <: HList] {
|
||||
unresErrs -> dt.copy(context = bCtx)
|
||||
}
|
||||
|
||||
println(Console.BLUE + combinedBlock.context + Console.RESET)
|
||||
|
||||
(dupErr ::: unresolvedErrs) -> combinedBlock
|
||||
}
|
||||
|
||||
@ -134,4 +133,21 @@ object Walker {
|
||||
|
||||
override def unresolved(ctx: Out): (List[UnresolvedError[F]], Out) = Nil -> ctx
|
||||
}
|
||||
|
||||
def noopFrom[F[_], I <: HList, O <: HList](from: Walker[F, I, O]): Walker[F, O, O] =
|
||||
new Walker[F, O, O] {
|
||||
override def exitFuncExprGroup(group: FuncExpr[F, O], last: O): O = last
|
||||
|
||||
override def funcOpCtx(op: FuncExpr[F, O], prev: O): O = prev
|
||||
|
||||
override def blockCtx(block: Block[F, O]): O = block.context
|
||||
|
||||
override def emptyCtx: Out = from.emptyCtx
|
||||
|
||||
override def combineBlockCtx(prev: Out, block: Out): Out = block
|
||||
|
||||
override def duplicates(prev: Out, next: Out): List[DupError[F]] = Nil
|
||||
|
||||
override def unresolved(ctx: Out): (List[UnresolvedError[F]], Out) = (Nil, ctx)
|
||||
}
|
||||
}
|
||||
|
@ -56,35 +56,30 @@ object LiteralType {
|
||||
val string = LiteralType(Set(ScalarType.string))
|
||||
}
|
||||
|
||||
case class ArrayType(element: DataType) extends DataType
|
||||
case class ProductType(name: String, fields: NonEmptyMap[String, DataType]) extends DataType
|
||||
case class ArrayType(element: Type) extends DataType
|
||||
case class ProductType(name: String, fields: NonEmptyMap[String, Type]) extends DataType
|
||||
|
||||
sealed trait CallableType extends Type {
|
||||
def acceptsValuesOf(valueTypes: List[Type]): Boolean
|
||||
def acceptsAsArguments(valueTypes: List[Type]): Boolean
|
||||
def args: List[Type]
|
||||
def res: Option[Type]
|
||||
}
|
||||
|
||||
case class ArrowType(args: List[DataType], res: Option[DataType]) extends CallableType {
|
||||
case class ArrowType(args: List[Type], res: Option[Type]) extends CallableType {
|
||||
|
||||
override def acceptsValuesOf(valueTypes: List[Type]): Boolean =
|
||||
override def acceptsAsArguments(valueTypes: List[Type]): Boolean =
|
||||
(args.length == valueTypes.length) && args.zip(valueTypes).forall(av => av._1.acceptsValueOf(av._2))
|
||||
|
||||
}
|
||||
|
||||
case class FuncArrowType(args: List[(String, Either[ArrowType, DataType])], res: Option[DataType])
|
||||
extends CallableType {
|
||||
case class FuncArrowType(funcArgs: List[(String, Type)], res: Option[Type]) extends CallableType {
|
||||
|
||||
def toArrowType: Option[ArrowType] = {
|
||||
val dataArgs = args.map(_._2).collect {
|
||||
case Right(dt) => dt
|
||||
}
|
||||
Option.when(dataArgs.length == args.length)(ArrowType(dataArgs, res))
|
||||
}
|
||||
lazy val toArrowType: ArrowType = ArrowType(funcArgs.map(_._2), res)
|
||||
|
||||
override def acceptsValuesOf(valueTypes: List[Type]): Boolean =
|
||||
(args.length == valueTypes.length) && args
|
||||
.map(_._2)
|
||||
.zip(valueTypes)
|
||||
.forall(av => av._1.fold(identity, identity).acceptsValueOf(av._2))
|
||||
override def acceptsAsArguments(valueTypes: List[Type]): Boolean =
|
||||
toArrowType.acceptsAsArguments(valueTypes)
|
||||
|
||||
override def args: List[Type] = toArrowType.args
|
||||
}
|
||||
|
||||
object Type {
|
||||
@ -103,7 +98,7 @@ object Type {
|
||||
case _ => NaN
|
||||
}
|
||||
|
||||
private def cmpProd(lf: NonEmptyMap[String, DataType], rf: NonEmptyMap[String, DataType]): Double =
|
||||
private def cmpProd(lf: NonEmptyMap[String, Type], rf: NonEmptyMap[String, Type]): Double =
|
||||
if (lf.toSortedMap == rf.toSortedMap) 0.0
|
||||
else if (
|
||||
lf.keys.forall(rf.contains) && cmpTypesList(
|
||||
@ -131,7 +126,11 @@ object Type {
|
||||
case (x: ArrayType, y: ArrayType) => cmp(x.element, y.element)
|
||||
case (ProductType(_, xFields), ProductType(_, yFields)) =>
|
||||
cmpProd(xFields, yFields)
|
||||
case (ArrowType(argL, resL), ArrowType(argR, resR)) =>
|
||||
case (l: CallableType, r: CallableType) =>
|
||||
val argL = l.args
|
||||
val resL = l.res
|
||||
val argR = r.args
|
||||
val resR = r.res
|
||||
val cmpTypes = cmpTypesList(argR, argL)
|
||||
val cmpRes =
|
||||
if (resL == resR) 0.0
|
||||
@ -141,12 +140,6 @@ object Type {
|
||||
else if (cmpTypes <= 0 && cmpRes <= 0) -1.0
|
||||
else NaN
|
||||
|
||||
case (x: FuncArrowType, y: ArrowType) =>
|
||||
x.toArrowType.fold(NaN)(cmp(_, y))
|
||||
|
||||
case (x: ArrowType, y: FuncArrowType) =>
|
||||
y.toArrowType.fold(NaN)(cmp(x, _))
|
||||
|
||||
case _ =>
|
||||
Double.NaN
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user