Typechecker typechecks

This commit is contained in:
alari 2021-03-05 21:55:57 +03:00
parent 2276f27389
commit 4356620579
7 changed files with 187 additions and 98 deletions

View File

@ -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)

View File

@ -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)))
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}