diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml index ffbc8ccba..374e4e001 100644 --- a/.github/workflows/scala.yml +++ b/.github/workflows/scala.yml @@ -4,7 +4,6 @@ on: push: branches: [ mlscript ] pull_request: - branches: [ mlscript ] jobs: build: diff --git a/.gitignore b/.gitignore index 1e05d21e3..4a989b4c7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ metals.sbt project/Dependencies.scala project/metals.sbt **.worksheet.sc +.DS_Store diff --git a/.vscode/settings.json b/.vscode/settings.json index 23778bfa8..3e44ed841 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,6 @@ { "files.associations": { - "*.fun": "typescript", - "*.mls": "scala" + "*.fun": "typescript" }, "typescript.validate.enable": false, "files.watcherExclude": { diff --git a/js/src/main/scala/Main.scala b/js/src/main/scala/Main.scala index cf4f7017d..89e211129 100644 --- a/js/src/main/scala/Main.scala +++ b/js/src/main/scala/Main.scala @@ -134,7 +134,7 @@ object Main { val vars: Map[Str, typer.SimpleType] = Map.empty val tpd = typer.typeTypingUnit(tu, topLevel = true)(ctx.nest, raise, vars) - + object SimplifyPipeline extends typer.SimplifyPipeline { def debugOutput(msg: => Str): Unit = // if (mode.dbgSimplif) output(msg) diff --git a/shared/src/main/scala/mlscript/NewLexer.scala b/shared/src/main/scala/mlscript/NewLexer.scala index 1fce939aa..b79abe599 100644 --- a/shared/src/main/scala/mlscript/NewLexer.scala +++ b/shared/src/main/scala/mlscript/NewLexer.scala @@ -55,6 +55,34 @@ class NewLexer(origin: Origin, raise: Diagnostic => Unit, dbg: Bool) { def takeWhile(i: Int, cur: Ls[Char] = Nil)(pred: Char => Bool): (Str, Int) = if (i < length && pred(bytes(i))) takeWhile(i + 1, bytes(i) :: cur)(pred) else (cur.reverseIterator.mkString, i) + + @tailrec final + def str(i: Int, escapeMode: Bool, cur: Ls[Char] = Nil): (Str, Int) = + if (escapeMode) + if (i < length) + bytes(i) match { + case '"' => str(i + 1, false, '"' :: cur) + case 'n' => str(i + 1, false, '\n' :: cur) + case 't' => str(i + 1, false, '\t' :: cur) + case 'r' => str(i + 1, false, '\r' :: cur) + case ch => + raise(WarningReport(msg"Found invalid escape character" -> S(loc(i, i + 1)) :: Nil, source = Lexing)) + str(i + 1, false, ch :: cur) + } + else { + raise(ErrorReport(msg"Expect an escape character" -> S(loc(i, i + 1)) :: Nil, source = Lexing)) + (cur.reverseIterator.mkString, i) + } + else { + if (i < length) + bytes(i) match { + case '\\' => str(i + 1, true, cur) + case '"' | '\n' => (cur.reverseIterator.mkString, i) + case ch => str(i + 1, false, ch :: cur) + } + else + (cur.reverseIterator.mkString, i) + } def loc(start: Int, end: Int): Loc = Loc(start, end, origin) @@ -78,7 +106,7 @@ class NewLexer(origin: Origin, raise: Diagnostic => Unit, dbg: Bool) { lex(j, ind, next(j, COMMA)) case '"' => val j = i + 1 - val (chars, k) = takeWhile(j)(c => c =/= '"' && c =/= '\n') + val (chars, k) = str(j, false) val k2 = if (bytes.lift(k) === Some('"')) k + 1 else { pe(msg"unclosed quotation mark") k @@ -135,14 +163,19 @@ class NewLexer(origin: Origin, raise: Diagnostic => Unit, dbg: Bool) { // go(j, if (keywords.contains(n)) KEYWORD(n) else IDENT(n, isAlphaOp(n))) lex(j, ind, next(j, if (keywords.contains(n)) KEYWORD(n) else IDENT(n, isAlphaOp(n)))) case _ if isOpChar(c) => - val (n, j) = takeWhile(i)(isOpChar) - if (n === "." && j < length && isIdentFirstChar(bytes(j))) { - val (name, k) = takeWhile(j)(isIdentChar) - // go(k, SELECT(name)) - lex(k, ind, next(k, SELECT(name))) + if (c === '-' && isDigit(bytes(i + 1))) { + val (str, j) = takeWhile(i + 1)(isDigit) + lex(j, ind, next(j, LITVAL(IntLit(-BigInt(str))))) + } else { + val (n, j) = takeWhile(i)(isOpChar) + if (n === "." && j < length && isIdentFirstChar(bytes(j))) { + val (name, k) = takeWhile(j)(isIdentChar) + // go(k, SELECT(name)) + lex(k, ind, next(k, SELECT(name))) + } + // else go(j, if (isSymKeyword.contains(n)) KEYWORD(n) else IDENT(n, true)) + else lex(j, ind, next(j, if (isSymKeyword.contains(n)) KEYWORD(n) else IDENT(n, true))) } - // else go(j, if (isSymKeyword.contains(n)) KEYWORD(n) else IDENT(n, true)) - else lex(j, ind, next(j, if (isSymKeyword.contains(n)) KEYWORD(n) else IDENT(n, true))) case _ if isDigit(c) => val (str, j) = takeWhile(i)(isDigit) // go(j, LITVAL(IntLit(BigInt(str)))) diff --git a/shared/src/main/scala/mlscript/Typer.scala b/shared/src/main/scala/mlscript/Typer.scala index 493d518d4..7985c8d23 100644 --- a/shared/src/main/scala/mlscript/Typer.scala +++ b/shared/src/main/scala/mlscript/Typer.scala @@ -255,6 +255,7 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool) val intBinOpTy = fun(singleTup(IntType), fun(singleTup(IntType), IntType)(noProv))(noProv) val numberBinOpTy = fun(singleTup(DecType), fun(singleTup(DecType), DecType)(noProv))(noProv) val numberBinPred = fun(singleTup(DecType), fun(singleTup(DecType), BoolType)(noProv))(noProv) + val stringBinPred = fun(singleTup(StrType), fun(singleTup(StrType), BoolType)(noProv))(noProv) Map( "true" -> TrueType, "false" -> FalseType, @@ -278,6 +279,10 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool) "le" -> numberBinPred, "gt" -> numberBinPred, "ge" -> numberBinPred, + "slt" -> stringBinPred, + "sle" -> stringBinPred, + "sgt" -> stringBinPred, + "sge" -> stringBinPred, "length" -> fun(singleTup(StrType), IntType)(noProv), "concat" -> fun(singleTup(StrType), fun(singleTup(StrType), StrType)(noProv))(noProv), "eq" -> { @@ -1062,19 +1067,9 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool) case ((a_ty, tv), req) => a_ty & tv | req & a_ty.neg() } con(s_ty, req, cs_ty) - case iff @ If(body, fallback) => - import mlscript.ucs._ - try { - val caseTree = MutCaseOf.build(desugarIf(body, fallback)) - println("The mutable CaseOf tree") - MutCaseOf.show(caseTree).foreach(println(_)) - checkExhaustive(caseTree, N)(summarizePatterns(caseTree), ctx, raise) - val desugared = MutCaseOf.toTerm(caseTree) - println(s"Desugared term: ${desugared.print(false)}") - iff.desugaredTerm = S(desugared) - typeTerm(desugared) - } catch { - case e: DesugaringException => err(e.messages) + case elf: If => + try typeTerm(desugarIf(elf)) catch { + case e: ucs.DesugaringException => err(e.messages) } case New(S((nmedTy, trm)), TypingUnit(Nil)) => typeMonomorphicTerm(App(Var(nmedTy.base.name).withLocOf(nmedTy), trm)) diff --git a/shared/src/main/scala/mlscript/codegen/Polyfill.scala b/shared/src/main/scala/mlscript/codegen/Polyfill.scala index 24b9580dd..73fc357be 100644 --- a/shared/src/main/scala/mlscript/codegen/Polyfill.scala +++ b/shared/src/main/scala/mlscript/codegen/Polyfill.scala @@ -166,6 +166,12 @@ object Polyfill { buffer += BuiltinFunc("not", makeUnaryFunc("!")) buffer += BuiltinFunc("negate", makeUnaryFunc("-")) buffer += BuiltinFunc("eq", makeBinaryFunc("===")) + buffer += BuiltinFunc("ne", makeBinaryFunc("!==")) + buffer += BuiltinFunc("sgt", makeBinaryFunc(">")) + buffer += BuiltinFunc("slt", makeBinaryFunc("<")) + buffer += BuiltinFunc("sge", makeBinaryFunc(">=")) + buffer += BuiltinFunc("sle", makeBinaryFunc("<=")) + buffer += BuiltinFunc("eq", makeBinaryFunc("===")) buffer += BuiltinFunc("unit", makeUnaryFunc("undefined")) buffer += BuiltinFunc( "log", fn(_, param("x")) { `return` { id("console.info")(id("x")) } } diff --git a/shared/src/main/scala/mlscript/codegen/Scope.scala b/shared/src/main/scala/mlscript/codegen/Scope.scala index bd2e0297f..e0f42bbae 100644 --- a/shared/src/main/scala/mlscript/codegen/Scope.scala +++ b/shared/src/main/scala/mlscript/codegen/Scope.scala @@ -47,6 +47,12 @@ class Scope(name: Str, enclosing: Opt[Scope]) { "div", "gt", "not", + "ne", + "eq", + "sgt", + "slt", + "sge", + "sle", "typeof", "toString", "negate", @@ -69,11 +75,20 @@ class Scope(name: Str, enclosing: Opt[Scope]) { i <- (1 to Int.MaxValue).iterator c <- Scope.nameAlphabet.combinations(i) name = c.mkString - if !runtimeSymbols.contains(name) + if !hasRuntimeName(name) } yield { name } + /** + * Check if a runtime name is used recursively. + * + * @param name the name + * @return whether it's available or not + */ + private def hasRuntimeName(name: Str): Bool = + runtimeSymbols.contains(name) || enclosing.exists(_.hasRuntimeName(name)) + /** * Allocate a non-sense runtime name. */ diff --git a/shared/src/main/scala/mlscript/helpers.scala b/shared/src/main/scala/mlscript/helpers.scala index 02cd4c9f0..6b2a45b19 100644 --- a/shared/src/main/scala/mlscript/helpers.scala +++ b/shared/src/main/scala/mlscript/helpers.scala @@ -218,7 +218,7 @@ trait TypeImpl extends Located { self: Type => case _: Union | _: Function | _: Tuple | _: Recursive | _: Neg | _: Rem | _: Bounds | _: WithExtension | Top | Bot | _: Literal | _: TypeVar | _: AppliedType | _: TypeName - | _: Constrained | _ : Splice | _: TypeTag | _: PolyType => + | _: Constrained | _ : Splice | _: TypeTag | _: PolyType | _: Selection => Nil } @@ -232,7 +232,8 @@ trait TypeImpl extends Located { self: Type => case Inter(lhs, rhs) => lhs.collectTypeNames ++ rhs.collectTypeNames case _: Union | _: Function | _: Record | _: Tuple | _: Recursive | _: Neg | _: Rem | _: Bounds | _: WithExtension | Top | Bot | _: PolyType - | _: Literal | _: TypeVar | _: Constrained | _ : Splice | _: TypeTag => + | _: Literal | _: TypeVar | _: Constrained | _ : Splice | _: TypeTag + | _: Selection => Nil } @@ -245,7 +246,8 @@ trait TypeImpl extends Located { self: Type => case Inter(ty1, ty2) => ty1.collectBodyFieldsAndTypes ++ ty2.collectBodyFieldsAndTypes case _: Union | _: Function | _: Tuple | _: Recursive | _: Neg | _: Rem | _: Bounds | _: WithExtension | Top | Bot | _: PolyType - | _: Literal | _: TypeVar | _: AppliedType | _: TypeName | _: Constrained | _ : Splice | _: TypeTag => + | _: Literal | _: TypeVar | _: AppliedType | _: TypeName | _: Constrained | _ : Splice | _: TypeTag + | _: Selection => Nil } } @@ -729,6 +731,8 @@ trait StatementImpl extends Located { self: Statement => ).withLocOf(hd) :: cs) case NuTypeDef(Nms, nme, tps, tup @ Tup(fs), sig, pars, sup, ths, unit) => ??? // TODO + case NuTypeDef(Mxn, nme, tps, tup @ Tup(fs), sig, pars, sup, ths, unit) => + ??? // TODO case NuTypeDef(k @ Als, nme, tps, tup @ Tup(fs), sig, pars, sup, ths, unit) => // TODO properly check: require(fs.isEmpty, fs) diff --git a/shared/src/main/scala/mlscript/ucs/Clause.scala b/shared/src/main/scala/mlscript/ucs/Clause.scala index d4c26b6b1..b91354d98 100644 --- a/shared/src/main/scala/mlscript/ucs/Clause.scala +++ b/shared/src/main/scala/mlscript/ucs/Clause.scala @@ -9,11 +9,11 @@ import scala.collection.mutable.Buffer * A `Clause` represents a minimal unit of logical predicate in the UCS. * There are three kinds of clauses: boolean test, class match, and tuple match. */ -abstract class Clause { +sealed abstract class Clause { /** * Local interleaved let bindings declared before this condition. */ - var bindings: Ls[(Bool, Var, Term)] = Nil + var bindings: Ls[LetBinding] = Nil /** * Locations of terms that build this `Clause`. @@ -21,52 +21,58 @@ abstract class Clause { * @return */ val locations: Ls[Loc] + + protected final def bindingsToString: String = + if (bindings.isEmpty) "" else " with " + (bindings match { + case Nil => "" + case bindings => bindings.map(_.name.name).mkString("(", ", ", ")") + }) +} + +sealed abstract class MatchClause extends Clause { + val scrutinee: Scrutinee } object Clause { + final case class MatchLiteral( + override val scrutinee: Scrutinee, + literal: SimpleTerm + )(override val locations: Ls[Loc]) extends MatchClause { + override def toString: String = s"«$scrutinee is $literal" + bindingsToString + } + + final case class MatchAny(override val scrutinee: Scrutinee)(override val locations: Ls[Loc]) extends MatchClause { + override def toString: String = s"«$scrutinee is any" + bindingsToString + } + final case class MatchClass( - scrutinee: Scrutinee, + override val scrutinee: Scrutinee, className: Var, fields: Ls[Str -> Var] - )(override val locations: Ls[Loc]) extends Clause + )(override val locations: Ls[Loc]) extends MatchClause { + override def toString: String = s"«$scrutinee is $className»" + bindingsToString + } final case class MatchTuple( scrutinee: Scrutinee, arity: Int, fields: Ls[Str -> Var] - )(override val locations: Ls[Loc]) extends Clause - - final case class BooleanTest(test: Term)(override val locations: Ls[Loc]) extends Clause - - def showBindings(bindings: Ls[(Bool, Var, Term)]): Str = - bindings match { - case Nil => "" - case bindings => bindings.map { - case (_, Var(name), _) => name - }.mkString("(", ", ", ")") - } - + )(override val locations: Ls[Loc]) extends Clause { + override def toString: String = s"«$scrutinee is Tuple#$arity»" + bindingsToString + } - def showClauses(clauses: Iterable[Clause]): Str = { - clauses.iterator.map { clause => - (clause match { - case Clause.BooleanTest(test) => s"«$test»" - case Clause.MatchClass(scrutinee, Var(className), fields) => - s"«$scrutinee is $className»" - case Clause.MatchTuple(scrutinee, arity, fields) => - s"«$scrutinee is Tuple#$arity»" - }) + (if (clause.bindings.isEmpty) "" else " with " + showBindings(clause.bindings)) - }.mkString("", " and ", "") + final case class BooleanTest(test: Term)( + override val locations: Ls[Loc] + ) extends Clause { + override def toString: String = s"«$test»" + bindingsToString } - def print(println: (=> Any) => Unit, conjunctions: Iterable[Conjunction -> Term]): Unit = { - println("Flattened conjunctions") - conjunctions.foreach { case Conjunction(clauses, trailingBindings) -> term => - println("+ " + showClauses(clauses) + { - (if (trailingBindings.isEmpty) "" else " ") + - showBindings(trailingBindings) + - s" => $term" - }) - } + /** + * @param isField whether this binding is extracting a class field + */ + final case class Binding(name: Var, term: Term, isField: Bool)( + override val locations: Ls[Loc] + ) extends Clause { + override def toString: String = s"«$name = $term»" + bindingsToString } } diff --git a/shared/src/main/scala/mlscript/ucs/Conjunction.scala b/shared/src/main/scala/mlscript/ucs/Conjunction.scala index ff9c6a2fb..f0928dcce 100644 --- a/shared/src/main/scala/mlscript/ucs/Conjunction.scala +++ b/shared/src/main/scala/mlscript/ucs/Conjunction.scala @@ -3,11 +3,21 @@ package mlscript.ucs import mlscript._, utils._, shorthands._ import Clause._, helpers._ import scala.collection.mutable.Buffer +import scala.annotation.tailrec /** * A `Conjunction` represents a list of `Clause`s. */ -final case class Conjunction(clauses: Ls[Clause], trailingBindings: Ls[(Bool, Var, Term)]) { +final case class Conjunction(clauses: Ls[Clause], trailingBindings: Ls[LetBinding]) { + override def toString: String = + clauses.mkString("", " and ", "") + { + (if (trailingBindings.isEmpty) "" else " ") + + (trailingBindings match { + case Nil => "" + case bindings => bindings.map(_.name.name).mkString("(", ", ", ")") + }) + } + /** * Concatenate two `Conjunction` together. * @@ -44,31 +54,55 @@ final case class Conjunction(clauses: Ls[Clause], trailingBindings: Ls[(Bool, Va } } + /** + * This is a shorthand if you only have one clause. + * + * @param last the list of clauses to append to this conjunction + * @return a new conjunction with clauses from `this` and `last` + */ + def +(last: Clause): Conjunction = { + last.bindings = trailingBindings ::: last.bindings + Conjunction(clauses :+ last, Nil) + } + /** * This is a shorthand if you only have the last binding. * * @param suffix the list of clauses to append to this conjunction * @return a new conjunction with clauses from `this` and `suffix` */ - def +(lastBinding: (Bool, Var, Term)): Conjunction = + def +(lastBinding: LetBinding): Conjunction = Conjunction(clauses, trailingBindings :+ lastBinding) - def separate(expectedScrutinee: Scrutinee): Opt[(MatchClass, Conjunction)] = { - def rec(past: Ls[Clause], upcoming: Ls[Clause]): Opt[(Ls[Clause], MatchClass, Ls[Clause])] = { + def findClauseMatches(expectedScrutinee: Scrutinee): Opt[(MatchClause, Conjunction)] = { + @tailrec + def rec(past: Ls[Clause], upcoming: Ls[Clause], firstAny: Opt[(Ls[Clause], MatchAny, Ls[Clause])]): Opt[(Ls[Clause], MatchClause, Ls[Clause])] = { upcoming match { - case Nil => N + case Nil => firstAny + case (head @ MatchLiteral(scrutinee, _)) :: tail => + if (scrutinee === expectedScrutinee) { + S((past, head, tail)) + } else { + rec(past :+ head, tail, firstAny) + } case (head @ MatchClass(scrutinee, _, _)) :: tail => if (scrutinee === expectedScrutinee) { S((past, head, tail)) } else { - rec(past :+ head, tail) + rec(past :+ head, tail, firstAny) + } + case (head @ MatchAny(scrutinee)) :: tail => + if (scrutinee === expectedScrutinee) { + rec(past, tail, firstAny.orElse(S((past, head, tail)))) + } else { + rec(past :+ head, tail, firstAny) } case head :: tail => - rec(past :+ head, tail) + rec(past :+ head, tail, firstAny) } } - rec(Nil, clauses).map { case (past, wanted, remaining) => + rec(Nil, clauses, None).map { case (past, wanted, remaining) => (wanted, Conjunction(past ::: remaining, trailingBindings)) } } @@ -79,7 +113,7 @@ final case class Conjunction(clauses: Ls[Clause], trailingBindings: Ls[(Bool, Va * @param interleavedLets the buffer of let bindings in the current context * @return idential to `conditions` */ - def withBindings(implicit interleavedLets: Buffer[(Bool, Var, Term)]): Conjunction = { + def withBindings(implicit interleavedLets: Buffer[LetBinding]): Conjunction = { clauses match { case Nil => Conjunction(Nil, interleavedLets.toList ::: trailingBindings) case head :: _ => diff --git a/shared/src/main/scala/mlscript/ucs/Desugarer.scala b/shared/src/main/scala/mlscript/ucs/Desugarer.scala index ae4a4c00e..f5d3325f9 100644 --- a/shared/src/main/scala/mlscript/ucs/Desugarer.scala +++ b/shared/src/main/scala/mlscript/ucs/Desugarer.scala @@ -6,6 +6,7 @@ import scala.collection.mutable.Buffer import mlscript._, utils._, shorthands._ import helpers._ import Message.MessageContext +import mlscript.ucs.MutCaseOf.MutCase.Constructor /** * This class contains main desugaring methods. @@ -16,6 +17,7 @@ class Desugarer extends TypeDefs { self: Typer => private def traceUCS[T](pre: => String)(thunk: => T)(post: T => String = noPostTrace) = if (dbgUCS) trace(pre)(thunk)(post) else thunk + import Desugarer.{ExhaustivenessMap, SubClassMap, SuperClassMap} import Clause.{MatchClass, MatchTuple, BooleanTest} type FieldAliasMap = MutMap[SimpleTerm, MutMap[Str, Var]] @@ -36,6 +38,24 @@ class Desugarer extends TypeDefs { self: Typer => res } + private type MutExhaustivenessMap = MutMap[Str \/ Int, MutMap[Either[Int, SimpleTerm], Buffer[Loc]]] + + private def addToExhaustivenessMap(scrutinee: Scrutinee, loc: Iterable[Loc]) + (implicit ctx: Ctx, raise: Raise, map: MutExhaustivenessMap) = { + map.getOrElseUpdate(getScurtineeKey(scrutinee), MutMap.empty) + } + + private def addToExhaustivenessMap(scrutinee: Scrutinee, tupleArity: Int, loc: Iterable[Loc]) + (implicit ctx: Ctx, raise: Raise, map: MutExhaustivenessMap) = { + map.getOrElseUpdate(getScurtineeKey(scrutinee), MutMap.empty) + .getOrElseUpdate(L(tupleArity), Buffer.empty) ++= loc + } + private def addToExhaustivenessMap(scrutinee: Scrutinee, litOrCls: SimpleTerm, loc: Iterable[Loc]) + (implicit ctx: Ctx, raise: Raise, map: MutExhaustivenessMap) = { + map.getOrElseUpdate(getScurtineeKey(scrutinee), MutMap.empty) + .getOrElseUpdate(R(litOrCls), Buffer.empty) ++= loc + } + /** * * @@ -44,7 +64,7 @@ class Desugarer extends TypeDefs { self: Typer => * @param positionals the corresponding field names of each parameter * @param aliasMap a map used to cache each the alias of each field * @param matchRootLoc the location to the root of the match - * @return a mapping from each field to their var + * @return two mappings: one is (variable -> sub-pattern), the other is (positional name -> variable) */ private def desugarPositionals (scrutinee: Scrutinee, params: IterableOnce[Term], positionals: Ls[Str]) @@ -54,7 +74,8 @@ class Desugarer extends TypeDefs { self: Typer => // `x is A(_)`: ignore this binding case (Var("_"), _) => N // `x is A(value)`: generate bindings directly - case (name: Var, fieldName) => S(fieldName -> name) + case (nameVar @ Var(n), fieldName) if (n.headOption.exists(_.isLower)) => + S(fieldName -> nameVar) // `x is B(A(x))`: generate a temporary name // use the name in the binding, and destruct sub-patterns case (pattern: Term, fieldName) => @@ -66,7 +87,7 @@ class Desugarer extends TypeDefs { self: Typer => subPatterns += ((alias, pattern)) S(fieldName -> alias) }.toList - subPatterns.toList -> bindings + (subPatterns.toList, bindings) } /** @@ -78,9 +99,9 @@ class Desugarer extends TypeDefs { self: Typer => * @return desugared conditions representing the sub-patterns */ private def destructSubPatterns(scrutinee: Scrutinee, subPatterns: Iterable[Var -> Term]) - (implicit ctx: Ctx, raise: Raise, aliasMap: FieldAliasMap): Ls[Clause] = { + (implicit ctx: Ctx, raise: Raise, exhaustivenessMap: MutExhaustivenessMap, aliasMap: FieldAliasMap): Ls[Clause] = { subPatterns.iterator.flatMap[Clause] { case (subScrutinee, subPattern) => - destructPattern(makeScrutinee(subScrutinee, scrutinee.matchRootLoc), subPattern) + destructPattern(makeScrutinee(subScrutinee, scrutinee.matchRootLoc), subPattern, false) }.toList } @@ -96,27 +117,41 @@ class Desugarer extends TypeDefs { self: Typer => * @param matchRootLoc the caller is expect to be in a match environment, * this parameter indicates the location of the match root */ - def makeScrutinee(term: Term, matchRootLoc: Opt[Loc])(implicit ctx: Ctx): Scrutinee = + private def makeScrutinee(term: Term, matchRootLoc: Opt[Loc])(implicit ctx: Ctx): Scrutinee = traceUCS(s"Making a scrutinee for `$term`") { term match { - case _: SimpleTerm => Scrutinee(N, term)(matchRootLoc) + case _: Var => + printlnUCS(s"The scrutinee does not need an alias.") + Scrutinee(N, term)(matchRootLoc) case _ => - val localName = if (localizedScrutineeMap.containsKey(term)) { - localizedScrutineeMap.get(term) - } else { - val v = Var(freshName).desugaredFrom(term) - localizedScrutineeMap.put(term, v) - v - } - Scrutinee(S(localName), term)(matchRootLoc) + val localizedName = makeLocalizedName(term) + printlnUCS(s"The scrutinee needs an alias: $localizedName") + Scrutinee(S(localizedName), term)(matchRootLoc) } }() + /** + * Create a fresh name for scrutinee to be localized. + * + * @param scrutinee the term of the scrutinee + * @param ctx the context + * @return the fresh name, as `Var` + */ + private def makeLocalizedName(scrutinee: Term)(implicit ctx: Ctx): Var = + if (localizedScrutineeMap.containsKey(scrutinee)) { + localizedScrutineeMap.get(scrutinee) + } else { + val v = Var(freshName).desugaredFrom(scrutinee) + localizedScrutineeMap.put(scrutinee, v) + v + } + /** * Destruct nested patterns to a list of simple condition with bindings. * * @param scrutinee the scrutinee of the pattern matching * @param pattern the pattern we will destruct + * @param isTopLevel whether this pattern just follows the `is` operator * @param raise the `Raise` function * @param aliasMap the field alias map * @param matchRootLoc the location of the root of the pattern matching @@ -127,9 +162,10 @@ class Desugarer extends TypeDefs { self: Typer => * do not contain interleaved let bindings. */ private def destructPattern - (scrutinee: Scrutinee, pattern: Term) + (scrutinee: Scrutinee, pattern: Term, isTopLevel: Bool) (implicit ctx: Ctx, raise: Raise, + exhaustivenessMap: MutExhaustivenessMap, aliasMap: FieldAliasMap, fragments: Ls[Term] = Nil): Ls[Clause] = trace(s"[Desugarer.destructPattern] scrutinee = ${scrutinee.term}; pattern = $pattern") { @@ -140,6 +176,7 @@ class Desugarer extends TypeDefs { self: Typer => tuple.fields.iterator.map(_._2.value), 1.to(tuple.fields.length).map("_" + _).toList ) + addToExhaustivenessMap(scrutinee, tuple.fields.length, tuple.toLoc) Clause.MatchTuple( scrutinee, tuple.fields.length, @@ -149,15 +186,40 @@ class Desugarer extends TypeDefs { self: Typer => pattern match { // This case handles top-level wildcard `Var`. // We don't make any conditions in this level. + case wildcard @ Var("_") if isTopLevel => + addToExhaustivenessMap(scrutinee, wildcard.toLoc) + Clause.MatchAny(scrutinee)(wildcard.toLoc.toList) :: Nil + // If it's not top-level, wildcard means we don't care. case Var("_") => Nil // This case handles literals. // x is true | x is false | x is 0 | x is "text" | ... - case literal @ (Var("true") | Var("false") | _: Lit) => - val test = mkBinOp(scrutinee.reference, Var("=="), literal) - val clause = Clause.BooleanTest(test)(scrutinee.term.toLoc.toList ::: literal.toLoc.toList) + case literal: Var if literal.name === "true" || literal.name === "false" => + addToExhaustivenessMap(scrutinee, literal, literal.toLoc) + val clause = Clause.MatchLiteral(scrutinee, literal)(scrutinee.term.toLoc.toList ::: literal.toLoc.toList) clause.bindings = scrutinee.asBinding.toList printlnUCS(s"Add bindings to the clause: ${scrutinee.asBinding}") clause :: Nil + case literal: Lit => + addToExhaustivenessMap(scrutinee, literal, literal.toLoc) + val clause = Clause.MatchLiteral(scrutinee, literal)(scrutinee.term.toLoc.toList ::: literal.toLoc.toList) + clause.bindings = scrutinee.asBinding.toList + printlnUCS(s"Add bindings to the clause: ${scrutinee.asBinding}") + clause :: Nil + // This case handles name binding. + // x is a + case bindingVar @ Var(bindingName) if bindingName.headOption.exists(_.isLower) => + val locations = scrutinee.term.toLoc.toList ::: bindingVar.toLoc.toList + if (isTopLevel) { + // If the binding name is at the top-level. We create decision path like + // ... /\ x is any /\ a = x /\ ... + addToExhaustivenessMap(scrutinee, bindingVar.toLoc) + Clause.MatchAny(scrutinee)(locations) :: + Clause.Binding(bindingVar, scrutinee.reference, !isTopLevel)(locations) :: + Nil + } else { + // Otherwise, we just create the binding. + Clause.Binding(bindingVar, scrutinee.term, !isTopLevel)(locations) :: Nil + } // This case handles simple class tests. // x is A case classNameVar @ Var(className) => @@ -169,6 +231,7 @@ class Desugarer extends TypeDefs { self: Typer => }, classNameVar.toLoc) } printlnUCS(s"Build a Clause.MatchClass from $scrutinee where pattern is $classNameVar") + addToExhaustivenessMap(scrutinee, classNameVar, classNameVar.toLoc) Clause.MatchClass(scrutinee, classNameVar, Nil)(collectLocations(scrutinee.term)) :: Nil // This case handles classes with destruction. // x is A(r, s, t) @@ -193,6 +256,7 @@ class Desugarer extends TypeDefs { self: Typer => args.iterator.map(_._2.value), positionals ) + addToExhaustivenessMap(scrutinee, classNameVar, app.toLoc) val clause = Clause.MatchClass(scrutinee, classNameVar, bindings)(pattern.toLoc.toList ::: collectLocations(scrutinee.term)) printlnUCS(s"Build a Clause.MatchClass from $scrutinee where pattern is $pattern") printlnUCS(s"Fragments: $fragments") @@ -225,12 +289,13 @@ class Desugarer extends TypeDefs { self: Typer => msg"Cannot find operator `$op` in the context" }, opVar.toLoc) case S(td) if td.positionals.length === 2 => - val (subPatterns, bindings) = desugarPositionals( + val (subPatterns, fields) = desugarPositionals( scrutinee, lhs :: rhs :: Nil, td.positionals ) - val clause = Clause.MatchClass(scrutinee, opVar, bindings)(collectLocations(scrutinee.term)) + addToExhaustivenessMap(scrutinee, opVar, app.toLoc) + val clause = Clause.MatchClass(scrutinee, opVar, fields)(collectLocations(scrutinee.term)) printlnUCS(s"Build a Clause.MatchClass from $scrutinee where operator is $opVar") clause :: destructSubPatterns(scrutinee, subPatterns) case S(td) => @@ -251,7 +316,7 @@ class Desugarer extends TypeDefs { self: Typer => // What else? case _ => throw new DesugaringException(msg"illegal pattern", pattern.toLoc) } - }("[Desugarer.destructPattern] result: " + Clause.showClauses(_)) + }("[Desugarer.destructPattern] Result: " + _.mkString(", ")) /** * Collect `Loc`s from a synthetic term. @@ -260,7 +325,7 @@ class Desugarer extends TypeDefs { self: Typer => * @param fragments the fragment terms * @return all original locations */ - def collectLocations(term: Term)(implicit fragments: Ls[Term]): Ls[Loc] = { + private def collectLocations(term: Term)(implicit fragments: Ls[Term]): Ls[Loc] = { val locations = Buffer.empty[Loc] def rec(term: Term): Unit = term.children.foreach { located => if (fragments.contains(located)) locations ++= located.toLoc @@ -268,11 +333,72 @@ class Desugarer extends TypeDefs { self: Typer => locations.toList } + private def unfoldNestedIf(elf: If, acc: Ls[IfBody] = Nil): (IfBody, Opt[Term]) = + traceUCS("[unfoldNestedIf]") { + elf.els match { + case S(innerElf: If) => unfoldNestedIf(innerElf, elf.body :: acc) + case default if acc.isEmpty => (elf.body, default) + case default => + val lines = (elf.body :: acc).reverseIterator.flatMap { + case IfBlock(subLines) => subLines + case other => Iterable.single(L(other)) + }.toList + (IfBlock(lines), default) + } + }(r => s"[unfoldNestedIf] (${r._1.getClass().getSimpleName()}, ${r._2})") - def desugarIf + /** + * The entry point of UCS desugarer. + * + * @param elf the root `If` term + * @param ctx the typing context + * @param raise the function to raise errors + * @return the desugared term + */ + def desugarIf(elf: If)(implicit ctx: Ctx, raise: Raise): Term = traceUCS("[desugarIf]") { + val superClassMap = getClassHierarchy() + Desugarer.printGraph(superClassMap, printlnUCS, "Super-class map", "<:") + val subClassMap = Desugarer.reverseGraph(superClassMap) + Desugarer.printGraph(subClassMap, printlnUCS, "Sub-class map", ":>") + val (body, els) = unfoldNestedIf(elf) + val exhaustivenessMap: MutExhaustivenessMap = MutMap.empty + printlnUCS("### Desugar the UCS to decision paths ###") + val paths = desugarIf(body, els)(ctx, raise, exhaustivenessMap) + printlnUCS("Exhaustiveness map") + if (exhaustivenessMap.isEmpty) + printlnUCS(" * ") + else + exhaustivenessMap.foreach { case (symbol, patternMap) => + printlnUCS(s" * Patterns of $symbol") + if (patternMap.isEmpty) + printlnUCS(s" + ") + else + patternMap.foreach { case (pattern, locations) => + val first = pattern match { + case Left(tupleArity) => s"()^$tupleArity" + case Right(litOrCls) => litOrCls.toString() + } + val second = locations.mkString("[", ", ", "]") + printlnUCS(s" + $first -> $second") + } + } + printlnUCS("### Build a case tree from decision paths ###") + val imExhaustivenessMap = Map.from(exhaustivenessMap.iterator.map { case (k, m) => k -> Map.from(m) }) + val caseTree = buildCaseTree(paths)(raise, getScurtineeKey, imExhaustivenessMap, superClassMap) + printlnUCS("### Checking exhaustiveness of the case tree ###") + checkExhaustive(caseTree, N)(ctx, raise, imExhaustivenessMap, subClassMap) + printlnUCS("### Construct a term from the case tree ###") + val desugared = constructTerm(caseTree) + println(s"Desugared term: ${desugared.print(false)}") + elf.desugaredTerm = S(desugared) + desugared + }() + + + private def desugarIf (body: IfBody, fallback: Opt[Term]) - (implicit ctx: Ctx, raise: Raise) - : Ls[Conjunction -> Term] = { + (implicit ctx: Ctx, raise: Raise, exhaustivenessMap: MutExhaustivenessMap) + : Ls[Conjunction -> Term] = traceUCS(s"[desugarIf] with fallback $fallback") { // We allocate temporary variable names for nested patterns. // This prevents aliasing problems. implicit val scrutineeFieldAliasMap: FieldAliasMap = MutMap.empty @@ -296,7 +422,7 @@ class Desugarer extends TypeDefs { self: Typer => // This is an inline `x is Class` match test. val inlineMatchLoc = isApp.toLoc val inlineScrutinee = makeScrutinee(scrutinee, inlineMatchLoc) - destructPattern(inlineScrutinee, pattern)(ctx, raise, scrutineeFieldAliasMap) + destructPattern(inlineScrutinee, pattern, true)(ctx, raise, exhaustivenessMap, scrutineeFieldAliasMap) case test => val clause = Clause.BooleanTest(test)(collectLocations(test)) Iterable.single(clause) @@ -318,30 +444,32 @@ class Desugarer extends TypeDefs { self: Typer => body: IfBody \/ Statement, partialPattern: PartialTerm, collectedConditions: Conjunction, - )(implicit interleavedLets: Buffer[(Bool, Var, Term)]): Unit = + )(implicit interleavedLets: Buffer[LetBinding]): Unit = traceUCS[Unit]("[desugarMatchBranch]") { body match { // This case handles default branches. For example, // if x is // A(...) then ... // else ... - case L(IfElse(consequent)) => + case L(els @ IfElse(consequent)) => // Because this pattern matching is incomplete, it's not included in // `acc`. This means that we discard this incomplete pattern matching. + // branches += (collectedConditions + Clause.MatchNot(scrutinee)(els.toLoc.toList) -> consequent) branches += (collectedConditions -> consequent) // This case handles default branches indicated by wildcards. // if x is // A(...) then ... // _ then ... - case L(IfThen(Var("_"), consequent)) => + case L(IfThen(wildcard @ Var("_"), consequent)) => + // branches += (collectedConditions + Clause.MatchNot(scrutinee)(wildcard.toLoc.toList) -> consequent) branches += (collectedConditions -> consequent) // if x is // A(...) then ... // Case 1: no conjunctions // B(...) and ... then ... // Case 2: more conjunctions case L(IfThen(patTest, consequent)) => val (patternPart, extraTestOpt) = separatePattern(patTest) - val clauses = destructPattern(scrutinee, partialPattern.addTerm(patternPart).term) + val clauses = destructPattern(scrutinee, partialPattern.addTerm(patternPart).term, true) val conditions = collectedConditions + Conjunction(clauses, Nil).withBindings - printlnUCS(s"result conditions: " + Clause.showClauses(conditions.clauses)) + printlnUCS(s"Result: " + conditions.clauses.mkString(", ")) extraTestOpt match { // Case 1. Just a pattern. Easy! case N => @@ -359,7 +487,7 @@ class Desugarer extends TypeDefs { self: Typer => // B(...) then ... case L(IfOpApp(patLhs, Var("and"), consequent)) => val (pattern, optTests) = separatePattern(patLhs) - val patternConditions = destructPattern(scrutinee, pattern) + val patternConditions = destructPattern(scrutinee, pattern, true) val tailTestConditions = optTests.fold(Nil: Ls[Clause])(x => desugarConditions(splitAnd(x))) val conditions = collectedConditions + Conjunction(patternConditions ::: tailTestConditions, Nil).withBindings @@ -370,7 +498,7 @@ class Desugarer extends TypeDefs { self: Typer => // The pattern is completed. There is also a conjunction. // So, we need to separate the pattern from remaining parts. case (pattern, S(extraTests)) => - val patternConditions = destructPattern(scrutinee, pattern) + val patternConditions = destructPattern(scrutinee, pattern, true) val extraConditions = desugarConditions(splitAnd(extraTests)) val conditions = collectedConditions + Conjunction(patternConditions ::: extraConditions, Nil).withBindings @@ -392,7 +520,7 @@ class Desugarer extends TypeDefs { self: Typer => desugarMatchBranch(scrutinee, L(consequent), partialPattern2.addOp(op), collectedConditions) } case (patternPart, S(extraTests)) => - val patternConditions = destructPattern(scrutinee, partialPattern.addTerm(patternPart).term) + val patternConditions = destructPattern(scrutinee, partialPattern.addTerm(patternPart).term, true) val testTerms = splitAnd(extraTests) val middleConditions = desugarConditions(testTerms.init) val conditions = @@ -412,16 +540,18 @@ class Desugarer extends TypeDefs { self: Typer => TODO("please add this rare case to test files") // This case handles interleaved lets. case R(NuFunDef(S(isRec), nameVar, _, L(term))) => - interleavedLets += ((isRec, nameVar, term)) + interleavedLets += (LetBinding(LetBinding.Kind.InterleavedLet, isRec, nameVar, term)) // Other statements are considered to be ill-formed. case R(statement) => throw new DesugaringException({ msg"Illegal interleaved statement ${statement.toString}" }, statement.toLoc) } + }(_ => "[desugarMatchBranch]") + def desugarIfBody (body: IfBody, expr: PartialTerm, acc: Conjunction) - (implicit interleavedLets: Buffer[(Bool, Var, Term)]) - : Unit = { + (implicit interleavedLets: Buffer[LetBinding]) + : Unit = traceUCS[Unit]("[desugarIfBody]") { body match { case IfOpsApp(exprPart, opsRhss) => val exprStart = expr.addTerm(exprPart) @@ -455,11 +585,12 @@ class Desugarer extends TypeDefs { self: Typer => case S(alias) => acc case N => acc } - // Create a buffer for interleaved let bindings. - val interleavedLets = Buffer.empty[(Bool, Var, Term)] + // We need to make a snapshot because the sub-branches mutate the buffer. + // But these changes should not affect sibling branches. + val interleavedLetsSnapshot = interleavedLets.clone() // Iterate each match case. lines.foreach { - desugarMatchBranch(scrutinee, _, PartialTerm.Empty, conjunction)(interleavedLets) + desugarMatchBranch(scrutinee, _, PartialTerm.Empty, conjunction)(interleavedLetsSnapshot) } // For example: "if x == 0 and y is \n ..." case IfOpApp(testPart, Var("and"), consequent) => @@ -479,44 +610,56 @@ class Desugarer extends TypeDefs { self: Typer => lines.foreach { case L(subBody) => desugarIfBody(subBody, expr, acc) case R(NuFunDef(S(isRec), nameVar, _, L(term))) => - interleavedLets += ((isRec, nameVar, term)) + printlnUCS(s"Found interleaved binding ${nameVar.name}") + interleavedLets += LetBinding(LetBinding.Kind.InterleavedLet, isRec, nameVar, term) case R(_) => throw new Error("unexpected statements at desugarIfBody") } } - } + }(_ => "[desugarIfBody]") + // Top-level interleaved let bindings. - val interleavedLets = Buffer.empty[(Bool, Var, Term)] + val interleavedLets = Buffer.empty[LetBinding] desugarIfBody(body, PartialTerm.Empty, Conjunction.empty)(interleavedLets) // Add the fallback case to conjunctions if there is any. fallback.foreach { branches += Conjunction.empty -> _ } - Clause.print(printlnUCS, branches) + printlnUCS("Decision paths:") + branches.foreach { case conjunction -> term => + printlnUCS(s"+ $conjunction => $term") + } branches.toList - } + }(r => s"[desugarIf] produces ${r.size} ${"path".pluralize(r.size)}") import MutCaseOf.{MutCase, IfThenElse, Match, MissingCase, Consequent} /** - * A map from each scrutinee term to all its cases and the first `MutCase`. + * This method obtains a proper key of the given scrutinee + * for memorizing patterns belongs to the scrutinee. + * + * @param scrutinee the scrutinee + * @param ctx the context + * @param raise we need this to raise errors. + * @return the variable name or the variable ID */ - type ExhaustivenessMap = Map[Str \/ Int, Map[Var, MutCase]] - - def getScurtineeKey(scrutinee: Scrutinee)(implicit ctx: Ctx, raise: Raise): Str \/ Int = { - scrutinee.term match { - // The original scrutinee is an reference. - case v @ Var(name) => - ctx.env.get(name) match { - case S(VarSymbol(_, defVar)) => defVar.uid.fold[Str \/ Int](L(v.name))(R(_)) - case S(_) | N => L(v.name) - } - // Otherwise, the scrutinee has a temporary name. - case _ => - scrutinee.local match { - case N => throw new Error("check your `makeScrutinee`") - case S(localNameVar) => L(localNameVar.name) - } - } - } + private def getScurtineeKey(scrutinee: Scrutinee)(implicit ctx: Ctx, raise: Raise): Str \/ Int = + traceUCS(s"[getScrutineeKey] $scrutinee") { + scrutinee.term match { + // The original scrutinee is an reference. + case v @ Var(name) => + printlnUCS("The original scrutinee is an reference.") + ctx.env.get(name) match { + case S(VarSymbol(_, defVar)) => defVar.uid.fold[Str \/ Int](L(v.name))(R(_)) + case S(_) | N => L(v.name) + } + // Otherwise, the scrutinee was localized because it might be effectful. + case _ => + printlnUCS("The scrutinee was localized because it might be effectful.") + scrutinee.local match { + case N => throw new Error("check your `makeScrutinee`") + case S(localNameVar) => L(localNameVar.name) + } + } + }() /** * Check the exhaustiveness of the given `MutCaseOf`. @@ -525,13 +668,14 @@ class Desugarer extends TypeDefs { self: Typer => * @param parentOpt the parent `MutCaseOf` * @param scrutineePatternMap the exhaustiveness map */ - def checkExhaustive + private def checkExhaustive (t: MutCaseOf, parentOpt: Opt[MutCaseOf]) - (implicit scrutineePatternMap: ExhaustivenessMap, ctx: Ctx, raise: Raise) - : Unit = { - printlnUCS(s"Check exhaustiveness of ${t.describe}") - indent += 1 - try t match { + (implicit ctx: Ctx, + raise: Raise, + exhaustivenessMap: ExhaustivenessMap, + subClassMap: SubClassMap) + : Unit = traceUCS(s"[checkExhaustive] ${t.describe}") { + t match { case _: Consequent => () case MissingCase => parentOpt match { @@ -545,24 +689,54 @@ class Desugarer extends TypeDefs { self: Typer => case S(Consequent(_)) | S(MissingCase) | N => die // unreachable } case IfThenElse(condition, whenTrue, whenFalse) => - checkExhaustive(whenTrue, S(t)) checkExhaustive(whenFalse, S(t)) + checkExhaustive(whenTrue, S(t)) case Match(scrutinee, branches, default) => - scrutineePatternMap.get(getScurtineeKey(scrutinee)) match { + exhaustivenessMap.get(getScurtineeKey(scrutinee)) match { case N => lastWords(s"unreachable case: unknown scrutinee ${scrutinee.term}") + case S(_) if default.isDefined => + printlnUCS("The match has a default branch. So, it is always safe.") case S(patternMap) => - printlnUCS(s"The exhaustiveness map is ${scrutineePatternMap}") + printlnUCS(s"The exhaustiveness map is") + exhaustivenessMap.foreach { case (key, matches) => + printlnUCS(s"- $key -> ${matches.keysIterator.mkString(", ")}") + } printlnUCS(s"The scrutinee key is ${getScurtineeKey(scrutinee)}") printlnUCS("Pattern map of the scrutinee:") if (patternMap.isEmpty) printlnUCS("") else patternMap.foreach { case (key, mutCase) => printlnUCS(s"- $key => $mutCase")} + // Compute all classes that can be covered by this match. + val coveredClassNames = Set.from[String](branches.iterator.flatMap { + case MutCase.Literal(_, _) => Nil + case Constructor(Var(className) -> _, _) => + subClassMap.get(className).fold[List[String]](Nil)(identity) + }) + printlnUCS("The match can cover following classes") + printlnUCS(coveredClassNames.mkString("{", ", ", "}")) // Filter out missing cases in `branches`. val missingCases = patternMap.removedAll(branches.iterator.map { - case MutCase(classNameVar -> _, _) => classNameVar - }) - printlnUCS(s"Number of missing cases: ${missingCases.size}") + case MutCase.Literal(lit, _) => R(lit) + case MutCase.Constructor(classNameVar -> _, _) => + classNameVar.name.split('#').toList match { + case "Tuple" :: ns :: Nil => + ns.toIntOption match { + case N => R(classNameVar) + case S(arity) => L(arity) + } + case _ => R(classNameVar) + } + }).filter { // Remove classes subsumed by super classes. + case R(Var(className)) -> _ => + !coveredClassNames.contains(className) + case L(_) -> _ => true // Tuple. Don't remove. + case R(_) -> _ => true // Literals. Don't remove. + } + printlnUCS("Missing cases") + missingCases.foreach { case (key, m) => + printlnUCS(s"- $key -> ${m}") + } if (!missingCases.isEmpty) { throw new DesugaringException({ val numMissingCases = missingCases.size @@ -570,50 +744,259 @@ class Desugarer extends TypeDefs { self: Typer => (msg"The scrutinee at this position misses ${numMissingCases.toString} ${ "case".pluralize(numMissingCases) }." -> scrutinee.term.toLoc) :: - missingCases.iterator.zipWithIndex.flatMap { case ((classNameVar, firstMutCase), index) => + missingCases.iterator.zipWithIndex.flatMap { case ((pattern, locations), index) => + val patternName = pattern match { + case L(tupleArity) => s"$tupleArity-ary tuple" + case R(litOrCls) => litOrCls.toString() + } val progress = s"[Missing Case ${index + 1}/$numMissingCases]" - (msg"$progress `${classNameVar.name}`" -> N) :: - firstMutCase.locations.iterator.zipWithIndex.map { case (loc, index) => - (if (index === 0) msg"It first appears here." else msg"continued at") -> S(loc) + (msg"$progress `$patternName`" -> N) :: + locations.iterator.zipWithIndex.map { case (loc, index) => + (if (index === 0) msg"It first appears here." else msg"And here.") -> S(loc) }.toList }.toList }) } } default.foreach(checkExhaustive(_, S(t))) - branches.foreach { case MutCase(_, consequent) => - checkExhaustive(consequent, S(t)) + branches.foreach { branch => + checkExhaustive(branch.consequent, S(t)) } - } finally indent -= 1 - } + } + }(_ => s"[checkExhaustive] ${t.describe}") - def summarizePatterns(t: MutCaseOf)(implicit ctx: Ctx, raise: Raise): ExhaustivenessMap = { - val m = MutMap.empty[Str \/ Int, MutMap[Var, MutCase]] - def rec(t: MutCaseOf): Unit = { - printlnUCS(s"Summarize pattern of ${t.describe}") - indent += 1 - try t match { - case Consequent(term) => () - case MissingCase => () - case IfThenElse(_, whenTrue, whenFalse) => - rec(whenTrue) - rec(whenFalse) - case Match(scrutinee, branches, _) => - val key = getScurtineeKey(scrutinee) - branches.foreach { mutCase => - val patternMap = m.getOrElseUpdate( key, MutMap.empty) - if (!patternMap.contains(mutCase.patternFields._1)) { - patternMap += ((mutCase.patternFields._1, mutCase)) + /** + * Make a term from a mutable case tree. + * This should be called after exhaustiveness checking. + * + * @param m the mutable case tree + * @param ctx the context + * @return the case expression + */ + private def constructTerm(m: MutCaseOf)(implicit ctx: Ctx): Term = traceUCS("[constructTerm]") { + /** + * Reconstruct case branches. + */ + def rec2(xs: Ls[MutCase])( + implicit defs: Set[Var], scrutinee: Scrutinee, wildcard: Option[MutCaseOf] + ): CaseBranches = { + xs match { + case MutCase.Constructor(className -> fields, cases) :: next => + printlnUCS(s"• Constructor pattern: $className(${fields.iterator.map(x => s"${x._1} -> ${x._2}").mkString(", ")})") + // TODO: expand bindings here + val consequent = rec(cases)(defs ++ fields.iterator.map(_._2)) + Case(className, mkLetFromFields(scrutinee, fields.toList, consequent), rec2(next)) + case MutCase.Literal(literal, cases) :: next => + printlnUCS(s"• Literal pattern: $literal") + Case(literal, rec(cases), rec2(next)) + case Nil => + wildcard match { + case None => + printlnUCS("• No wildcard branch") + NoCases + case Some(value) => + printlnUCS("• Wildcard branch") + Wildcard(rec(value)) + } + } + } + /** + * Reconstruct the entire match. + */ + def rec(m: MutCaseOf)(implicit defs: Set[Var]): Term = traceUCS(s"[rec] ${m.describe} -| {${defs.mkString(", ")}}") { + m match { + case Consequent(term) => + mkBindings(m.getBindings.toList, term, defs) + case Match(scrutinee, branches, wildcard) => + printlnUCS("• Owned let bindings") + val ownedBindings = m.getBindings.iterator.filterNot { + _.kind === LetBinding.Kind.InterleavedLet + }.toList + if (ownedBindings.isEmpty) + printlnUCS(" * ") + else + ownedBindings.foreach { case LetBinding(kind, _, name, value) => + printlnUCS(s" * ($kind) $name = $value") } - rec(mutCase.consequent) + // Collect interleaved let bindings from case branches. + // Because they should be declared before + val interleavedBindings = branches.iterator.map(_.consequent).concat(wildcard).flatMap(_.getBindings).filter { + _.kind === LetBinding.Kind.InterleavedLet + }.toList + printlnUCS("• Collect interleaved let bindings from case branches") + if (interleavedBindings.isEmpty) + printlnUCS(" * ") + else + interleavedBindings.foreach { case LetBinding(_, _, name, value) => + printlnUCS(s" * $name = $value") + } + val resultTerm = if (branches.isEmpty) { + // If the match does not have any branches. + wildcard match { + case None => + // Internal error! + printlnUCS("• The match has neither branches nor default case") + throw new DesugaringException({ + import Message.MessageContext + msg"found an empty match" + }, scrutinee.term.toLoc) + case Some(default) => + printlnUCS("• Degenerated case: the match only has a wildcard") + val subTerm = rec(default) + scrutinee.local match { + case N => subTerm + case S(aliasVar) => Let(false, aliasVar, scrutinee.term, subTerm) + } + } + } else { + // If the match has some branches. + printlnUCS("• The match has some case branches") + val cases = traceUCS("• For each case branch"){ + rec2(branches.toList)(defs, scrutinee, wildcard) + }(_ => "• End for each") + scrutinee.local match { + case N => CaseOf(scrutinee.term, cases) + case S(aliasVar) => Let(false, aliasVar, scrutinee.term, CaseOf(aliasVar, cases)) + } + } + mkBindings(ownedBindings, mkBindings(interleavedBindings, resultTerm, defs), defs) + case MissingCase => + import Message.MessageContext + throw new DesugaringException(msg"missing a default branch", N) + case IfThenElse(condition, whenTrue, whenFalse) => + val falseBody = mkBindings(whenFalse.getBindings.toList, rec(whenFalse)(defs ++ whenFalse.getBindings.iterator.map(_.name)), defs) + val trueBody = mkBindings(whenTrue.getBindings.toList, rec(whenTrue)(defs ++ whenTrue.getBindings.iterator.map(_.name)), defs) + val falseBranch = Wildcard(falseBody) + val trueBranch = Case(Var("true"), trueBody, falseBranch) + CaseOf(condition, trueBranch) + } + }() + val term = rec(m)(Set.from(m.getBindings.iterator.map(_.name))) + // Create immutable map from the mutable map. + mkBindings(m.getBindings.toList, term, Set.empty) + }(_ => "[constructTerm]") + + /** + * Generate a chain of field selection to the given scrutinee. + * + * @param scrutinee the pattern matching scrutinee + * @param fields a list of pairs from field names to binding names + * @param body the final body + */ + private def mkLetFromFields(scrutinee: Scrutinee, fields: Ls[Str -> Var], body: Term)(implicit ctx: Ctx): Term = { + def rec(scrutineeReference: SimpleTerm, fields: Ls[Str -> Var]): Term = + fields match { + case Nil => body + case (field -> (aliasVar @ Var(alias))) :: tail => + scrutinee.term match { + // Check if the scrutinee is a `Var` and its name conflicts with + // one of the positionals. If so, we create an alias and extract + // fields by selecting the alias. + case Var(scrutineeName) if alias === scrutineeName => + val scrutineeAlias = Var(freshName) + Let( + false, + scrutineeAlias, + scrutinee.reference, + Let( + false, + aliasVar, + Sel(scrutineeAlias, Var(field)).desugaredFrom(scrutinee.term), + rec(scrutineeAlias, tail) + ) + ) + case _ => + Let( + false, + aliasVar, + Sel(scrutineeReference, Var(field)).desugaredFrom(scrutinee.term), + rec(scrutineeReference, tail) + ) } - } finally indent -= 1 + } + rec(scrutinee.reference, fields) + } + + private def buildCaseTree + (paths: Ls[Conjunction -> Term]) + (implicit raise: Diagnostic => Unit, + getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap, + superClassMap: SuperClassMap) + : MutCaseOf = traceUCS("[buildCaseTree]") { + paths match { + case Nil => MissingCase + case (conditions -> term) :: remaining => + val root = MutCaseOf.buildFirst(conditions, term) + traceUCS("*** Initial tree ***") { + MutCaseOf.show(root).foreach(printlnUCS(_)) + }() + remaining.foreach { path => + root.merge(path) + printlnUCS(s"*** Merging `${path._1} => ${path._2}` ***") + traceUCS("*** Updated tree ***") { + MutCaseOf.show(root).foreach(printlnUCS(_)) + }() + } + root } - rec(t) - printlnUCS("Exhaustiveness map") - m.foreach { case (scrutinee, patterns) => - printlnUCS(s"- $scrutinee => " + patterns.keys.mkString(", ")) + }(_ => "[buildCaseTree]") + + private def getClassHierarchy()(implicit ctx: Ctx): SuperClassMap = + traceUCS("[getClassHierarchy]") { + // ctx.tyDefs + val superClassMap = ctx.tyDefs.iterator + .filter(_._2.toLoc.isDefined) + .map { case (className, td) => + className -> td.baseClasses.iterator.map(_.name).toList + } |> Map.from + Desugarer.transitiveClosure(superClassMap) + }(_ => "[getClassHierarchy]") +} + +object Desugarer { + /** + * A map from each scrutinee term to all its cases and the first `MutCase`. + */ + type ExhaustivenessMap = Map[Str \/ Int, Map[Either[Int, SimpleTerm], Buffer[Loc]]] + + type SuperClassMap = Map[String, List[String]] + + type SubClassMap = Map[String, List[String]] + + def reverseGraph(graph: Map[String, List[String]]): Map[String, List[String]] = { + graph.iterator.flatMap { case (source, targets) => targets.iterator.map(_ -> source) } + .foldLeft(Map.empty[String, List[String]]) { case (map, target -> source) => + map.updatedWith(target) { + case None => Some(source :: Nil) + case Some(sources) => Some(source :: sources) + } + } + } + + def transitiveClosure(graph: Map[String, List[String]]): Map[String, List[String]] = { + def dfs(vertex: String, visited: Set[String]): Set[String] = { + if (visited.contains(vertex)) visited + else graph.getOrElse(vertex, List()) + .foldLeft(visited + vertex)((acc, v) => dfs(v, acc)) } - Map.from(m.iterator.map { case (key, patternMap) => key -> Map.from(patternMap) }) + + graph.keys.map { vertex => + val closure = dfs(vertex, Set()) + vertex -> (closure - vertex).toList + }.toMap } -} + + def printGraph(graph: Map[String, List[String]], print: (=> Any) => Unit, title: String, arrow: String): Unit = { + print(s"• $title") + if (graph.isEmpty) + print(" + ") + else + graph.foreach { case (source, targets) => + print(s" + $source $arrow " + { + if (targets.isEmpty) s"{}" + else targets.mkString("{ ", ", ", " }") + }) + } + } +} \ No newline at end of file diff --git a/shared/src/main/scala/mlscript/ucs/LetBinding.scala b/shared/src/main/scala/mlscript/ucs/LetBinding.scala new file mode 100644 index 000000000..6e08b3ce2 --- /dev/null +++ b/shared/src/main/scala/mlscript/ucs/LetBinding.scala @@ -0,0 +1,46 @@ +package mlscript.ucs + +import mlscript._ +import mlscript.utils._ +import mlscript.utils.shorthands._ +import scala.collection.immutable.Set +import scala.collection.mutable.{Set => MutSet, Buffer} + +final case class LetBinding(val kind: LetBinding.Kind, val recursive: Bool, val name: Var, val term: Term) + +object LetBinding { + sealed abstract class Kind + + object Kind { + case object ScrutineeAlias extends Kind { + override def toString: String = "scrutinee alias" + } + case object FieldExtraction extends Kind { + override def toString: String = "pattern destruction" + } + case object InterleavedLet extends Kind { + override def toString: String = "interleaved let" + } + } +} + +trait WithBindings { this: MutCaseOf => + private val bindingsSet: MutSet[LetBinding] = MutSet.empty + private val bindings: Buffer[LetBinding] = Buffer.empty + + def addBindings(newBindings: IterableOnce[LetBinding]): Unit = { + newBindings.iterator.foreach { + case binding if bindingsSet.contains(binding) => () + case binding => + bindingsSet += binding + bindings += binding + } + } + + def getBindings: Iterable[LetBinding] = bindings + + def withBindings(newBindings: IterableOnce[LetBinding]): MutCaseOf = { + addBindings(newBindings) + this + } +} diff --git a/shared/src/main/scala/mlscript/ucs/MutCaseOf.scala b/shared/src/main/scala/mlscript/ucs/MutCaseOf.scala index 37d908499..1b5f7e6d4 100644 --- a/shared/src/main/scala/mlscript/ucs/MutCaseOf.scala +++ b/shared/src/main/scala/mlscript/ucs/MutCaseOf.scala @@ -8,27 +8,9 @@ import scala.collection.mutable.{Map => MutMap, Set => MutSet, Buffer} import helpers._ import mlscript.ucs.MutCaseOf.Consequent - -trait WithBindings { this: MutCaseOf => - private val bindingsSet: MutSet[(Bool, Var, Term)] = MutSet.empty - private val bindings: Buffer[(Bool, Var, Term)] = Buffer.empty - - def addBindings(newBindings: IterableOnce[(Bool, Var, Term)]): Unit = { - newBindings.iterator.foreach { - case binding if bindingsSet.contains(binding) => () - case binding => - bindingsSet += binding - bindings += binding - } - } - - def getBindings: Iterable[(Bool, Var, Term)] = bindings - - def withBindings(newBindings: IterableOnce[(Bool, Var, Term)]): MutCaseOf = { - addBindings(newBindings) - this - } -} +import scala.collection.immutable +import Desugarer.{ExhaustivenessMap, SuperClassMap} +import mlscript.ucs.Clause.MatchAny sealed abstract class MutCaseOf extends WithBindings { def kind: Str = { @@ -41,26 +23,44 @@ sealed abstract class MutCaseOf extends WithBindings { } } + def duplicate(): MutCaseOf + + def fill(subTree: MutCaseOf): Unit + def describe: Str + def isComplete: Bool + + def isExhaustive(implicit getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap): Bool + + def tryMerge + (branch: Conjunction -> Term) + (implicit raise: Diagnostic => Unit, + getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap, + superClassMap: SuperClassMap): Unit = + merge(branch)(_ => (), getScrutineeKey, exhaustivenessMap, superClassMap) + def merge (branch: Conjunction -> Term) - (implicit raise: Diagnostic => Unit): Unit + (implicit raise: Diagnostic => Unit, + getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap, + superClassMap: SuperClassMap): Unit + def mergeDefault - (bindings: Ls[(Bool, Var, Term)], default: Term) - (implicit raise: Diagnostic => Unit): Unit - def toTerm(defs: Set[Var]): Term + (bindings: Ls[LetBinding], default: Term) + (implicit raise: Diagnostic => Unit, + getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap, + superClassMap: SuperClassMap): Int // TODO: Make it immutable. var locations: Ls[Loc] = Nil } object MutCaseOf { - def toTerm(t: MutCaseOf): Term = { - val term = t.toTerm(Set.from(t.getBindings.iterator.map(_._2))) - mkBindings(t.getBindings.toList, term, Set.empty) - } - def showScrutinee(scrutinee: Scrutinee): Str = s"«${scrutinee.term}»" + (scrutinee.local match { case N => "" @@ -69,57 +69,69 @@ object MutCaseOf { def show(t: MutCaseOf): Ls[Str] = { val lines = Buffer.empty[String] - def rec(t: MutCaseOf, indent: Int, leading: String): Unit = { + def rec(t: MutCaseOf, indent: Int): Unit = { val baseIndent = " " * indent - val bindingNames = t.getBindings match { - case Nil => "" - case bindings => bindings.iterator.map(_._2.name).mkString("[", ", ", "] ") - } + lazy val bindingLines = t.getBindings.iterator.map { + case LetBinding(_, recursive, name, term) => + // Show bindings + s"[binding $name = $term]" + }.toList t match { case IfThenElse(condition, whenTrue, whenFalse) => // Output the `whenTrue` with the prefix "if". - lines += baseIndent + leading + bindingNames + s"if «$condition»" - rec(whenTrue, indent + 1, "") + bindingLines.foreach { lines += baseIndent + _ } + lines += baseIndent + s"if «$condition»" + rec(whenTrue, indent + 1) // Output the `whenFalse` case with the prefix "else". - lines += s"$baseIndent${leading}else" - rec(whenFalse, indent + 1, "") + lines += s"${baseIndent}else" + rec(whenFalse, indent + 1) case Match(scrutinee, branches, default) => - lines += baseIndent + leading + bindingNames + showScrutinee(scrutinee) + " match" - branches.foreach { case MutCase(Var(className) -> fields, consequent) => - lines += s"$baseIndent case $className =>" - fields.foreach { case (field, Var(alias)) => - lines += s"$baseIndent let $alias = .$field" - } - rec(consequent, indent + 2, "") + bindingLines.foreach { lines += baseIndent + _ } + lines += baseIndent + showScrutinee(scrutinee) + " match" + branches.foreach { + case MutCase.Literal(literal, consequent) => + lines += s"$baseIndent case $literal =>" + rec(consequent, indent + 1) + case MutCase.Constructor(Var(className) -> fields, consequent) => + lines += s"$baseIndent case $className =>" + fields.foreach { case (field, Var(alias)) => + // Show pattern bindings. + lines += s"$baseIndent [pattern $alias = ${scrutinee.reference}.$field]" + } + rec(consequent, indent + 2) } default.foreach { consequent => lines += s"$baseIndent default" - rec(consequent, indent + 2, "") + rec(consequent, indent + 2) } case Consequent(term) => - lines += s"$baseIndent$leading$bindingNames«$term»" + bindingLines.foreach { lines += baseIndent + _ } + lines += s"$baseIndent«$term»" case MissingCase => - lines += s"$baseIndent$leading$bindingNames" + bindingLines.foreach { lines += baseIndent + _ } + lines += s"$baseIndent" } } - rec(t, 0, "") + rec(t, 0) lines.toList } - /** - * MutCase is a _mutable_ representation of a case in `MutCaseOf.Match`. - * - * @param patternFields the alias to the fields - * @param consequent the consequential `MutCaseOf` - */ - final case class MutCase( - val patternFields: Var -> Buffer[Str -> Var], - var consequent: MutCaseOf, - ) { - def matches(expected: Var): Bool = matches(expected.name) - def matches(expected: Str): Bool = patternFields._1.name === expected - def addFields(fields: Iterable[Str -> Var]): Unit = - patternFields._2 ++= fields.iterator.filter(!patternFields._2.contains(_)) + sealed abstract class MutCase { + var consequent: MutCaseOf + + @inline + def isComplete: Bool = consequent.isComplete + + def duplicate(): MutCase + + /** + * Check whether this case can cover the expected class or literal. + * + * @param expected the expected class name or literal + * @param superClassMap a map from each class to its super classes + * @return whether the given pattern can be covered by this case + */ + def covers(expected: SimpleTerm)(implicit superClassMap: SuperClassMap): Bool // Note 1 // ====== @@ -142,41 +154,104 @@ object MutCaseOf { locations ++= locOpt this } - def withLocations(locs: Ls[Loc]): MutCase = { + def withLocations(locs: IterableOnce[Loc]): MutCase = { locations ++= locs this } } - import Clause.{MatchClass, MatchTuple, BooleanTest} + object MutCase { + final case class Literal( + val literal: SimpleTerm, + var consequent: MutCaseOf, + ) extends MutCase { + override def duplicate(): MutCase = + Literal(literal, consequent.duplicate()).withLocations(locations) + override def covers(expected: SimpleTerm)(implicit superClassMap: SuperClassMap): Bool = + expected match { + case _: Lit | Var("true") | Var("false") => expected === literal + case Var(_) => false + } + } + + /** + * MutCase is a _mutable_ representation of a case in `MutCaseOf.Match`. + * + * @param patternFields the alias to the fields + * @param consequent the consequential `MutCaseOf` + */ + final case class Constructor( + val patternFields: Var -> Buffer[Str -> Var], + var consequent: MutCaseOf, + ) extends MutCase { + override def duplicate(): MutCase = + Constructor(patternFields.copy(_2 = patternFields._2.clone()), consequent.duplicate()) + .withLocations(locations) + override def covers(expected: SimpleTerm)(implicit superClassMap: SuperClassMap): Bool = + expected match { + case lit: Lit => false + case Var(tof) if tof === "true" || tof === "false" => false + case Var(expectedClassName) if expectedClassName === patternFields._1.name => true + case Var(expectedClassName) => + (superClassMap.get(expectedClassName) match { + case Some(superClasses) => superClasses.contains(patternFields._1.name) + case None => + // Should we raise? + false + }) + } + def addFields(fields: Iterable[Str -> Var]): Unit = + patternFields._2 ++= fields.iterator.filter(!patternFields._2.contains(_)) + } + } + + import Clause.{MatchLiteral, MatchAny, MatchClass, MatchTuple, BooleanTest, Binding} // A short-hand for pattern matchings with only true and false branches. final case class IfThenElse(condition: Term, var whenTrue: MutCaseOf, var whenFalse: MutCaseOf) extends MutCaseOf { def describe: Str = s"IfThenElse($condition, whenTrue = ${whenTrue.kind}, whenFalse = ${whenFalse.kind})" - def merge(branch: Conjunction -> Term)(implicit raise: Diagnostic => Unit): Unit = + def duplicate(): MutCaseOf = + IfThenElse(condition, whenTrue.duplicate(), whenFalse.duplicate()) + .withBindings(getBindings) + + override def fill(subTree: MutCaseOf): Unit = { + whenTrue.fill(subTree) + if (whenFalse === MissingCase) + whenFalse = subTree + else + whenFalse.fill(subTree) + } + + def isComplete: Bool = whenTrue.isComplete && whenFalse.isComplete + + def isExhaustive(implicit getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap): Bool = + whenTrue.isExhaustive && whenFalse.isExhaustive + + def merge(branch: Conjunction -> Term) + (implicit raise: Diagnostic => Unit, + getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap, + superClassMap: SuperClassMap): Unit = branch match { // The CC is a wildcard. So, we call `mergeDefault`. case Conjunction(Nil, trailingBindings) -> term => - this.mergeDefault(trailingBindings, term) - // The CC is an if-then-else. We create a pattern match of true/false. - case Conjunction((head @ BooleanTest(test)) :: tail, trailingBindings) -> term => - // If the test is the same. So, we merge. - if (test === condition) { - whenTrue.addBindings(head.bindings) - whenTrue.merge(Conjunction(tail, trailingBindings) -> term) - } else { - whenFalse match { - case Consequent(_) => - raise(WarningReport(Message.fromStr("duplicated else in the if-then-else") -> N :: Nil)) - case MissingCase => - whenFalse = buildFirst(branch._1, branch._2) - whenFalse.addBindings(head.bindings) - case _ => whenFalse.merge(branch) - } + if (mergeDefault(trailingBindings, term) === 0) { + import Message.MessageContext + raise(WarningReport( + msg"Found a redundant else branch" -> term.toLoc :: Nil + )) } + // The CC is an if-then-else. We create a pattern match of true/false. + case Conjunction((head @ BooleanTest(test)) :: tail, trailingBindings) -> term if test === condition => + // If the test is the same. So, we can insert the path to the true branch. + whenTrue.addBindings(head.bindings) + whenTrue.merge(Conjunction(tail, trailingBindings) -> term) + // Otherwise, we try to insert to the true branch. case Conjunction(head :: _, _) -> _ => + whenTrue.tryMerge(branch) whenFalse match { case Consequent(_) => raise(WarningReport(Message.fromStr("duplicated else in the if-then-else") -> N :: Nil)) @@ -187,28 +262,21 @@ object MutCaseOf { } } - def mergeDefault(bindings: Ls[(Bool, Var, Term)], default: Term)(implicit raise: Diagnostic => Unit): Unit = { - whenTrue.mergeDefault(bindings, default) - whenFalse match { - case Consequent(term) => - import Message.MessageContext - raise(WarningReport( - msg"Found a duplicated else branch" -> default.toLoc :: - (msg"The first else branch was declared here." -> term.toLoc) :: - Nil)) - case MissingCase => - whenFalse = Consequent(default).withBindings(bindings) - case _: IfThenElse | _: Match => whenFalse.mergeDefault(bindings, default) + def mergeDefault(bindings: Ls[LetBinding], default: Term) + (implicit raise: Diagnostic => Unit, + getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap, + superClassMap: SuperClassMap): Int = { + whenTrue.mergeDefault(bindings, default) + { + whenFalse match { + case Consequent(term) => 0 + case MissingCase => + whenFalse = Consequent(default).withBindings(bindings) + 1 + case _: IfThenElse | _: Match => whenFalse.mergeDefault(bindings, default) + } } } - - def toTerm(defs: Set[Var]): Term = { - val falseBody = mkBindings(whenFalse.getBindings.toList, whenFalse.toTerm(defs ++ whenFalse.getBindings.iterator.map(_._2)), defs) - val trueBody = mkBindings(whenTrue.getBindings.toList, whenTrue.toTerm(defs ++ whenTrue.getBindings.iterator.map(_._2)), defs) - val falseBranch = Wildcard(falseBody) - val trueBranch = Case(Var("true"), trueBody, falseBranch) - CaseOf(condition, trueBranch) - } } final case class Match( scrutinee: Scrutinee, @@ -222,31 +290,86 @@ object MutCaseOf { })" } - def merge(branch: Conjunction -> Term)(implicit raise: Diagnostic => Unit): Unit = { - branch._1.separate(scrutinee) match { + def duplicate(): MutCaseOf = + Match(scrutinee, branches.map(_.duplicate()), wildcard.map(_.duplicate())) + .withBindings(getBindings) + + override def fill(subTree: MutCaseOf): Unit = { + branches.foreach(_.consequent.fill(subTree)) + wildcard.foreach(_.fill(subTree)) + } + + def isComplete: Bool = + branches.forall(_.consequent.isComplete) && wildcard.forall(_.isComplete) + + def isExhaustive(implicit getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap): Bool = { + exhaustivenessMap.get(getScrutineeKey(scrutinee)) match { + case None => ??? // TODO: Raise. + case Some(patternLocationsMap) => + // Find patterns that are not included in `branches`. + patternLocationsMap.keysIterator.filterNot { + case L(tupleArity) => branches.iterator.exists { + case MutCase.Literal(_, _) => false + case MutCase.Constructor(Var(className) -> _, _) => + className === s"Tuple#$tupleArity" + } + case R(litOrCls) => branches.iterator.exists { + case MutCase.Literal(lit, _) => litOrCls === lit + case MutCase.Constructor(cls -> _, _) => litOrCls === cls + } + }.isEmpty + } + } + + def merge(originalBranch: Conjunction -> Term) + (implicit raise: Diagnostic => Unit, + getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap, + superClassMap: SuperClassMap): Unit = { + // Remove let bindings that already has been declared. + val branch = originalBranch._1.copy(clauses = originalBranch._1.clauses.filter { + case Binding(name, value, false) if (getBindings.exists { + case LetBinding(LetBinding.Kind.ScrutineeAlias, _, n, v) => + n === name && v === value + case _ => false + }) => false + case _ => true + }) -> originalBranch._2 + // Promote the match against the same scrutinee. + branch._1.findClauseMatches(scrutinee) match { // No conditions against the same scrutinee. case N => branch match { case Conjunction((head @ MatchTuple(scrutinee2, arity, fields)) :: tail, trailingBindings) -> term if scrutinee2 === scrutinee => // Same scrutinee! val tupleClassName = Var(s"Tuple#$arity") // TODO: Find a name known by Typer. - branches.find(_.matches(tupleClassName)) match { + branches.find(_.covers(tupleClassName)) match { // No such pattern. We should create a new one. - case N => + case N | S(MutCase.Literal(_, _)) => val newBranch = buildFirst(Conjunction(tail, trailingBindings), term) newBranch.addBindings(head.bindings) - branches += MutCase(tupleClassName -> Buffer.from(fields), newBranch) + branches += MutCase.Constructor(tupleClassName -> Buffer.from(fields), newBranch) .withLocations(head.locations) // Found existing pattern. - case S(branch) => + case S(branch: MutCase.Constructor) => branch.consequent.addBindings(head.bindings) branch.addFields(fields) branch.consequent.merge(Conjunction(tail, trailingBindings) -> term) } // A wild card case. We should propagate wildcard to every default positions. - case Conjunction(Nil, trailingBindings) -> term => mergeDefault(trailingBindings, term) + case Conjunction(Nil, trailingBindings) -> term => + if (mergeDefault(trailingBindings, term) === 0) { + import Message.MessageContext + raise(WarningReport( + msg"Found a redundant else branch" -> term.toLoc :: Nil + )) + } // The conditions to be inserted does not overlap with me. case conjunction -> term => + branches.foreach { + _.consequent.tryMerge(conjunction -> term) + } wildcard match { // No wildcard. We will create a new one. case N => wildcard = S(buildFirst(conjunction, term)) @@ -255,114 +378,213 @@ object MutCaseOf { } } // Found a match condition against the same scrutinee - case S((head @ MatchClass(_, className, fields), remainingConditions)) => - branches.find(_.matches(className)) match { + case S((head @ MatchClass(_, className, fields)) -> remainingConditions) => + // Find all branches which can cover the `className`. + val inclusiveBranches = branches.iterator.filter(_.covers(className)) + if (inclusiveBranches.isEmpty) { + // No such pattern. We should create a new one. + wildcard match { + // If the wildcard branch is incomplete, there might be some + // preemptive branches in front of this branch. + case Some(default) if !default.isComplete => + val subTree = default.duplicate() + subTree.fill(buildFirst(remainingConditions, branch._2)) + subTree.addBindings(head.bindings) + branches += MutCase.Constructor(className -> Buffer.from(fields), subTree) + .withLocations(head.locations) + case Some(_) | None => + val newBranch = buildFirst(remainingConditions, branch._2) + newBranch.addBindings(head.bindings) + branches += MutCase.Constructor(className -> Buffer.from(fields), newBranch) + .withLocations(head.locations) + } + } else { + // Found some branches that can cover the `className`. + inclusiveBranches.foreach { + case MutCase.Literal(_, _) => () // This shouldn't happen. + case matchedCase @ MutCase.Constructor(Var(branchClassName) -> _, _) => + if (branchClassName === className.name) { + // This branch exactly matches the given class name. + // So, we just do a simple merge. + // Merge interleaved bindings. + matchedCase.consequent.addBindings(head.bindings) + matchedCase.addFields(fields) + matchedCase.consequent.merge(remainingConditions -> branch._2) + } else { + // This branch matches the super classes of the given class name. + // There will be refinement matches inside the consequent. + // Therefore, we should not merge with `remainingConditions`. + // Instead, we should use the original conjunction. + matchedCase.consequent.addBindings(head.bindings) + matchedCase.addFields(fields) + matchedCase.consequent.merge(branch) + } + } + } + case S((head @ MatchLiteral(_, literal)) -> remainingConditions) => + branches.find(_.covers(literal)) match { // No such pattern. We should create a new one. - case N => - val newBranch = buildFirst(remainingConditions, branch._2) - newBranch.addBindings(head.bindings) - branches += MutCase(className -> Buffer.from(fields), newBranch) + case N | S(MutCase.Constructor(_, _)) => + val newConsequent = buildFirst(remainingConditions, branch._2) + newConsequent.addBindings(head.bindings) + branches += MutCase.Literal(literal, newConsequent) .withLocations(head.locations) - // Found existing pattern. - case S(matchCase) => + case S(matchCase: MutCase.Literal) => // Merge interleaved bindings. matchCase.consequent.addBindings(head.bindings) - matchCase.addFields(fields) matchCase.consequent.merge(remainingConditions -> branch._2) } + case S((head @ MatchAny(_)) -> remainingConditions) => + // Existing branches may be complete but not exhaustive. + // Find inexhaustiveness branches and try to merge. + branches.iterator.filterNot(_.consequent.isExhaustive).foreach { + _.consequent.tryMerge(remainingConditions -> branch._2) + } + // Then, let's consider the wildcard branch. + wildcard match { + // No wildcard. We will create a new one. + case N => wildcard = S(buildFirst(remainingConditions, branch._2)) + // There is a wildcard case. Just merge! + case S(consequent) => consequent.merge(remainingConditions -> branch._2) + } } } - def mergeDefault(bindings: Ls[(Bool, Var, Term)], default: Term)(implicit raise: Diagnostic => Unit): Unit = { - branches.foreach { - case MutCase(_, consequent) => consequent.mergeDefault(bindings, default) - } - wildcard match { - case N => wildcard = S(Consequent(default).withBindings(bindings)) - case S(consequent) => consequent.mergeDefault(bindings, default) - } - } - - def toTerm(defs: Set[Var]): Term = { - def rec(xs: Ls[MutCase]): CaseBranches = - xs match { - case MutCase(className -> fields, cases) :: next => - // TODO: expand bindings here - val consequent = cases.toTerm(defs ++ fields.iterator.map(_._2)) - Case(className, mkLetFromFields(scrutinee, fields.toList, consequent), rec(next)) - case Nil => - wildcard.fold[CaseBranches](NoCases)(_.toTerm(defs) |> Wildcard) + def mergeDefault(bindings: Ls[LetBinding], default: Term) + (implicit raise: Diagnostic => Unit, + getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap, + superClassMap: SuperClassMap): Int = { + branches.iterator.map { + case MutCase.Constructor(_, consequent) => consequent.mergeDefault(bindings, default) + case MutCase.Literal(_, consequent) => consequent.mergeDefault(bindings, default) + }.sum + { + wildcard match { + case N => + wildcard = S(Consequent(default).withBindings(bindings)) + 1 + case S(consequent) => consequent.mergeDefault(bindings, default) } - val cases = rec(branches.toList) - val resultTerm = scrutinee.local match { - case N => CaseOf(scrutinee.term, cases) - case S(aliasVar) => Let(false, aliasVar, scrutinee.term, CaseOf(aliasVar, cases)) } - // Collect let bindings from case branches. - val bindings = branches.iterator.flatMap(_.consequent.getBindings).toList - mkBindings(bindings, resultTerm, defs) } } final case class Consequent(term: Term) extends MutCaseOf { def describe: Str = s"Consequent($term)" - def merge(branch: Conjunction -> Term)(implicit raise: Diagnostic => Unit): Unit = - raise(WarningReport(Message.fromStr("duplicated branch") -> N :: Nil)) - - def mergeDefault(bindings: Ls[(Bool, Var, Term)], default: Term)(implicit raise: Diagnostic => Unit): Unit = () + override def fill(subTree: MutCaseOf): Unit = () + + override def duplicate(): MutCaseOf = Consequent(term).withBindings(getBindings) + + def isComplete: Bool = true + + def isExhaustive(implicit getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap): Bool = true + + def merge(branch: Conjunction -> Term) + (implicit raise: Diagnostic => Unit, + getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap, + superClassMap: SuperClassMap): Unit = + raise { + import scala.collection.mutable.ListBuffer + val buffer = ListBuffer.empty[Message -> Opt[Loc]] + buffer += Message.fromStr("Found a duplicated branch") -> N + buffer += Message.fromStr("This branch") -> { + val (Conjunction(clauses, _) -> consequent) = branch + consequent.toLoc + // TODO: Make a complete location. + // clauses match { + // case head :: _ => head. + // case Nil => consequent.toLoc + // } + } + buffer += Message.fromStr("is subsumed by the branch here.") -> term.toLoc + WarningReport(buffer.toList) + } - def toTerm(defs: Set[Var]): Term = term + def mergeDefault(bindings: Ls[LetBinding], default: Term) + (implicit raise: Diagnostic => Unit, + getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap, + superClassMap: SuperClassMap): Int = 0 } final case object MissingCase extends MutCaseOf { def describe: Str = "MissingCase" - def merge(branch: Conjunction -> Term)(implicit raise: Diagnostic => Unit): Unit = - lastWords("`MissingCase` is a placeholder and cannot be merged") + override def duplicate() = MissingCase - def mergeDefault(bindings: Ls[(Bool, Var, Term)], default: Term)(implicit raise: Diagnostic => Unit): Unit = () + override def fill(subTree: MutCaseOf): Unit = () - def toTerm(defs: Set[Var]): Term = { - import Message.MessageContext - throw new DesugaringException(msg"missing a default branch", N) - } + def isComplete: Bool = false + + def isExhaustive(implicit getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap): Bool = false + + def merge(branch: Conjunction -> Term) + (implicit raise: Diagnostic => Unit, + getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap, + superClassMap: SuperClassMap): Unit = + lastWords("`MissingCase` is a placeholder and cannot be merged") + + def mergeDefault(bindings: Ls[LetBinding], default: Term) + (implicit raise: Diagnostic => Unit, + getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap, + superClassMap: SuperClassMap): Int = 0 } - private def buildFirst(conjunction: Conjunction, term: Term): MutCaseOf = { + def buildFirst(conjunction: Conjunction, term: Term) + (implicit getScrutineeKey: Scrutinee => Str \/ Int, + exhaustivenessMap: ExhaustivenessMap, + superClassMap: SuperClassMap): MutCaseOf = { def rec(conjunction: Conjunction): MutCaseOf = conjunction match { case Conjunction(head :: tail, trailingBindings) => - val realTail = Conjunction(tail, trailingBindings) + lazy val (beforeHeadBindings, afterHeadBindings) = head.bindings.partition { + case LetBinding(LetBinding.Kind.InterleavedLet, _, _, _) => false + case LetBinding(_, _, _, _) => true + } + val consequentTree = rec(Conjunction(tail, trailingBindings)) (head match { - case BooleanTest(test) => IfThenElse(test, rec(realTail), MissingCase) + case MatchLiteral(scrutinee, literal) => + val branches = Buffer[MutCase]( + MutCase.Literal(literal, consequentTree.withBindings(afterHeadBindings)).withLocation(literal.toLoc) + ) + Match(scrutinee, branches, N) + .withBindings(beforeHeadBindings) + case MatchAny(scrutinee) => + Match(scrutinee, Buffer.empty, S(consequentTree.withBindings(afterHeadBindings))) + .withBindings(beforeHeadBindings) case MatchClass(scrutinee, className, fields) => - val branches = Buffer( - MutCase(className -> Buffer.from(fields), rec(realTail)) + val branches = Buffer[MutCase]( + MutCase.Constructor(className -> Buffer.from(fields), consequentTree.withBindings(afterHeadBindings)) .withLocations(head.locations) ) - Match(scrutinee, branches, N) + Match(scrutinee, branches, N).withBindings(beforeHeadBindings) case MatchTuple(scrutinee, arity, fields) => - val branches = Buffer( - MutCase(Var(s"Tuple#$arity") -> Buffer.from(fields), rec(realTail)) + val branches = Buffer[MutCase]( + MutCase.Constructor(Var(s"Tuple#$arity") -> Buffer.from(fields), consequentTree.withBindings(afterHeadBindings)) .withLocations(head.locations) ) - Match(scrutinee, branches, N) - }).withBindings(head.bindings) + Match(scrutinee, branches, N).withBindings(beforeHeadBindings) + case BooleanTest(test) => + IfThenElse(test, consequentTree, MissingCase) + .withBindings(beforeHeadBindings) + .withBindings(afterHeadBindings) + case Binding(name, term, isField) => + val kind = if (isField) + LetBinding.Kind.FieldExtraction + else + LetBinding.Kind.ScrutineeAlias + consequentTree + .withBindings(beforeHeadBindings) + .withBindings(LetBinding(kind, false, name, term) :: Nil) + .withBindings(afterHeadBindings) + }) case Conjunction(Nil, trailingBindings) => Consequent(term).withBindings(trailingBindings) } rec(conjunction) } - - def build - (cnf: Ls[Conjunction -> Term]) - (implicit raise: Diagnostic => Unit) - : MutCaseOf = { - cnf match { - case Nil => MissingCase - case (conditions -> term) :: next => - val root = MutCaseOf.buildFirst(conditions, term) - next.foreach(root.merge(_)) - root - } - } } diff --git a/shared/src/main/scala/mlscript/ucs/Scrutinee.scala b/shared/src/main/scala/mlscript/ucs/Scrutinee.scala index 48a447ee5..a0ce56d3e 100644 --- a/shared/src/main/scala/mlscript/ucs/Scrutinee.scala +++ b/shared/src/main/scala/mlscript/ucs/Scrutinee.scala @@ -6,7 +6,7 @@ import mlscript.utils.shorthands._ // The point is to remember where the scrutinee comes from. // Is it from nested patterns? Or is it from a `IfBody`? -final case class Scrutinee(local: Opt[Var], term: Term)(val matchRootLoc: Opt[Loc]) { +final case class Scrutinee(var local: Opt[Var], term: Term)(val matchRootLoc: Opt[Loc]) { def reference: SimpleTerm = local.getOrElse(term match { case term: SimpleTerm => term case _ => lastWords("`term` must be a `SimpleTerm` when `local` is empty") @@ -18,7 +18,9 @@ final case class Scrutinee(local: Opt[Var], term: Term)(val matchRootLoc: Opt[Lo * * @return `Some` if the scrutinee is localized, otherwise, `None`. */ - def asBinding: Opt[(Bool, Var, Term)] = local.map((false, _, term)) + def asBinding: Opt[LetBinding] = local.map { + LetBinding(LetBinding.Kind.ScrutineeAlias, false, _, term) + } override def toString: String = (local match { diff --git a/shared/src/main/scala/mlscript/ucs/helpers.scala b/shared/src/main/scala/mlscript/ucs/helpers.scala index 0b20c070f..5aaa37bbb 100644 --- a/shared/src/main/scala/mlscript/ucs/helpers.scala +++ b/shared/src/main/scala/mlscript/ucs/helpers.scala @@ -84,34 +84,17 @@ object helpers { * @param bindings a list of bindings, * @param body the final body */ - def mkBindings(bindings: Ls[(Bool, Var, Term)], body: Term, defs: Set[Var]): Term = { - def rec(bindings: Ls[(Bool, Var, Term)], defs: Set[Var]): Term = + def mkBindings(bindings: Ls[LetBinding], body: Term, defs: Set[Var]): Term = { + def rec(bindings: Ls[LetBinding], defs: Set[Var]): Term = bindings match { case Nil => body - case (head @ (isRec, nameVar, value)) :: tail => - if (defs.contains(head._2)) { + case LetBinding(_, isRec, nameVar, value) :: tail => + if (defs.contains(nameVar)) { rec(tail, defs) } else { - Let(isRec, nameVar, value, rec(tail, defs + head._2)) + Let(isRec, nameVar, value, rec(tail, defs + nameVar)) } } rec(bindings, defs) } - - /** - * Generate a chain of field selection to the given scrutinee. - * - * @param scrutinee the pattern matching scrutinee - * @param fields a list of pairs from field names to binding names - * @param body the final body - */ - def mkLetFromFields(scrutinee: Scrutinee, fields: Ls[Str -> Var], body: Term): Term = { - def rec(fields: Ls[Str -> Var]): Term = - fields match { - case Nil => body - case (field -> (aliasVar @ Var(alias))) :: tail => - Let(false, aliasVar, Sel(scrutinee.reference, Var(field)).desugaredFrom(scrutinee.term), rec(tail)) - } - rec(fields) - } } diff --git a/shared/src/test/diff/codegen/Mixin.mls b/shared/src/test/diff/codegen/Mixin.mls index e51cb76fd..6a63e89e6 100644 --- a/shared/src/test/diff/codegen/Mixin.mls +++ b/shared/src/test/diff/codegen/Mixin.mls @@ -412,7 +412,7 @@ fun mk(n) = if n is 1 then Neg(mk(n)) _ then Add(mk(n), mk(n)) TestLang.eval(mk(0)) -//│ fun mk: forall 'E. number -> 'E +//│ fun mk: forall 'E. anything -> 'E //│ int //│ where //│ 'E :> Add['E] | Lit | Neg['E] @@ -421,7 +421,8 @@ TestLang.eval(mk(0)) //│ const typing_unit6 = new TypingUnit6; //│ // Query 1 //│ globalThis.mk = function mk(n) { -//│ return n == 0 === true ? Lit(0) : n == 1 === true ? Neg(mk(n)) : Add(mk(n), mk(n)); +//│ let a; +//│ return a = n, a === 0 ? Lit(0) : a === 1 ? Neg(mk(n)) : Add(mk(n), mk(n)); //│ }; //│ // Query 2 //│ res = TestLang.eval(mk(0)); @@ -437,7 +438,8 @@ TestLang.eval(mk(0)) //│ │ ├── Prelude: //│ │ ├── Code: //│ │ ├── globalThis.mk = function mk(n) { -//│ │ ├── return n == 0 === true ? Lit(0) : n == 1 === true ? Neg(mk(n)) : Add(mk(n), mk(n)); +//│ │ ├── let a; +//│ │ ├── return a = n, a === 0 ? Lit(0) : a === 1 ? Neg(mk(n)) : Add(mk(n), mk(n)); //│ │ ├── }; //│ │ ├── Intermediate: [Function: mk] //│ │ └── Reply: [success] [Function: mk] @@ -458,7 +460,7 @@ class Foo(x: int) :e class Bar(x: int, y: int) extends Foo(x + y) //│ ╔══[ERROR] Class inheritance is not supported yet (use mixins) -//│ ║ l.459: class Bar(x: int, y: int) extends Foo(x + y) +//│ ║ l.461: class Bar(x: int, y: int) extends Foo(x + y) //│ ╙── ^^^^^^^^^^ //│ class Bar(x: int, y: int) @@ -581,7 +583,7 @@ mixin Base { fun x = y } //│ ╔══[ERROR] identifier not found: y -//│ ║ l.581: fun x = y +//│ ║ l.583: fun x = y //│ ╙── ^ //│ mixin Base() { //│ fun x: error diff --git a/shared/src/test/diff/codegen/Nested.mls b/shared/src/test/diff/codegen/Nested.mls index b47e15c78..887b3af4f 100644 --- a/shared/src/test/diff/codegen/Nested.mls +++ b/shared/src/test/diff/codegen/Nested.mls @@ -1186,7 +1186,7 @@ fun main = //│ // Query 1 //│ globalThis.main = function main() { //│ return ((() => { -//│ let f = (x) => x == 0 === true ? 1 : g(x - 1); +//│ let f = (x) => x === 0 ? 1 : g(x - 1); //│ let g = (x) => f(x); //│ return f; //│ })()); diff --git a/shared/src/test/diff/ecoop23/ExpressionProblem.mls b/shared/src/test/diff/ecoop23/ExpressionProblem.mls index 09623fe14..b7b077695 100644 --- a/shared/src/test/diff/ecoop23/ExpressionProblem.mls +++ b/shared/src/test/diff/ecoop23/ExpressionProblem.mls @@ -200,7 +200,7 @@ fun mk(n) = if n is 0 then Lit(0) 1 then Neg(mk(n)) _ then Add(mk(n), mk(n)) -//│ fun mk: forall 'E. number -> 'E +//│ fun mk: forall 'E. anything -> 'E //│ where //│ 'E :> Add['E] | Lit | Neg['E] diff --git a/shared/src/test/diff/ecoop23/SimpleRegionDSL.mls b/shared/src/test/diff/ecoop23/SimpleRegionDSL.mls index 0e4251990..089e4fe2a 100644 --- a/shared/src/test/diff/ecoop23/SimpleRegionDSL.mls +++ b/shared/src/test/diff/ecoop23/SimpleRegionDSL.mls @@ -45,7 +45,7 @@ fun go(x, offset) = else let shared = go(x - 1, round(offset / 2)) Union(Translate(Vector(0 - offset, 0), shared), Translate(Vector(offset, 0), shared)) -//│ fun go: forall 'Region. (int, int,) -> 'Region +//│ fun go: forall 'Region. (0 | int & ~0, int,) -> 'Region //│ where //│ 'Region :> Circle | Union[Translate['Region]] @@ -87,36 +87,27 @@ module TestSize extends SizeBase, SizeExt //│ where //│ 'a <: Circle | Empty | Intersect['a] | Outside['a] | Scale['a] | Translate['a] | Union['a] | Univ -// TODO investigate -:re TestSize.size(Empty()) //│ int //│ res -//│ Runtime error: -//│ Error: non-exhaustive case expression +//│ = 1 -// TODO investigate -:re TestSize.size(circles) //│ int //│ res -//│ Runtime error: -//│ Error: non-exhaustive case expression +//│ = 13 -// TODO investigate -:re TestSize.size(Scale(Vector(1, 1), circles)) //│ int //│ res -//│ Runtime error: -//│ Error: non-exhaustive case expression +//│ = 14 // ******************* Adding a New Interpretation ******************* // a stupid power (int ** int) implementation fun pow(x, a) = if a is 0 then 1 else x * pow(x, a - 1) -//│ fun pow: (int, int,) -> int +//│ fun pow: (int, 0 | int & ~0,) -> int mixin Contains { fun contains(a, p) = @@ -139,29 +130,20 @@ module TestContains extends Contains //│ where //│ 'a <: Circle | Intersect['a] | Outside['a] | Translate['a] | Union['a] -// TODO investigate -:re TestContains.contains(Translate(Vector(0, 0), Circle(1)), Vector(0, 0)) //│ bool //│ res -//│ Runtime error: -//│ Error: non-exhaustive case expression +//│ = true -// TODO investigate -:re TestContains.contains(Intersect(Translate(Vector(0, 0), Circle(1)), Circle(1)), Vector(0, 0)) //│ bool //│ res -//│ Runtime error: -//│ Error: non-exhaustive case expression +//│ = true -// TODO investigate -:re TestContains.contains(circles, Vector(0, 0)) //│ bool //│ res -//│ Runtime error: -//│ Error: non-exhaustive case expression +//│ = false // ******************* Dependencies, Complex Interpretations, and Domain-Specific Optimizations ******************* @@ -187,16 +169,16 @@ mixin Text { :e module SizeText extends Text //│ ╔══[ERROR] Module `SizeText` does not contain member `size` -//│ ║ l.180: Translate then concat("a translated region of size ", toString(this.size(e))) +//│ ║ l.162: Translate then concat("a translated region of size ", toString(this.size(e))) //│ ╙── ^^^^^ //│ ╔══[ERROR] Module `SizeText` does not contain member `size` -//│ ║ l.179: Intersect then concat("the intersection of two regions of size ", toString(this.size(e))) +//│ ║ l.161: Intersect then concat("the intersection of two regions of size ", toString(this.size(e))) //│ ╙── ^^^^^ //│ ╔══[ERROR] Module `SizeText` does not contain member `size` -//│ ║ l.178: Union then concat("the union of two regions of size ", toString(this.size(e))) +//│ ║ l.160: Union then concat("the union of two regions of size ", toString(this.size(e))) //│ ╙── ^^^^^ //│ ╔══[ERROR] Module `SizeText` does not contain member `size` -//│ ║ l.177: Outside(a) then concat("outside a region of size ", toString(this.size(a))) +//│ ║ l.159: Outside(a) then concat("outside a region of size ", toString(this.size(a))) //│ ╙── ^^^^^ //│ module SizeText() { //│ fun text: (Circle | Intersect[anything] | Outside[anything] | Translate[anything] | Union[anything]) -> string @@ -329,7 +311,7 @@ module TestElim extends Eliminate TestElim.eliminate(Outside(Outside(Univ()))) //│ 'a //│ where -//│ 'a :> Intersect['a] | Translate['a] | Scale['a] | Univ | Outside['a] | Union['a] +//│ 'a :> Union['a] | Intersect['a] | Translate['a] | Scale['a] | Univ | Outside['a] //│ res //│ = Univ {} @@ -346,7 +328,7 @@ fun mk(n) = if n is 3 then Intersect(mk(n), mk(n)) 4 then Translate(Vector(0, 0), mk(n)) _ then Scale(Vector(0, 0), mk(n)) -//│ fun mk: forall 'Region. number -> 'Region +//│ fun mk: forall 'Region. anything -> 'Region //│ where //│ 'Region :> Intersect['Region] | Outside['Region] | Scale['Region] | Translate['Region] | Union['Region] @@ -377,32 +359,23 @@ module Lang extends SizeBase, SizeExt, Contains, Text, IsUniv, IsEmpty, Eliminat //│ 'd <: Intersect['d] | Outside['e] | Scale['d] | Translate['d] | Union['d] | Univ | ~Intersect[anything] & ~Outside[anything] & ~Scale[anything] & ~Translate[anything] & ~Union[anything] & ~Univ //│ 'e <: Intersect['e] | Outside['d] | Scale['e] | Translate['e] | Union['e] | Univ | ~Intersect[anything] & ~Outside[anything] & ~Scale[anything] & ~Translate[anything] & ~Union[anything] & ~Univ //│ 'b <: Intersect['b] | Outside['b & (Outside['b] | ~#Outside)] | Scale['b] | Translate['b] | Union['b] | 'c & ~#Intersect & ~#Outside & ~#Scale & ~#Translate & ~#Union -//│ 'c :> Translate['c] | Scale['c] | Outside['c] | Union['c] | Intersect['c] +//│ 'c :> Outside['c] | Union['c] | Intersect['c] | Translate['c] | Scale['c] //│ 'a <: Circle | Intersect['a] | Outside['a] | Translate['a] | Union['a] -// TODO investigate -:re Lang.size(circles) //│ int //│ res -//│ Runtime error: -//│ Error: non-exhaustive case expression +//│ = 13 -// TODO investigate -:re Lang.contains(circles, Vector(0, 0)) //│ bool //│ res -//│ Runtime error: -//│ Error: non-exhaustive case expression +//│ = false -// TODO investigate -:re Lang.text(circles) //│ string //│ res -//│ Runtime error: -//│ Error: non-exhaustive case expression +//│ = 'the union of two regions of size ' Lang.isUniv(circles) //│ bool @@ -414,13 +387,10 @@ Lang.isEmpty(circles) //│ res //│ = false -// TODO investigate -:re Lang.size(Lang.eliminate(circles)) //│ int //│ res -//│ Runtime error: -//│ Error: non-exhaustive case expression +//│ = 13 :re Lang.size(mk(100)) @@ -433,16 +403,16 @@ Lang.size(mk(100)) :re Lang.contains(mk(100), Vector(0, 0)) //│ ╔══[ERROR] Type mismatch in application: -//│ ║ l.434: Lang.contains(mk(100), Vector(0, 0)) +//│ ║ l.404: Lang.contains(mk(100), Vector(0, 0)) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── application of type `Scale[?Region]` does not match type `Circle | Intersect[?Region0] | Outside[?Region1] | Translate[?Region2] | Union[?Region3]` -//│ ║ l.348: _ then Scale(Vector(0, 0), mk(n)) +//│ ║ l.330: _ then Scale(Vector(0, 0), mk(n)) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── but it flows into application with expected type `Circle | Intersect[?Region4] | Outside[?Region5] | Translate[?Region6] | Union[?Region7]` -//│ ║ l.434: Lang.contains(mk(100), Vector(0, 0)) +//│ ║ l.404: Lang.contains(mk(100), Vector(0, 0)) //│ ║ ^^^^^^^ //│ ╟── Note: constraint arises from reference: -//│ ║ l.123: if a is +//│ ║ l.114: if a is //│ ╙── ^ //│ error | bool //│ res @@ -453,16 +423,16 @@ Lang.contains(mk(100), Vector(0, 0)) :re Lang.text(mk(100)) //│ ╔══[ERROR] Type mismatch in application: -//│ ║ l.454: Lang.text(mk(100)) +//│ ║ l.424: Lang.text(mk(100)) //│ ║ ^^^^^^^^^^^^^^^^^^ //│ ╟── application of type `Scale[?Region]` does not match type `Circle | Intersect[?Region0] | Outside[?Region1] | Translate[?Region2] | Union[?Region3]` -//│ ║ l.348: _ then Scale(Vector(0, 0), mk(n)) +//│ ║ l.330: _ then Scale(Vector(0, 0), mk(n)) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── but it flows into application with expected type `Circle | Intersect[?Region4] | Outside[?Region5] | Translate[?Region6] | Union[?Region7]` -//│ ║ l.454: Lang.text(mk(100)) +//│ ║ l.424: Lang.text(mk(100)) //│ ║ ^^^^^^^ //│ ╟── Note: constraint arises from reference: -//│ ║ l.175: if e is +//│ ║ l.157: if e is //│ ╙── ^ //│ error | string //│ res diff --git a/shared/src/test/diff/nu/Andong.mls b/shared/src/test/diff/nu/Andong.mls index ed6471fc8..0602b7a8a 100644 --- a/shared/src/test/diff/nu/Andong.mls +++ b/shared/src/test/diff/nu/Andong.mls @@ -5,11 +5,9 @@ class Union(a: Region, b: Region) //│ class Union[Region](a: Region, b: Region) -// * [FIXME:UCS] unhygienically desugars to: -// | | | | Desugared term: case x of { Union => let x = (x).a in let y = (x).b in x } fun hmm(x) = if x is Union(x, y) then x -//│ fun hmm: forall 'a. Union[{b: anything} & 'a] -> 'a +//│ fun hmm: forall 'a. Union['a] -> 'a fun hmm(x) = if x is Union(z, y) then x diff --git a/shared/src/test/diff/nu/BadUCS.mls b/shared/src/test/diff/nu/BadUCS.mls index 26ecfc6c8..fcea2c2e2 100644 --- a/shared/src/test/diff/nu/BadUCS.mls +++ b/shared/src/test/diff/nu/BadUCS.mls @@ -72,14 +72,8 @@ fun foo(x) = if x is M() then 0 //│ if expression was not desugared -:e fun foo0(x, y) = if x is y then 0 -//│ ╔══[ERROR] Cannot find constructor `y` in scope -//│ ║ l.76: fun foo0(x, y) = if x is y then 0 -//│ ╙── ^ -//│ fun foo0: (anything, anything,) -> error -//│ Code generation encountered an error: -//│ if expression was not desugared +//│ fun foo0: (anything, anything,) -> 0 fun foo = 0 @@ -88,7 +82,7 @@ fun foo = 0 :e fun foo0(x) = if x is foo() then 0 //│ ╔══[ERROR] Illegal pattern `foo` -//│ ║ l.89: fun foo0(x) = if x is foo() then 0 +//│ ║ l.83: fun foo0(x) = if x is foo() then 0 //│ ╙── ^^^ //│ fun foo0: anything -> error //│ Code generation encountered an error: @@ -97,13 +91,23 @@ fun foo0(x) = if x is foo() then 0 :e fun foo(x) = if x is foo() then 0 //│ ╔══[ERROR] Unhandled cyclic definition -//│ ║ l.98: fun foo(x) = if x is foo() then 0 +//│ ║ l.92: fun foo(x) = if x is foo() then 0 //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[ERROR] Illegal pattern `foo` -//│ ║ l.98: fun foo(x) = if x is foo() then 0 +//│ ║ l.92: fun foo(x) = if x is foo() then 0 //│ ╙── ^^^ //│ fun foo: anything -> error //│ Code generation encountered an error: //│ if expression was not desugared - +module Nil +class Cons[out A](head: A, tail: Cons[A] | Nil) +//│ module Nil() +//│ class Cons[A](head: A, tail: Cons[A] | Nil) + +fun join(xs) = + if xs is + Nil then "" + Cons(x, Nil) then toString(x) + Cons(x, xs') then concat(toString(x))(concat(", ")(join(xs'))) +//│ fun join: (Cons[anything] | Nil) -> string diff --git a/shared/src/test/diff/nu/EvalNegNeg.mls b/shared/src/test/diff/nu/EvalNegNeg.mls index a62b491cd..f77a17807 100644 --- a/shared/src/test/diff/nu/EvalNegNeg.mls +++ b/shared/src/test/diff/nu/EvalNegNeg.mls @@ -60,7 +60,7 @@ fun mk(n) = if n is 1 then Neg(mk(n)) _ then Add(mk(n), mk(n)) TestLang.eval(mk(0)) -//│ fun mk: forall 'E. number -> 'E +//│ fun mk: forall 'E. anything -> 'E //│ int //│ where //│ 'E :> Add['E] | Lit | Neg['E] diff --git a/shared/src/test/diff/nu/FilterMap.mls b/shared/src/test/diff/nu/FilterMap.mls index 8c5cd4dd2..916cfcdd3 100644 --- a/shared/src/test/diff/nu/FilterMap.mls +++ b/shared/src/test/diff/nu/FilterMap.mls @@ -28,30 +28,9 @@ fun filtermap(f, xs) = if xs is false then filtermap(f, ys) true then Cons(y, filtermap(f, ys)) [true, z] then Cons(y, filtermap(f, ys)) -//│ ╔══[ERROR] identifier not found: ys -//│ ║ l.27: Cons(y, ys) and f(ys) is -//│ ╙── ^^ -//│ ╔══[ERROR] Type mismatch in application: -//│ ║ l.27: Cons(y, ys) and f(ys) is -//│ ║ ^^^^^^^^ -//│ ║ l.28: false then filtermap(f, ys) -//│ ║ ^^^^^^^^^ -//│ ╟── reference of type `false` is not an instance of type `number` -//│ ║ l.28: false then filtermap(f, ys) -//│ ╙── ^^^^^ -//│ ╔══[ERROR] Type mismatch in application: -//│ ║ l.27: Cons(y, ys) and f(ys) is -//│ ║ ^^^^^^^^ -//│ ║ l.28: false then filtermap(f, ys) -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.29: true then Cons(y, filtermap(f, ys)) -//│ ║ ^^^^^^^^ -//│ ╟── reference of type `true` is not an instance of type `number` -//│ ║ l.29: true then Cons(y, filtermap(f, ys)) -//│ ╙── ^^^^ //│ ╔══[ERROR] type identifier not found: Tuple#2 //│ ╙── -//│ fun filtermap: ((Cons[nothing] | error | Nil) -> number & (Cons[nothing] | Nil) -> error, Cons[anything] | Nil,) -> (Cons[nothing] | Nil | error) +//│ fun filtermap: ((Cons[nothing] | Nil) -> (error | false | true), Cons[anything] | Nil,) -> (Cons[nothing] | Nil | error) //│ Code generation encountered an error: //│ unknown match case: Tuple#2 @@ -70,6 +49,35 @@ fun filtermap(f, xs) = if xs is True then filtermap(f, ys) False then Cons(y, filtermap(f, ys)) Pair(True, z) then Cons(z, filtermap(f, ys)) -//│ fun filtermap: forall 'head 'A. ('head -> (False | Pair[anything, 'A] | True), Cons['head & 'A] | Nil,) -> (Cons['A] | Nil) +//│ fun filtermap: forall 'head 'A. ('head -> (False | Pair[True, 'A] | True), Cons['head & 'A] | Nil,) -> (Cons['A] | Nil) +fun mkString(xs) = + if xs is + Nil then "" + Cons(x, xs') and xs' is + Nil then toString(x) + else concat(toString(x))(concat(", ")(mkString(xs'))) +//│ fun mkString: (Cons[anything] | Nil) -> string +let list = Cons(1, Cons(2, Cons(3, Cons(4, Cons(5, Cons(6, Cons(7, Nil))))))) +mkString of list +//│ let list: Cons[1 | 2 | 3 | 4 | 5 | 6 | 7] +//│ string +//│ list +//│ = Cons {} +//│ res +//│ = '1, 2, 3, 4, 5, 6, 7' + +mkString of filtermap(x => if x % 2 == 0 then True else False, list) +mkString of filtermap(x => if x % 2 == 0 then False else True, list) +mkString of filtermap(x => (if + x % 2 == 0 then False + x % 3 == 0 then Pair(True, x / 3) + else True), list) +//│ string +//│ res +//│ = '1, 3, 5, 7' +//│ res +//│ = '2, 4, 6' +//│ res +//│ = '2, 1, 4, 6' diff --git a/shared/src/test/diff/nu/repro_EvalNegNeg.mls b/shared/src/test/diff/nu/repro_EvalNegNeg.mls index 11cd86598..8607b174f 100644 --- a/shared/src/test/diff/nu/repro_EvalNegNeg.mls +++ b/shared/src/test/diff/nu/repro_EvalNegNeg.mls @@ -19,8 +19,8 @@ mixin EvalBase { Add(l, r) then this.eval(l) + this.eval(r) } //│ mixin EvalBase() { -//│ this: {eval: nothing -> 'a & 'lhs -> int} -//│ fun eval: (Add['lhs] | Lit | Neg[nothing]) -> (int | 'a) +//│ this: {eval: 'expr -> 'a & ('expr0 | 'lhs) -> int} +//│ fun eval: (Add['lhs] | Lit | Neg['expr0 & (Neg['expr] | ~#Neg)]) -> (int | 'a) //│ } // module TestLang extends EvalBase, EvalNeg @@ -29,34 +29,24 @@ module TestLang extends EvalBase //│ fun eval: 'a -> int //│ } //│ where -//│ 'a <: Add['a] | Lit | Neg[nothing] +//│ 'a <: Add['a] | Lit | Neg['a & (Neg['a] | ~#Neg)] fun mk(n) = if n is 0 then Lit(0) 1 then Neg(mk(n)) _ then Add(mk(n), mk(n)) -//│ fun mk: forall 'E. number -> 'E +//│ fun mk: forall 'E. anything -> 'E //│ where //│ 'E :> Add['E] | Lit | Neg['E] -// TODO support this in UCS :stats TestLang.eval(mk(0)) -//│ ╔══[ERROR] Type mismatch in application: -//│ ║ l.45: TestLang.eval(mk(0)) -//│ ║ ^^^^^^^^^^^^^^^^^^^^ -//│ ╟── application of type `Lit` does not match type `nothing` -//│ ║ l.36: 0 then Lit(0) -//│ ║ ^^^^^^ -//│ ╟── Note: type parameter A is defined at: -//│ ║ l.6: class Neg(expr: A) -//│ ╙── ^ -//│ error | int +//│ int //│ res //│ = 0 -//│ constrain calls : 242 -//│ annoying calls : 116 -//│ subtyping calls : 4071 +//│ constrain calls : 117 +//│ annoying calls : 32 +//│ subtyping calls : 856 diff --git a/shared/src/test/diff/parser/NegativeLits.mls b/shared/src/test/diff/parser/NegativeLits.mls new file mode 100644 index 000000000..79c8446d8 --- /dev/null +++ b/shared/src/test/diff/parser/NegativeLits.mls @@ -0,0 +1,14 @@ +:NewParser +:ParseOnly + +type MinusOne = -1 +//│ |#type| |MinusOne| |#=| |-1| +//│ Parsed: {type alias MinusOne(): -1 {}} + +fun f(x: MinusOne) = x +//│ |#fun| |f|(|x|#:| |MinusOne|)| |#=| |x| +//│ Parsed: {fun f = (x: MinusOne,) => x} + +f(-1) +//│ |f|(|-1|)| +//│ Parsed: {f (-1,)} diff --git a/shared/src/test/diff/parser/Ops.mls b/shared/src/test/diff/parser/Ops.mls index db31346ce..a86338ee1 100644 --- a/shared/src/test/diff/parser/Ops.mls +++ b/shared/src/test/diff/parser/Ops.mls @@ -206,3 +206,22 @@ a + //│ Parsed: {+ (a,) ({+ (* b 2) (* 1 3)},)} +a + + b * + c +//│ |a| |+|→|b| |*|→|c|←|←| +//│ Parsed: {+ (a,) ({* (b,) ({c},)},)} + +a * + b + + c +//│ |a| |*|→|b| |+|→|c|←|←| +//│ Parsed: {* (a,) ({+ (b,) ({c},)},)} + +a * + let x = 1 + b + + c +//│ |a| |*|→|#let| |x| |#=| |1|↵|b| |+|→|c|←|←| +//│ Parsed: {* (a,) ({let x = 1; + (b,) ({c},)},)} + diff --git a/shared/src/test/diff/tapl/NuSimplyTyped.mls b/shared/src/test/diff/tapl/NuSimplyTyped.mls new file mode 100644 index 000000000..e34f1c569 --- /dev/null +++ b/shared/src/test/diff/tapl/NuSimplyTyped.mls @@ -0,0 +1,182 @@ +:NewParser +:NewDefs + +let str = toString +fun concat2(a, b) = concat(a)(b) +fun concat3(a, b, c) = concat2(a, concat2(b, c)) +fun concat4(a, b, c, d) = concat2(a, concat3(b, c, d)) +fun concat5(a, b, c, d, e) = concat2(a, concat4(b, c, d, e)) +fun concat6(a, b, c, d, e, f) = concat2(a, concat5(b, c, d, e, f)) +fun concat7(a, b, c, d, e, f, g) = concat2(a, concat6(b, c, d, e, f, g)) +fun concat8(a, b, c, d, e, f, g, h) = concat2(a, concat7(b, c, d, e, f, g, h)) +fun par(a) = concat3("(", a, ")") +//│ let str: anything -> string +//│ fun concat2: (string, string,) -> string +//│ fun concat3: (string, string, string,) -> string +//│ fun concat4: (string, string, string, string,) -> string +//│ fun concat5: (string, string, string, string, string,) -> string +//│ fun concat6: (string, string, string, string, string, string,) -> string +//│ fun concat7: (string, string, string, string, string, string, string,) -> string +//│ fun concat8: (string, string, string, string, string, string, string, string,) -> string +//│ fun par: string -> string +//│ str +//│ = [Function: toString] + +type Option[A] = Some[A] | None +class Some[A](value: A) +module None +//│ type Option[A] = Some[A] | None +//│ class Some[A](value: A) +//│ module None() + +type Result[A, B] = Ok[A] | Err[B] +class Ok[A](value: A) +class Err[A](message: A) +//│ type Result[A, B] = Err[B] | Ok[A] +//│ class Ok[A](value: A) +//│ class Err[A](message: A) + +type Type = FunctionType | PrimitiveType +class PrimitiveType(name: string) +class FunctionType(lhs: Type, rhs: Type) +//│ type Type = FunctionType | PrimitiveType +//│ class PrimitiveType(name: string) +//│ class FunctionType(lhs: Type, rhs: Type) + +// Helpers. +fun _f(lhs, rhs) = FunctionType(lhs, rhs) +fun _t(name) = PrimitiveType(name) +//│ fun _f: (Type, Type,) -> FunctionType +//│ fun _t: string -> PrimitiveType + +type Term = Lit | Var | Abs | App +class Lit(tag: string, ty: Type) +class Var(name: string) +class Abs(lhs: Var, lty: Type, rhs: Term) +class App(lhs: Term, rhs: Term) +// class App(lhs: Term, rhs: Term): Term +//│ type Term = Abs | App | Lit | Var +//│ class Lit(tag: string, ty: Type) +//│ class Var(name: string) +//│ class Abs(lhs: Var, lty: Type, rhs: Term) +//│ class App(lhs: Term, rhs: Term) + +type TreeMap[A] = Node[A] | Empty +class Node[A](key: string, value: A, left: TreeMap[A], right: TreeMap[A]) +module Empty +//│ type TreeMap[A] = Node[A] | Empty +//│ class Node[A](key: string, value: A, left: TreeMap[A], right: TreeMap[A]) +//│ module Empty() + +fun insert(t, k, v) = + if t is + Node(k', _, l, r) and + slt(k)(k') then Node(k', v, insert(l, k, v), r) + sgt(k)(k') then Node(k', v, l, insert(r, k, v)) + _ then Node(k, v, l, r) + Empty then Node(k, v, Empty, Empty) +fun find(t, k) = + if t is + Node(k', v, l, r) and + slt(k)(k') then find(l, k) + sgt(k)(k') then find(r, k) + _ then Some(v) + Empty then None +//│ fun insert: forall 'A. (Empty | Node['A], string, 'A,) -> Node['A] +//│ fun find: forall 'A0. (Empty | Node['A0], string,) -> (None | Some['A0]) + +fun showType(ty) = + if ty is + FunctionType(PrimitiveType(name), rhs) then concat3(name, " -> ", showType(rhs)) + FunctionType(lhs, rhs) then concat4("(", showType(lhs), ") -> ", showType(rhs)) + PrimitiveType(name) then name +//│ fun showType: (FunctionType | PrimitiveType) -> string + +showType(_t("int")) +showType(_f(_t("int"), _t("bool"))) +showType(_f(_f(_t("int"), _t("bool")), _t("bool"))) +showType(_f(_t("bool"), _f(_t("int"), _t("bool")))) +//│ string +//│ res +//│ = 'int' +//│ res +//│ = 'int -> bool' +//│ res +//│ = '(int -> bool) -> bool' +//│ res +//│ = 'bool -> int -> bool' + +fun typeEqual(t1, t2) = + if + t1 is PrimitiveType(name1) and t2 is PrimitiveType(name2) then eq(name1)(name2) + t1 is FunctionType(lhs1, rhs1) and t2 is FunctionType(lhs2, rhs2) then + typeEqual(lhs1, lhs2) and typeEqual(rhs1, rhs2) + _ then false +//│ fun typeEqual: (anything, anything,) -> bool + +fun showTerm(t) = + if t is + Lit(tag, _) then toString(tag) + Var(name) then toString(name) + Abs(lhs, ty, rhs) then concat6("&", showTerm(lhs), ": ", showType(ty), " => ", showTerm(rhs)) + App(Abs(lhs0, ty, lhs1), rhs) then + concat5("((", showTerm(Abs(lhs0, ty, rhs)), ") ", showTerm(rhs), ")") + App(lhs, rhs) then par(concat3(showTerm(lhs), " ", showTerm(rhs))) +//│ fun showTerm: (Abs | App | Lit | Var) -> string + +showTerm(Var("x")) +showTerm(Abs(Var("x"), _t("int"), Var("y"))) +showTerm(App(Var("x"), Var("y"))) +showTerm(App(Abs(Var("x"), _t("int"), Var("y")), Var("z"))) +//│ string +//│ res +//│ = 'x' +//│ res +//│ = '&x: int => y' +//│ res +//│ = '(x y)' +//│ res +//│ = '((&x: int => z) z)' + +// Removing the return type annotation causes stack overflow. +fun typeTerm(t: Term, ctx: TreeMap[Type]): Result[Type, string] = + if t is + Lit(_, ty) then Ok(ty) + Var(name) and find(ctx, name) is + Some(ty) then Ok(ty) + None then Err(concat3("unbound variable `", name, "`")) + Abs(Var(name), ty, body) and typeTerm(body, insert(ctx, name, ty)) is + Ok(resTy) then Ok(FunctionType(ty, resTy)) + Err(message) then Err(message) + App(lhs, rhs) and typeTerm(lhs, ctx) is + Ok(FunctionType(pTy, resTy)) and typeTerm(rhs, ctx) is + Ok(aTy) and + typeEqual(pTy, aTy) then Ok(resTy) + else Err(concat5("expect the argument to be of type `", showType(pTy), "` but found `", showType(aTy), "`")) + Err(message) then Err(message) + Ok(PrimitiveType(name)) then Err(concat3("cannot apply primitive type `", name, "`")) + Err(message) then Err(message) +//│ fun typeTerm: (t: Term, ctx: TreeMap[Type],) -> Result[Type, string] + +fun showTypeTerm(t, ctx) = + if typeTerm(t, ctx) is + Ok(ty) then concat3(showTerm(t), " : ", showType(ty)) + Err(message) then concat2("Type error: ", message) +//│ fun showTypeTerm: (Term, TreeMap[Type],) -> string + +showTypeTerm(Var("x"), Empty) +showTypeTerm(Abs(Var("x"), _t("int"), Var("x")), Empty) +showTypeTerm(App(Var("f"), Lit("0", _t("int"))), insert(Empty, "f", _f(_t("int"), _t("int")))) +showTypeTerm(App(Var("f"), Lit("0.2", _t("float"))), insert(Empty, "f", _f(_t("int"), _t("int")))) +showTypeTerm(App(Var("f"), Lit("0", _t("int"))), insert(Empty, "f", _t("string"))) +//│ string +//│ res +//│ = 'Type error: unbound variable `x`' +//│ res +//│ = '&x: int => x : int -> int' +//│ res +//│ = '(f 0) : int' +//│ res +//│ = 'Type error: expect the argument to be of type `int` but found `float`' +//│ res +//│ = 'Type error: cannot apply primitive type `string`' diff --git a/shared/src/test/diff/tapl/NuUntyped.mls b/shared/src/test/diff/tapl/NuUntyped.mls new file mode 100644 index 000000000..2ecfed25b --- /dev/null +++ b/shared/src/test/diff/tapl/NuUntyped.mls @@ -0,0 +1,460 @@ +:NewParser +:NewDefs + +fun concat2(a, b) = concat(a)(b) +fun concat3(a, b, c) = concat2(a, concat2(b, c)) +fun concat4(a, b, c, d) = concat2(a, concat3(b, c, d)) +fun concat5(a, b, c, d, e) = concat2(a, concat4(b, c, d, e)) +fun concat6(a, b, c, d, e, f) = concat2(a, concat5(b, c, d, e, f)) +fun concat7(a, b, c, d, e, f, g) = concat2(a, concat6(b, c, d, e, f, g)) +fun concat8(a, b, c, d, e, f, g, h) = concat2(a, concat7(b, c, d, e, f, g, h)) +fun par(a) = concat3("(", a, ")") +//│ fun concat2: (string, string,) -> string +//│ fun concat3: (string, string, string,) -> string +//│ fun concat4: (string, string, string, string,) -> string +//│ fun concat5: (string, string, string, string, string,) -> string +//│ fun concat6: (string, string, string, string, string, string,) -> string +//│ fun concat7: (string, string, string, string, string, string, string,) -> string +//│ fun concat8: (string, string, string, string, string, string, string, string,) -> string +//│ fun par: string -> string + +:escape +let String: nothing +let makeString: anything => { length: int, charCodeAt: int => int } = String +let StringInstance: { fromCharCode: int => string } = String +//│ let String: nothing +//│ let makeString: anything -> {charCodeAt: int -> int, length: int} +//│ let StringInstance: {fromCharCode: int -> string} +//│ String +//│ = +//│ makeString +//│ = [Function: String] +//│ StringInstance +//│ = [Function: String] + + +let anythingToString = toString +fun fromCharCode(n: int) = StringInstance.fromCharCode(n) +fun stringCharCodeAt(s: string, i) = makeString(s).charCodeAt(i) +fun stringLength(s: string) = makeString(s).length +//│ let anythingToString: anything -> string +//│ fun fromCharCode: (n: int,) -> string +//│ fun stringCharCodeAt: (s: string, int,) -> int +//│ fun stringLength: (s: string,) -> int +//│ anythingToString +//│ = [Function: toString] + +type Option[A] = Some[A] | None +class Some[A](value: A) { + fun toString() = concat3("Some(", anythingToString(value), ")") +} +module None { + fun toString() = "None" +} +//│ type Option[A] = Some[A] | None +//│ class Some[A](value: A) { +//│ fun toString: () -> string +//│ } +//│ module None() { +//│ fun toString: () -> "None" +//│ } + +type List[A] = Cons[A] | Nil +class Cons[A](head: A, tail: List[A]) +module Nil +//│ type List[A] = Cons[A] | Nil +//│ class Cons[A](head: A, tail: List[A]) +//│ module Nil() + +fun list1(x) = Cons(x, Nil) +fun list2(x, y) = Cons(x, list1(y)) +fun list3(x, y, z) = Cons(x, list2(y, z)) +fun list4(x, y, z, w) = Cons(x, list3(y, z, w)) +fun list5(x, y, z, w, v) = Cons(x, list4(y, z, w, v)) +fun list6(x, y, z, w, v, u) = Cons(x, list5(y, z, w, v, u)) +fun list7(x, y, z, w, v, u, t) = Cons(x, list6(y, z, w, v, u, t)) +fun list8(x, y, z, w, v, u, t, s) = Cons(x, list7(y, z, w, v, u, t, s)) +//│ fun list1: forall 'A. 'A -> Cons['A] +//│ fun list2: forall 'A. ('A, 'A,) -> Cons['A] +//│ fun list3: forall 'A. ('A, 'A, 'A,) -> Cons['A] +//│ fun list4: forall 'A. ('A, 'A, 'A, 'A,) -> Cons['A] +//│ fun list5: forall 'A. ('A, 'A, 'A, 'A, 'A,) -> Cons['A] +//│ fun list6: forall 'A. ('A, 'A, 'A, 'A, 'A, 'A,) -> Cons['A] +//│ fun list7: forall 'A. ('A, 'A, 'A, 'A, 'A, 'A, 'A,) -> Cons['A] +//│ fun list8: forall 'A. ('A, 'A, 'A, 'A, 'A, 'A, 'A, 'A,) -> Cons['A] + +fun findFirst(list, p) = + if list is + Nil then None + Cons(x, xs) and + p(x) then Some(x) + else findFirst(xs, p) +//│ fun findFirst: forall 'A. (Cons['A] | Nil, 'A -> anything,) -> (None | Some['A]) + +fun listConcat(xs, ys) = + if xs is + Nil then ys + Cons(x, xs') then Cons(x, listConcat(xs', ys)) +//│ fun listConcat: forall 'A 'A0 'a. (Cons['A] | Nil, List['A0] & 'a,) -> (Cons['A0] | 'a) +//│ where +//│ 'A <: 'A0 + +fun listContains(xs, x) = + if xs is + Nil then false + Cons(x', xs') and + eq(x)(x') then true + _ then listContains(xs', x) +//│ fun listContains: forall 'A. (Cons['A] | Nil, anything,) -> bool + +// Remove all occurrences of x from xs. +fun listWithout(xs, x) = + if xs is + Nil then Nil + Cons(x', xs') and + eq(x)(x') then listWithout(xs', x) + _ then Cons(x', listWithout(xs', x)) +//│ fun listWithout: forall 'A 'A0. (Cons['A] | Nil, anything,) -> (Cons['A0] | Nil) +//│ where +//│ 'A <: 'A0 + +// fix this: +// fun listJoin(xs, sep) = +// if xs is +// Nil then "" +// Cons(x, Nil) then toString(x) +// Cons(x, xs') then concat3(toString(x), sep, listJoin(xs', sep)) +fun listJoin(xs, sep) = + if xs is + Nil then "" + Cons(x, xs') and xs' is + Nil then toString(x) + _ then concat3(toString(x), sep, listJoin(xs', sep)) +//│ fun listJoin: forall 'A. (Cons['A] | Nil, string,) -> string + +listJoin(list3("x", "y", "z"), ", ") +//│ string +//│ res +//│ = 'x, y, z' + +type Term = Var | Abs | App +class Var(name: string) +class Abs(lhs: Var, rhs: Term) +class App(lhs: Term, rhs: Term) +//│ type Term = Abs | App | Var +//│ class Var(name: string) +//│ class Abs(lhs: Var, rhs: Term) +//│ class App(lhs: Term, rhs: Term) + +fun showTerm(t) = + if t is + Var(name) then toString(name) + Abs(lhs, rhs) then concat4("&", showTerm(lhs), ". ", showTerm(rhs)) + App(Abs(lhs0, lhs1), rhs) then + concat8("((", "&", showTerm(lhs0), ". ", showTerm(lhs1), ") ", showTerm(rhs), ")") + App(lhs, rhs) then par(concat3(showTerm(lhs), " ", showTerm(rhs))) +//│ fun showTerm: (Abs | App | Var) -> string + +showTerm(Var("x")) +showTerm(Abs(Var("x"), Var("y"))) +showTerm(App(Var("x"), Var("y"))) +showTerm(App(Abs(Var("x"), Var("y")), Var("z"))) +//│ string +//│ res +//│ = 'x' +//│ res +//│ = '&x. y' +//│ res +//│ = '(x y)' +//│ res +//│ = '((&x. y) z)' + +fun isValue(t) = + if t is + Var then true + Abs then true + App then false +//│ fun isValue: (Abs | App | Var) -> bool + +isValue(Var("x")) +isValue(Abs(Var("x"), Var("y"))) +isValue(App(Var("x"), Var("y"))) +//│ bool +//│ res +//│ = true +//│ res +//│ = true +//│ res +//│ = false + +fun hasFree(t, n) = + if t is + // let __ = debug(concat3(showTerm(t), ", ", n)) + Var(na) then eq(n)(na) + Abs(Var(name), body) and eq(name)(n) then false + Abs(Var(name), body) then hasFree(body, n) + App(lhs, rhs) then hasFree(lhs, n) || hasFree(rhs, n) + _ then false +//│ fun hasFree: (anything, anything,) -> bool + +fun showHasFree(t, n) = + concat4(showTerm(t), if hasFree(t, n) then " has " else " DOES NOT have ", "free variable ", n) +//│ fun showHasFree: (Abs | App | Var, string,) -> string + +showHasFree(Var("x"), "x") +showHasFree(Var("x"), "y") +showHasFree(Abs(Var("x"), Var("x")), "x") +showHasFree(Abs(Var("x"), Var("x")), "y") +showHasFree(Abs(Var("x"), Var("y")), "x") +showHasFree(Abs(Var("x"), Var("y")), "y") +showHasFree(App(Var("x"), Var("y")), "x") +showHasFree(App(Var("x"), Var("y")), "y") +showHasFree(App(Abs(Var("x"), Var("x")), Var("x")), "x") +showHasFree(App(Abs(Var("x"), Var("x")), Var("x")), "y") +showHasFree(App(Abs(Var("x"), Var("x")), Var("y")), "y") +showHasFree(App(Abs(Var("x"), Var("x")), Var("x")), "y") +//│ string +//│ res +//│ = 'x has free variable x' +//│ res +//│ = 'x DOES NOT have free variable y' +//│ res +//│ = '&x. x DOES NOT have free variable x' +//│ res +//│ = '&x. x DOES NOT have free variable y' +//│ res +//│ = '&x. y DOES NOT have free variable x' +//│ res +//│ = '&x. y has free variable y' +//│ res +//│ = '(x y) has free variable x' +//│ res +//│ = '(x y) has free variable y' +//│ res +//│ = '((&x. x) x) has free variable x' +//│ res +//│ = '((&x. x) x) DOES NOT have free variable y' +//│ res +//│ = '((&x. x) y) has free variable y' +//│ res +//│ = '((&x. x) x) DOES NOT have free variable y' + +fun fv(t) = + if t is + Var(name) then list1(name) + Abs(Var(name), body) then listWithout(fv(body), name) + App(lhs, rhs) then listConcat(fv(lhs), fv(rhs)) +//│ fun fv: forall 'A. (Abs | App | Var) -> (Cons['A] | Nil) +//│ where +//│ 'A :> string + +fun showFv(t) = + concat2(showTerm(t), if fv(t) is + Nil then " DOES NOT have free variables" + _ then concat2(" has free variables: ", listJoin(fv(t), ", ")) + ) +//│ fun showFv: (Abs | App | Var) -> string + +showFv(Var("x")) +showFv(Abs(Var("x"), Var("x"))) +showFv(Abs(Var("x"), Var("y"))) +showFv(App(Var("x"), Var("y"))) +showFv(App(Abs(Var("x"), Var("x")), Var("x"))) +//│ string +//│ res +//│ = 'x has free variables: x' +//│ res +//│ = '&x. x DOES NOT have free variables' +//│ res +//│ = '&x. y has free variables: y' +//│ res +//│ = '(x y) has free variables: x, y' +//│ res +//│ = '((&x. x) x) has free variables: x' + +fun tryNextAlphabet(initialCode, currentCode, freeNames) = + if + currentCode + > 122 then tryNextAlphabet(initialCode, 97, freeNames) + == initialCode then None + let name = fromCharCode(currentCode) + listContains(freeNames, name) then tryNextAlphabet(initialCode, currentCode + 1, freeNames) + _ then Some(name) +//│ fun tryNextAlphabet: forall 'A. (number, int, Cons['A] | Nil,) -> (None | Some[string]) + +toString(tryNextAlphabet(97, 97, list1("a"))) +toString(tryNextAlphabet(97, 98, list1("a"))) +toString(tryNextAlphabet(97, 98, list2("a", "b"))) +toString(tryNextAlphabet(121, 122, list1("y"))) +toString(tryNextAlphabet(121, 122, list2("y", "z"))) +//│ string +//│ res +//│ = 'None' +//│ res +//│ = 'Some(b)' +//│ res +//│ = 'Some(c)' +//│ res +//│ = 'Some(z)' +//│ res +//│ = 'Some(a)' + +fun tryAppendDigits(name, index, freeNames) = + if + let currentName = concat2(name, toString(index)) + listContains(freeNames, currentName) then + tryAppendDigits(name, index + 1, freeNames) + _ then currentName +//│ fun tryAppendDigits: forall 'A. (string, int, Cons['A] | Nil,) -> string + +// Note: some weird behavior here... Just try the commented code. +fun findFreshName(name, freeNames) = + if + stringLength(name) == 1 and + let charCode = stringCharCodeAt(name, 0) + tryNextAlphabet(charCode, charCode + 1, freeNames) is + Some(newName) then newName + _ then tryAppendDigits(name, 0, freeNames) +//│ fun findFreshName: forall 'A 'A0 'A1. (string, Cons[in 'A | 'A0 | 'A1 out 'A & 'A0 & 'A1] | Nil,) -> string + +// Find a fresh name to replace `name` that does not conflict with any bound +// variables in the `body`. +fun freshName(name, body) = findFreshName(name, fv(body)) +//│ fun freshName: (string, Abs | App | Var,) -> string + +fun subst(t, n, v) = + if t is + Var(name) and eq(name)(n) then v + Abs(Var(name), body) and ne(name)(n) and + hasFree(v, name) and freshName(name, body) is newName then + subst(Abs(Var(newName), subst(body, name, Var(newName))), n, v) + _ then Abs(Var(name), subst(body, n, v)) + App(lhs, rhs) then App(subst(lhs, n, v), subst(rhs, n, v)) + _ then t +//│ fun subst: forall 'a. (Abs | App | Var | Term & 'a & ~#Abs & ~#App & ~#Var, anything, Term & 'a,) -> (Abs | App | Var | 'a) + +fun showSubst(t, n, v) = + concat8(showTerm(t), " [", n, " / ", showTerm(v), "]", " => ", showTerm(subst(t, n, v))) +//│ fun showSubst: (Abs | App | Var, string, Abs & Term | App & Term | Var & Term,) -> string + +showSubst(Var("x"), "x", Var("y")) +showSubst(Abs(Var("x"), Var("x")), "x", Var("z")) +showSubst(App(Var("x"), Var("y")), "x", Abs(Var("x"), Var("x"))) +showSubst(App(Abs(Var("x"), Var("x")), Var("x")), "x", Abs(Var("y"), Var("y"))) +showSubst(Abs(Var("x"), App(Var("x"), Var("y"))), "y", Var("x")) +showSubst(Abs(Var("z"), Abs(Var("x"), App(Var("z"), App(Var("x"), Var("y"))))), "y", Var("x")) +//│ string +//│ res +//│ = 'x [x / y] => y' +//│ res +//│ = '&x. x [x / z] => &x. x' +//│ res +//│ = '(x y) [x / &x. x] => ((&x. x) y)' +//│ res +//│ = '((&x. x) x) [x / &y. y] => ((&x. x) &y. y)' +//│ res +//│ = '&x. (x y) [y / x] => &z. (z x)' +//│ res +//│ = '&z. &x. (z (x y)) [y / x] => &z. &a. (z (a x))' + +type Result = Normal | Stuck | Stepped +class Normal(term: Term) { + fun toString() = concat2("Normal form: ", showTerm(term)) +} +class Stuck(term: Term, part: Term) { + fun toString() = concat4("Stuck: ", showTerm(part), " in ", showTerm(term)) +} +class Stepped(from: Term, to: Term) { + fun toString() = concat3(showTerm(from), " => ", showTerm(to)) +} +//│ type Result = Normal | Stepped | Stuck +//│ class Normal(term: Term) { +//│ fun toString: () -> string +//│ } +//│ class Stuck(term: Term, part: Term) { +//│ fun toString: () -> string +//│ } +//│ class Stepped(from: Term, to: Term) { +//│ fun toString: () -> string +//│ } + +fun stepByValue(t) = + if t is + Var then Stuck(t, t) + Abs then Normal(t) + App(lhs, rhs) and stepByValue(lhs) is + Stepped(_, lhs) then Stepped(t, App(lhs, rhs)) + Stuck(_, part) then Stuck(t, part) + Normal and stepByValue(rhs) is + Stepped(_, rhs) then Stepped(t, App(lhs, rhs)) + Stuck(_, part) then Stuck(t, part) + Normal and lhs is + Abs(Var(name), body) then Stepped(t, subst(body, name, rhs)) + _ then Stuck(t, lhs) +//│ fun stepByValue: (Abs | App | Var) -> (Normal | Stepped | Stuck) + +toString of stepByValue of Var("x") +toString of stepByValue of Abs(Var("x"), Var("y")) +toString of stepByValue of App(Var("x"), Var("y")) +toString of stepByValue of App(Abs(Var("x"), Var("x")), Var("x")) +toString of stepByValue of App(Abs(Var("x"), Var("x")), Abs(Var("y"), Var("y"))) +//│ string +//│ res +//│ = 'Stuck: x in x' +//│ res +//│ = 'Normal form: &x. y' +//│ res +//│ = 'Stuck: x in (x y)' +//│ res +//│ = 'Stuck: x in ((&x. x) x)' +//│ res +//│ = '((&x. x) &y. y) => &y. y' + +fun evalByValue(t) = + if stepByValue(t) is result and result is + Stepped(_, term) then evalByValue(term) + else result +//│ fun evalByValue: (Abs | App | Var) -> (Normal | Stuck) + +// Let's program with Church encoding! +let zero = Abs(Var("f"), Abs(Var("x"), Var("x"))) +let one = Abs(Var("f"), Abs(Var("x"), App(Var("f"), Var("x")))) +toString of stepByValue of zero +toString of stepByValue of one +let succ = Abs(Var("n"), Abs(Var("f"), Abs(Var("x"), App(Var("f"), App(App(Var("n"), Var("f")), Var("x")))))) +toString of stepByValue of succ +toString of stepByValue of App(succ, zero) +//│ let zero: Abs +//│ let one: Abs +//│ let succ: Abs +//│ string +//│ zero +//│ = Abs {} +//│ one +//│ = Abs {} +//│ res +//│ = 'Normal form: &f. &x. x' +//│ res +//│ = 'Normal form: &f. &x. (f x)' +//│ succ +//│ = Abs {} +//│ res +//│ = 'Normal form: &n. &f. &x. (f ((n f) x))' +//│ res +//│ = '((&n. &f. &x. (f ((n f) x))) &f. &x. x) => &f. &x. (f (((&f. &x. x) f) x))' + +toString of evalByValue of App(succ, App(succ, zero)) +toString of evalByValue of App(succ, App(succ, App(succ, App(succ, zero)))) +//│ string +//│ res +//│ = 'Normal form: &f. &x. (f (((&f. &x. (f (((&f. &x. x) f) x))) f) x))' +//│ res +//│ = 'Normal form: &f. &x. (f (((&f. &x. (f (((&f. &x. (f (((&f. &x. (f (((&f. &x. x) f) x))) f) x))) f) x))) f) x))' + +fun equalTerm(a, b) = + if a is + Var(na) and b is Var(nb) then eq(na)(nb) + Abs(la, ra) and b is Abs(lb, rb) then equalTerm(la, lb) && equalTerm(ra, rb) + App(la, ra) and b is App(lb, rb) then equalTerm(la, lb) && equalTerm(ra, rb) + _ then false +//│ fun equalTerm: (anything, anything,) -> bool diff --git a/shared/src/test/diff/tapl/SimplyTyped.mls b/shared/src/test/diff/tapl/SimplyTyped.mls new file mode 100644 index 000000000..2b386c208 --- /dev/null +++ b/shared/src/test/diff/tapl/SimplyTyped.mls @@ -0,0 +1,352 @@ +:NewParser + +fun concat2(a, b) = concat(a)(b) +fun concat3(a, b, c) = concat2(a, concat2(b, c)) +fun concat4(a, b, c, d) = concat2(a, concat3(b, c, d)) +fun concat5(a, b, c, d, e) = concat2(a, concat4(b, c, d, e)) +fun concat6(a, b, c, d, e, f) = concat2(a, concat5(b, c, d, e, f)) +fun concat7(a, b, c, d, e, f, g) = concat2(a, concat6(b, c, d, e, f, g)) +fun concat8(a, b, c, d, e, f, g, h) = concat2(a, concat7(b, c, d, e, f, g, h)) +fun par(a) = concat3("(", a, ")") +//│ concat2: (string, string,) -> string +//│ = [Function: concat2] +//│ concat3: (string, string, string,) -> string +//│ = [Function: concat3] +//│ concat4: (string, string, string, string,) -> string +//│ = [Function: concat4] +//│ concat5: (string, string, string, string, string,) -> string +//│ = [Function: concat5] +//│ concat6: (string, string, string, string, string, string,) -> string +//│ = [Function: concat6] +//│ concat7: (string, string, string, string, string, string, string,) -> string +//│ = [Function: concat7] +//│ concat8: (string, string, string, string, string, string, string, string,) -> string +//│ = [Function: concat8] +//│ par: string -> string +//│ = [Function: par] + +class Option +class Some(value): Option +class None(): Option +//│ Defined class Option +//│ Defined class Some +//│ Defined class None +//│ Option: () -> Option +//│ = [Function: Option1] +//│ Some: 'value -> (Some & {value: 'value}) +//│ = [Function: Some1] +//│ None: () -> None +//│ = [Function: None1] + +class Result +class Ok(value): Result +class Err(message): Result +//│ Defined class Result +//│ Defined class Ok +//│ Defined class Err +//│ Result: () -> Result +//│ = [Function: Result1] +//│ Ok: 'value -> (Ok & {value: 'value}) +//│ = [Function: Ok1] +//│ Err: 'message -> (Err & {message: 'message}) +//│ = [Function: Err1] + +class Type +class FunctionType(lhs, rhs): Type +class PrimitiveType(name): Type +//│ Defined class Type +//│ Defined class FunctionType +//│ Defined class PrimitiveType +//│ Type: () -> Type +//│ = [Function: Type1] +//│ FunctionType: ('lhs, 'rhs,) -> (FunctionType & {lhs: 'lhs, rhs: 'rhs}) +//│ = [Function: FunctionType1] +//│ PrimitiveType: 'name -> (PrimitiveType & {name: 'name}) +//│ = [Function: PrimitiveType1] + +// Helpers. +fun _f(lhs, rhs) = FunctionType(lhs, rhs) +fun _t(name) = PrimitiveType(name) +//│ _f: ('lhs, 'rhs,) -> (FunctionType & {lhs: 'lhs, rhs: 'rhs}) +//│ = [Function: _f] +//│ _t: 'name -> (PrimitiveType & {name: 'name}) +//│ = [Function: _t] + +class Term +class Lit(tag, ty): Term +class Var(name): Term +class Abs(lhs, lty, rhs): Term +class App(lhs, rhs): Term +// class App(lhs: Term, rhs: Term): Term +//│ Defined class Term +//│ Defined class Lit +//│ Defined class Var +//│ Defined class Abs +//│ Defined class App +//│ Term: () -> Term +//│ = [Function: Term1] +//│ Lit: ('tag, 'ty,) -> (Lit & {tag: 'tag, ty: 'ty}) +//│ = [Function: Lit1] +//│ Var: 'name -> (Var & {name: 'name}) +//│ = [Function: Var1] +//│ Abs: ('lhs, 'lty, 'rhs,) -> (Abs & {lhs: 'lhs, lty: 'lty, rhs: 'rhs}) +//│ = [Function: Abs1] +//│ App: ('lhs, 'rhs,) -> (App & {lhs: 'lhs, rhs: 'rhs}) +//│ = [Function: App1] + +class Assumption(name, ty) +//│ Defined class Assumption +//│ Assumption: ('name, 'ty,) -> (Assumption & {name: 'name, ty: 'ty}) +//│ = [Function: Assumption1] + +class Tree +class Node(key, value, left, right): Tree +class Empty(): Tree +//│ Defined class Tree +//│ Defined class Node +//│ Defined class Empty +//│ Tree: () -> Tree +//│ = [Function: Tree1] +//│ Node: ('key, 'value, 'left, 'right,) -> (Node & {key: 'key, left: 'left, right: 'right, value: 'value}) +//│ = [Function: Node1] +//│ Empty: () -> Empty +//│ = [Function: Empty1] + +fun empty = Empty() +fun insert(t, k, v) = + if t is + Node(k', _, l, r) and + slt(k)(k') then Node(k', v, insert(l, k, v), r) + sgt(k)(k') then Node(k', v, l, insert(r, k, v)) + _ then Node(k, v, l, r) + Empty then Node(k, v, empty, empty) +fun find(t, k) = + if t is + Node(k', v, l, r) and + slt(k)(k') then find(l, k) + sgt(k)(k') then find(r, k) + _ then Some(v) + Empty then None() +//│ empty: Empty +//│ = [Function: empty] +//│ insert: ('a, string & 'key, 'value,) -> 'right +//│ where +//│ 'right :> Node & {key: 'key, left: Empty | 'left | 'right, right: Empty | 'right | 'right0, value: 'value} +//│ 'a <: Empty | Node & {key: string & 'key, left: 'left, right: 'right0} +//│ 'right0 <: 'a +//│ 'left <: 'a +//│ = [Function: insert] +//│ find: ('right, string,) -> (Some & {value: 'value} | None) +//│ where +//│ 'right <: Empty | Node & {key: string, left: 'right, right: 'right, value: 'value} +//│ = [Function: find] + +fun showType(ty) = + if ty is + FunctionType(PrimitiveType(name), rhs) then concat3(name, " -> ", showType(rhs)) + FunctionType(lhs, rhs) then concat4("(", showType(lhs), ") -> ", showType(rhs)) + PrimitiveType(name) then name +//│ showType: 'lhs -> string +//│ where +//│ 'lhs <: FunctionType & {lhs: 'lhs & (PrimitiveType & {name: string} | ~#PrimitiveType), rhs: 'lhs} | PrimitiveType & {name: string} +//│ = [Function: showType] + +showType(_t("int")) +showType(_f(_t("int"), _t("bool"))) +showType(_f(_f(_t("int"), _t("bool")), _t("bool"))) +showType(_f(_t("bool"), _f(_t("int"), _t("bool")))) +//│ res: string +//│ = 'int' +//│ res: string +//│ = 'int -> bool' +//│ res: string +//│ = '(int -> bool) -> bool' +//│ res: string +//│ = 'bool -> int -> bool' + +fun typeEqual(t1, t2) = + if + t1 is PrimitiveType(name1) and t2 is PrimitiveType(name2) then eq(name1)(name2) + t1 is FunctionType(lhs1, rhs1) and t2 is FunctionType(lhs2, rhs2) then + typeEqual(lhs1, lhs2) and typeEqual(rhs1, rhs2) + _ then false +//│ typeEqual: ('rhs, 'rhs,) -> bool +//│ where +//│ 'rhs <: FunctionType & {lhs: 'rhs, rhs: 'rhs} | PrimitiveType | ~FunctionType & ~PrimitiveType +//│ = [Function: typeEqual] + +fun showTerm(t) = + if t is + Lit(tag, _) then toString(tag) + Var(name) then toString(name) + Abs(lhs, ty, rhs) then concat6("&", showTerm(lhs), ": ", showType(ty), " => ", showTerm(rhs)) + App(Abs(lhs0, ty, lhs1), rhs) then + concat5("((", showTerm(Abs(lhs0, ty, rhs)), ") ", showTerm(rhs), ")") + App(lhs, rhs) then par(concat3(showTerm(lhs), " ", showTerm(rhs))) +//│ showTerm: 'rhs -> string +//│ where +//│ 'rhs <: Abs & {lhs: 'rhs, lty: 'lty, rhs: 'rhs} | App & {lhs: 'rhs & (Abs & {lhs: 'rhs, lty: 'lty} | ~#Abs), rhs: 'rhs} | Lit | Var +//│ 'lty <: FunctionType & {lhs: 'lty & (PrimitiveType & {name: string} | ~#PrimitiveType), rhs: 'lty} | PrimitiveType & {name: string} +//│ = [Function: showTerm] + +showTerm(Var("x")) +showTerm(Abs(Var("x"), _t("int"), Var("y"))) +showTerm(App(Var("x"), Var("y"))) +showTerm(App(Abs(Var("x"), _t("int"), Var("y")), Var("z"))) +//│ res: string +//│ = 'x' +//│ res: string +//│ = '&x: int => y' +//│ res: string +//│ = '(x y)' +//│ res: string +//│ = '((&x: int => z) z)' + +// FIXME +fun typeTerm(t, ctx) = + if t is + Lit(_, ty) then Ok(ty) + Var(name) and find(ctx, name) is + Some(ty) then Ok(ty) + None then Err(concat3("unbound variable `", name, "`")) + Abs(Var(name), ty, body) and typeTerm(body, insert(ctx, name, ty)) is + Ok(resTy) then Ok(FunctionType(ty, resTy)) + Err(message) then Err(message) + App(lhs, rhs) and typeTerm(lhs, ctx) is + Ok(FunctionType(pTy, resTy)) and typeTerm(rhs, ctx) is + Ok(aTy) and + typeEqual(pTy, aTy) then Ok(resTy) + _ then Err(concat5("expect the argument to be of type `", showType(pTy), "` but found `", showType(aTy), "`")) + Err(message) then Err(message) + Ok(PrimitiveType(name)) then Err(concat3("cannot apply primitive type `", name, "`")) + Err(message) then Err(message) +//│ ╔══[ERROR] Cyclic-looking constraint while typing binding of lambda expression; a type annotation may be required +//│ ║ l.206: fun typeTerm(t, ctx) = +//│ ║ ^^^^^^^^^^ +//│ ║ l.207: if t is +//│ ║ ^^^^^^^^^ +//│ ║ l.208: Lit(_, ty) then Ok(ty) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.209: Var(name) and find(ctx, name) is +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.210: Some(ty) then Ok(ty) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.211: None then Err(concat3("unbound variable `", name, "`")) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.212: Abs(Var(name), ty, body) and typeTerm(body, insert(ctx, name, ty)) is +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.213: Ok(resTy) then Ok(FunctionType(ty, resTy)) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.214: Err(message) then Err(message) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.215: App(lhs, rhs) and typeTerm(lhs, ctx) is +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.216: Ok(FunctionType(pTy, resTy)) and typeTerm(rhs, ctx) is +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.217: Ok(aTy) and +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ║ l.218: typeEqual(pTy, aTy) then Ok(resTy) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.219: _ then Err(concat5("expect the argument to be of type `", showType(pTy), "` but found `", showType(aTy), "`")) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.220: Err(message) then Err(message) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.221: Ok(PrimitiveType(name)) then Err(concat3("cannot apply primitive type `", name, "`")) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.222: Err(message) then Err(message) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── Note: use flag `:ex` to see internal error info. +//│ ╔══[ERROR] Cyclic-looking constraint while typing binding of lambda expression; a type annotation may be required +//│ ║ l.206: fun typeTerm(t, ctx) = +//│ ║ ^^^^^^^^^^ +//│ ║ l.207: if t is +//│ ║ ^^^^^^^^^ +//│ ║ l.208: Lit(_, ty) then Ok(ty) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.209: Var(name) and find(ctx, name) is +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.210: Some(ty) then Ok(ty) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.211: None then Err(concat3("unbound variable `", name, "`")) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.212: Abs(Var(name), ty, body) and typeTerm(body, insert(ctx, name, ty)) is +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.213: Ok(resTy) then Ok(FunctionType(ty, resTy)) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.214: Err(message) then Err(message) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.215: App(lhs, rhs) and typeTerm(lhs, ctx) is +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.216: Ok(FunctionType(pTy, resTy)) and typeTerm(rhs, ctx) is +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.217: Ok(aTy) and +//│ ║ ^^^^^^^^^^^^^^^^^^^ +//│ ║ l.218: typeEqual(pTy, aTy) then Ok(resTy) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.219: _ then Err(concat5("expect the argument to be of type `", showType(pTy), "` but found `", showType(aTy), "`")) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.220: Err(message) then Err(message) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.221: Ok(PrimitiveType(name)) then Err(concat3("cannot apply primitive type `", name, "`")) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.222: Err(message) then Err(message) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── Note: use flag `:ex` to see internal error info. +//│ typeTerm: ('rhs, 'right,) -> (Err & {message: 'message} | Ok & {value: 'ty}) +//│ where +//│ 'message :> string +//│ 'right <: 'right0 & (Empty | Node & {key: string, left: 'right, right: 'right}) +//│ 'right0 <: Empty | Node & {key: string, left: 'right0, right: 'right0, value: 'ty & 'lhs & (PrimitiveType & {name: string} | 'a & (FunctionType & 'b | FunctionType & ~#FunctionType))} +//│ 'rhs <: Abs & {lhs: Var & {name: string}, lty: 'rhs0 & 'lhs & 'lty & (PrimitiveType & {name: string} | 'a & (FunctionType & 'b | FunctionType & ~#FunctionType)), rhs: 'rhs} | App & {lhs: 'rhs, rhs: 'rhs} | Lit & {ty: 'ty & 'lhs & (PrimitiveType & {name: string} | 'a & (FunctionType & 'b | FunctionType & ~#FunctionType))} | Var & {name: string} +//│ 'a <: {lhs: 'rhs0 & 'lhs, rhs: 'rhs1} +//│ 'rhs1 :> 'ty +//│ <: 'lhs & (PrimitiveType & {name: string} | 'a & (FunctionType & 'b | FunctionType & ~#FunctionType)) +//│ 'b <: {lhs: 'rhs2, rhs: 'rhs2} +//│ 'rhs2 <: FunctionType & 'b | PrimitiveType | ~FunctionType & ~PrimitiveType +//│ 'ty :> 'lty | FunctionType & {lhs: 'lty, rhs: 'ty} | 'rhs1 +//│ 'lhs <: FunctionType & {lhs: 'lhs & (PrimitiveType & {name: string} | ~#PrimitiveType), rhs: 'lhs} | PrimitiveType & {name: string} +//│ 'rhs0 <: FunctionType & {lhs: 'rhs0, rhs: 'rhs0} | PrimitiveType | ~FunctionType & ~PrimitiveType +//│ = [Function: typeTerm] + +fun showTypeTerm(t, ctx) = + if typeTerm(t, ctx) is + Ok(ty) then concat3(showTerm(t), " : ", showType(ty)) + Err(message) then concat2("Type error: ", message) +//│ showTypeTerm: ('rhs & 'rhs0, 'right & (Empty | Node & 'a),) -> string +//│ where +//│ 'a <: {key: string, left: 'right0, right: 'right0} +//│ 'right0 <: 'right1 & (Empty | Node & 'a) +//│ 'right1 <: Empty | Node & {key: string, left: 'right1, right: 'right1, value: 'rhs1} +//│ 'rhs1 <: 'lhs & (FunctionType & {lhs: 'rhs2 & 'lhs, rhs: 'rhs1} & ~#FunctionType | FunctionType & {lhs: 'rhs2 & 'lhs, rhs: 'rhs1} & 'b | PrimitiveType & {name: string}) +//│ 'b <: {lhs: 'rhs3, rhs: 'rhs3} +//│ 'rhs3 <: FunctionType & 'b | PrimitiveType | ~FunctionType & ~PrimitiveType +//│ 'right <: Empty | Node & {key: string, left: 'right, right: 'right, value: 'rhs4} +//│ 'rhs4 <: 'lhs & (FunctionType & {lhs: 'rhs2 & 'lhs, rhs: 'rhs4} & ~#FunctionType | FunctionType & {lhs: 'rhs2 & 'lhs, rhs: 'rhs4} & 'c | PrimitiveType & {name: string}) +//│ 'c <: {lhs: 'rhs5, rhs: 'rhs5} +//│ 'rhs5 <: FunctionType & 'c | PrimitiveType | ~FunctionType & ~PrimitiveType +//│ 'rhs0 <: Abs & {lhs: 'rhs0, lty: 'lhs, rhs: 'rhs0} | App & {lhs: 'rhs0 & (Abs & {lhs: 'rhs0, lty: 'lhs} | ~#Abs), rhs: 'rhs0} | Lit | Var +//│ 'rhs <: Abs & {lhs: Var & {name: string}, lty: 'lhs & 'rhs2 & (PrimitiveType & {name: string} | 'd & (FunctionType & 'e | FunctionType & ~#FunctionType)), rhs: 'rhs} | App & {lhs: 'rhs, rhs: 'rhs} | Lit & {ty: 'rhs6} | Var & {name: string} +//│ 'd <: {lhs: 'rhs2 & 'lhs, rhs: 'rhs6} +//│ 'rhs6 <: 'lhs & (PrimitiveType & {name: string} | 'd & (FunctionType & 'e | FunctionType & ~#FunctionType)) +//│ 'e <: {lhs: 'rhs7, rhs: 'rhs7} +//│ 'rhs7 <: FunctionType & 'e | PrimitiveType | ~FunctionType & ~PrimitiveType +//│ 'rhs2 <: FunctionType & {lhs: 'rhs2, rhs: 'rhs2} | PrimitiveType | ~FunctionType & ~PrimitiveType +//│ 'lhs <: FunctionType & {lhs: 'lhs & (PrimitiveType & {name: string} | ~#PrimitiveType), rhs: 'lhs} | PrimitiveType & {name: string} +//│ = [Function: showTypeTerm] + +// FIXME +showTypeTerm(Var("x"), empty) +showTypeTerm(Abs(Var("x"), _t("int"), Var("x")), empty) +showTypeTerm(App(Var("f"), Lit("0", _t("int"))), insert(empty, "f", _f(_t("int"), _t("int")))) +showTypeTerm(App(Var("f"), Lit("0.2", _t("float"))), insert(empty, "f", _f(_t("int"), _t("int")))) +showTypeTerm(App(Var("f"), Lit("0", _t("int"))), insert(empty, "f", _t("string"))) +//│ res: string +//│ = 'Type error: unbound variable `x`' +//│ res: string +//│ = '&x: int => x : int -> int' +//│ res: string +//│ = '(f 0) : int' +//│ res: string +//│ = 'Type error: expect the argument to be of type `int` but found `float`' +//│ res: string +//│ = 'Type error: cannot apply primitive type `string`' diff --git a/shared/src/test/diff/tapl/Untyped.mls b/shared/src/test/diff/tapl/Untyped.mls new file mode 100644 index 000000000..4dbba3420 --- /dev/null +++ b/shared/src/test/diff/tapl/Untyped.mls @@ -0,0 +1,502 @@ +:NewParser + +fun concat2(a, b) = concat(a)(b) +fun concat3(a, b, c) = concat2(a, concat2(b, c)) +fun concat4(a, b, c, d) = concat2(a, concat3(b, c, d)) +fun concat5(a, b, c, d, e) = concat2(a, concat4(b, c, d, e)) +fun concat6(a, b, c, d, e, f) = concat2(a, concat5(b, c, d, e, f)) +fun concat7(a, b, c, d, e, f, g) = concat2(a, concat6(b, c, d, e, f, g)) +fun concat8(a, b, c, d, e, f, g, h) = concat2(a, concat7(b, c, d, e, f, g, h)) +fun par(a) = concat3("(", a, ")") +//│ concat2: (string, string,) -> string +//│ = [Function: concat2] +//│ concat3: (string, string, string,) -> string +//│ = [Function: concat3] +//│ concat4: (string, string, string, string,) -> string +//│ = [Function: concat4] +//│ concat5: (string, string, string, string, string,) -> string +//│ = [Function: concat5] +//│ concat6: (string, string, string, string, string, string,) -> string +//│ = [Function: concat6] +//│ concat7: (string, string, string, string, string, string, string,) -> string +//│ = [Function: concat7] +//│ concat8: (string, string, string, string, string, string, string, string,) -> string +//│ = [Function: concat8] +//│ par: string -> string +//│ = [Function: par] + +:escape +let String: nothing +let makeString: anything => { length: int, charCodeAt: int => int } = String +let StringInstance: { fromCharCode: int => string } = String +//│ String: nothing +//│ = +//│ makeString: anything -> {charCodeAt: int -> int, length: int} +//│ = [Function: String] +//│ StringInstance: {fromCharCode: int -> string} +//│ = [Function: String] + +fun fromCharCode(n) = StringInstance.fromCharCode(n) +fun stringCharCodeAt(s, i) = makeString(s).charCodeAt(i) +fun stringLength(s) = makeString(s).length +//│ fromCharCode: int -> string +//│ = [Function: fromCharCode] +//│ stringCharCodeAt: (anything, int,) -> int +//│ = [Function: stringCharCodeAt] +//│ stringLength: anything -> int +//│ = [Function: stringLength] + +class Option +class Some(value): Option +class None(): Option +//│ Defined class Option +//│ Defined class Some +//│ Defined class None +//│ Option: () -> Option +//│ = [Function: Option1] +//│ Some: 'value -> (Some & {value: 'value}) +//│ = [Function: Some1] +//│ None: () -> None +//│ = [Function: None1] + +class List +class Cons(head, tail): List +class Nil(): List +//│ Defined class List +//│ Defined class Cons +//│ Defined class Nil +//│ List: () -> List +//│ = [Function: List1] +//│ Cons: ('head, 'tail,) -> (Cons & {head: 'head, tail: 'tail}) +//│ = [Function: Cons1] +//│ Nil: () -> Nil +//│ = [Function: Nil1] + +fun list1(x) = Cons(x, Nil()) +fun list2(x, y) = Cons(x, list1(y)) +fun list3(x, y, z) = Cons(x, list2(y, z)) +fun list4(x, y, z, w) = Cons(x, list3(y, z, w)) +fun list5(x, y, z, w, v) = Cons(x, list4(y, z, w, v)) +fun list6(x, y, z, w, v, u) = Cons(x, list5(y, z, w, v, u)) +fun list7(x, y, z, w, v, u, t) = Cons(x, list6(y, z, w, v, u, t)) +fun list8(x, y, z, w, v, u, t, s) = Cons(x, list7(y, z, w, v, u, t, s)) +//│ list1: 'head -> (Cons & {head: 'head, tail: Nil}) +//│ = [Function: list1] +//│ list2: ('head, 'head0,) -> (Cons & {head: 'head, tail: Cons & {head: 'head0, tail: Nil}}) +//│ = [Function: list2] +//│ list3: ('head, 'head0, 'head1,) -> (Cons & {head: 'head, tail: Cons & {head: 'head0, tail: Cons & {head: 'head1, tail: Nil}}}) +//│ = [Function: list3] +//│ list4: ('head, 'head0, 'head1, 'head2,) -> (Cons & {head: 'head, tail: Cons & {head: 'head0, tail: Cons & {head: 'head1, tail: Cons & {head: 'head2, tail: Nil}}}}) +//│ = [Function: list4] +//│ list5: ('head, 'head0, 'head1, 'head2, 'head3,) -> (Cons & {head: 'head, tail: Cons & {head: 'head0, tail: Cons & {head: 'head1, tail: Cons & {head: 'head2, tail: Cons & {head: 'head3, tail: Nil}}}}}) +//│ = [Function: list5] +//│ list6: ('head, 'head0, 'head1, 'head2, 'head3, 'head4,) -> (Cons & {head: 'head, tail: Cons & {head: 'head0, tail: Cons & {head: 'head1, tail: Cons & {head: 'head2, tail: Cons & {head: 'head3, tail: Cons & {head: 'head4, tail: Nil}}}}}}) +//│ = [Function: list6] +//│ list7: ('head, 'head0, 'head1, 'head2, 'head3, 'head4, 'head5,) -> (Cons & {head: 'head, tail: Cons & {head: 'head0, tail: Cons & {head: 'head1, tail: Cons & {head: 'head2, tail: Cons & {head: 'head3, tail: Cons & {head: 'head4, tail: Cons & {head: 'head5, tail: Nil}}}}}}}) +//│ = [Function: list7] +//│ list8: ('head, 'head0, 'head1, 'head2, 'head3, 'head4, 'head5, 'head6,) -> (Cons & {head: 'head, tail: Cons & {head: 'head0, tail: Cons & {head: 'head1, tail: Cons & {head: 'head2, tail: Cons & {head: 'head3, tail: Cons & {head: 'head4, tail: Cons & {head: 'head5, tail: Cons & {head: 'head6, tail: Nil}}}}}}}}) +//│ = [Function: list8] + +fun listConcat(xs, ys) = + if xs is + Nil() then ys + Cons(x, xs') then Cons(x, listConcat(xs', ys)) +//│ listConcat: ('tail, 'tail0,) -> 'tail0 +//│ where +//│ 'tail0 :> Cons & {head: 'head, tail: 'tail0} +//│ 'tail <: Cons & {head: 'head, tail: 'tail} | Nil +//│ = [Function: listConcat] + +fun listContains(xs, x) = + if xs is + Nil() then false + Cons(x', xs') and + eq(x)(x') then true + _ then listContains(xs', x) +//│ listContains: ('tail, anything,) -> bool +//│ where +//│ 'tail <: Cons & {tail: 'tail} | Nil +//│ = [Function: listContains] + +// Remove all occurrences of x from xs. +fun listWithout(xs, x) = + if xs is + Nil() then Nil() + Cons(x', xs') and + eq(x)(x') then listWithout(xs', x) + _ then Cons(x', listWithout(xs', x)) +//│ listWithout: ('tail, anything,) -> 'tail0 +//│ where +//│ 'tail0 :> Nil | Cons & {head: 'head, tail: 'tail0} +//│ 'tail <: Cons & {head: 'head, tail: 'tail} | Nil +//│ = [Function: listWithout] + +fun listJoin(xs, sep) = + if xs is + Nil() then "" + Cons(x, Nil()) then toString(x) + Cons(x, xs') then concat3(toString(x), sep, listJoin(xs', sep)) +//│ listJoin: ('tail, string,) -> string +//│ where +//│ 'tail <: Cons & {tail: 'tail} | Nil +//│ = [Function: listJoin] + +listJoin(list3("x", "y", "z"), ", ") +//│ res: string +//│ = 'x, y, z' + +class Term +class Var(name): Term +class Abs(lhs, rhs): Term +class App(lhs, rhs): Term +//│ Defined class Term +//│ Defined class Var +//│ Defined class Abs +//│ Defined class App +//│ Term: () -> Term +//│ = [Function: Term1] +//│ Var: 'name -> (Var & {name: 'name}) +//│ = [Function: Var1] +//│ Abs: ('lhs, 'rhs,) -> (Abs & {lhs: 'lhs, rhs: 'rhs}) +//│ = [Function: Abs1] +//│ App: ('lhs, 'rhs,) -> (App & {lhs: 'lhs, rhs: 'rhs}) +//│ = [Function: App1] + +fun showTerm(t) = + if t is + Var(name) then toString(name) + Abs(lhs, rhs) then concat4("&", showTerm(lhs), ". ", showTerm(rhs)) + App(Abs(lhs0, lhs1), rhs) then + concat8("((", "&", showTerm(lhs0), ". ", showTerm(lhs1), ") ", showTerm(rhs), ")") + App(lhs, rhs) then par(concat3(showTerm(lhs), " ", showTerm(rhs))) +//│ showTerm: 'rhs -> string +//│ where +//│ 'rhs <: Abs & {lhs: 'rhs, rhs: 'rhs} | App & {lhs: 'rhs & (Abs & {lhs: 'rhs, rhs: 'rhs} | ~#Abs), rhs: 'rhs} | Var +//│ = [Function: showTerm] + +showTerm(Var("x")) +showTerm(Abs(Var("x"), Var("y"))) +showTerm(App(Var("x"), Var("y"))) +showTerm(App(Abs(Var("x"), Var("y")), Var("z"))) +//│ res: string +//│ = 'x' +//│ res: string +//│ = '&x. y' +//│ res: string +//│ = '(x y)' +//│ res: string +//│ = '((&x. y) z)' + +fun isValue(t) = + if t is + Var then true + Abs then true + App then false +//│ isValue: (Abs | App | Var) -> bool +//│ = [Function: isValue] + +isValue(Var("x")) +isValue(Abs(Var("x"), Var("y"))) +isValue(App(Var("x"), Var("y"))) +//│ res: bool +//│ = true +//│ res: bool +//│ = true +//│ res: bool +//│ = false + +fun hasFree(t, n) = + if t is + // let __ = debug(concat3(showTerm(t), ", ", n)) + Var(na) then eq(n)(na) + Abs(Var(name), body) and eq(name)(n) then false + Abs(Var(name), body) then hasFree(body, n) + App(lhs, rhs) then hasFree(lhs, n) || hasFree(rhs, n) + _ then false +//│ hasFree: ('rhs, anything,) -> bool +//│ where +//│ 'rhs <: Abs & {rhs: 'rhs} | App & {lhs: 'rhs, rhs: 'rhs} | Var | ~Abs & ~App & ~Var +//│ = [Function: hasFree] + +fun showHasFree(t, n) = + concat4(showTerm(t), if hasFree(t, n) then " has " else " DOES NOT have ", "free variable ", n) +//│ showHasFree: ('rhs & 'rhs0, string,) -> string +//│ where +//│ 'rhs0 <: Abs & {rhs: 'rhs0} | App & {lhs: 'rhs0, rhs: 'rhs0} | Var | ~Abs & ~App & ~Var +//│ 'rhs <: Abs & {lhs: 'rhs, rhs: 'rhs} | App & {lhs: 'rhs & (Abs & {lhs: 'rhs, rhs: 'rhs} | ~#Abs), rhs: 'rhs} | Var +//│ = [Function: showHasFree] + +showHasFree(Var("x"), "x") +showHasFree(Var("x"), "y") +showHasFree(Abs(Var("x"), Var("x")), "x") +showHasFree(Abs(Var("x"), Var("x")), "y") +showHasFree(Abs(Var("x"), Var("y")), "x") +showHasFree(Abs(Var("x"), Var("y")), "y") +showHasFree(App(Var("x"), Var("y")), "x") +showHasFree(App(Var("x"), Var("y")), "y") +showHasFree(App(Abs(Var("x"), Var("x")), Var("x")), "x") +showHasFree(App(Abs(Var("x"), Var("x")), Var("x")), "y") +showHasFree(App(Abs(Var("x"), Var("x")), Var("y")), "y") +showHasFree(App(Abs(Var("x"), Var("x")), Var("x")), "y") +//│ res: string +//│ = 'x has free variable x' +//│ res: string +//│ = 'x DOES NOT have free variable y' +//│ res: string +//│ = '&x. x DOES NOT have free variable x' +//│ res: string +//│ = '&x. x DOES NOT have free variable y' +//│ res: string +//│ = '&x. y DOES NOT have free variable x' +//│ res: string +//│ = '&x. y has free variable y' +//│ res: string +//│ = '(x y) has free variable x' +//│ res: string +//│ = '(x y) has free variable y' +//│ res: string +//│ = '((&x. x) x) has free variable x' +//│ res: string +//│ = '((&x. x) x) DOES NOT have free variable y' +//│ res: string +//│ = '((&x. x) y) has free variable y' +//│ res: string +//│ = '((&x. x) x) DOES NOT have free variable y' + +fun fv(t) = + if t is + Var(name) then list1(name) + Abs(Var(name), body) then listWithout(fv(body), name) + App(lhs, rhs) then listConcat(fv(lhs), fv(rhs)) +//│ fv: 'rhs -> 'a +//│ where +//│ 'a :> forall 'tail 'tail0. Cons & {head: 'name, tail: Nil} | 'tail | 'tail0 +//│ 'tail0 :> Cons & {head: 'head, tail: 'tail0} | 'a +//│ 'tail :> Nil | Cons & {head: 'head, tail: 'tail} +//│ 'head :> 'name +//│ 'rhs <: Abs & {lhs: Var, rhs: 'rhs} | App & {lhs: 'rhs, rhs: 'rhs} | Var & {name: 'name} +//│ = [Function: fv] + +fun showFv(t) = + concat2(showTerm(t), if fv(t) is + Nil then " DOES NOT have free variables" + _ then concat2(" has free variables: ", listJoin(fv(t), ", ")) + ) +//│ showFv: ('rhs & 'rhs0) -> string +//│ where +//│ 'rhs0 <: Abs & {lhs: Var, rhs: 'rhs0} | App & {lhs: 'rhs0, rhs: 'rhs0} | Var +//│ 'rhs <: Abs & {lhs: 'rhs, rhs: 'rhs} | App & {lhs: 'rhs & (Abs & {lhs: 'rhs, rhs: 'rhs} | ~#Abs), rhs: 'rhs} | Var +//│ = [Function: showFv] + +showFv(Var("x")) +showFv(Abs(Var("x"), Var("x"))) +showFv(Abs(Var("x"), Var("y"))) +showFv(App(Var("x"), Var("y"))) +showFv(App(Abs(Var("x"), Var("x")), Var("x"))) +//│ res: string +//│ = 'x has free variables: x' +//│ res: string +//│ = '&x. x DOES NOT have free variables' +//│ res: string +//│ = '&x. y has free variables: y' +//│ res: string +//│ = '(x y) has free variables: x, y' +//│ res: string +//│ = '((&x. x) x) has free variables: x' + +fun tryNextAlphabet(initialCode, currentCode, freeNames) = + if + currentCode + > 122 then tryNextAlphabet(initialCode, 97, freeNames) + == initialCode then None() + let name = fromCharCode(currentCode) + listContains(freeNames, name) then tryNextAlphabet(initialCode, currentCode + 1, freeNames) + _ then Some(name) +//│ tryNextAlphabet: (number, int, 'tail,) -> (None | Some & {value: string}) +//│ where +//│ 'tail <: Cons & {tail: 'tail} | Nil +//│ = [Function: tryNextAlphabet] + +tryNextAlphabet(97, 97, list1("a")) +tryNextAlphabet(97, 98, list1("a")) +tryNextAlphabet(97, 98, list2("a", "b")) +tryNextAlphabet(121, 122, list1("y")) +tryNextAlphabet(121, 122, list2("y", "z")) +//│ res: None | Some & {value: string} +//│ = None {} +//│ res: None | Some & {value: string} +//│ = Some { value: 'b' } +//│ res: None | Some & {value: string} +//│ = Some { value: 'c' } +//│ res: None | Some & {value: string} +//│ = Some { value: 'z' } +//│ res: None | Some & {value: string} +//│ = Some { value: 'a' } + +fun tryAppendDigits(name, index, freeNames) = + if + let currentName = concat2(name, toString(index)) + listContains(freeNames, currentName) then + tryAppendDigits(name, index + 1, freeNames) + _ then currentName +//│ tryAppendDigits: (string, int, 'tail,) -> string +//│ where +//│ 'tail <: Cons & {tail: 'tail} | Nil +//│ = [Function: tryAppendDigits] + +// Note: some weird behavior here... Just try the commented code. +fun findFreshName(name, freeNames) = + if + stringLength(name) == 1 and + let charCode = stringCharCodeAt(name, 0) + tryNextAlphabet(charCode, charCode + 1, freeNames) is + Some(newName) then newName + _ then tryAppendDigits(name, 0, freeNames) +//│ findFreshName: (string, 'tail,) -> string +//│ where +//│ 'tail <: Cons & {tail: 'tail} | Nil +//│ = [Function: findFreshName] + +// Find a fresh name to replace `name` that does not conflict with any bound +// variables in the `body`. +fun freshName(name, body) = findFreshName(name, fv(body)) +//│ freshName: (string, 'rhs,) -> string +//│ where +//│ 'rhs <: Abs & {lhs: Var, rhs: 'rhs} | App & {lhs: 'rhs, rhs: 'rhs} | Var +//│ = [Function: freshName] + +fun subst(t, n, v) = + if t is + Var(name) and eq(name)(n) then v + Abs(Var(name), body) and ne(name)(n) and + hasFree(v, name) and freshName(name, body) is newName then + subst(Abs(Var(newName), subst(body, name, Var(newName))), n, v) + _ then Abs(Var(name), subst(body, n, v)) + App(lhs, rhs) then App(subst(lhs, n, v), subst(rhs, n, v)) + _ then t +//│ subst: ('rhs, anything, 'rhs & 'rhs0 & 'rhs1 & 'rhs2,) -> 'rhs0 +//│ where +//│ 'rhs2 <: Abs & {rhs: 'rhs2} | App & {lhs: 'rhs2, rhs: 'rhs2} | Var | ~Abs & ~App & ~Var +//│ 'rhs <: Abs & 'a | App & 'b | Var & 'c | 'd & ~#Abs & ~#App & ~#Var +//│ 'a :> Abs & {lhs: Var & {name: string}, rhs: 'rhs0} | 'e +//│ <: 'rhs1 & (Abs & {lhs: Var & {name: 'name} | ~Var, rhs: 'rhs & 'rhs1} | App & {lhs: Var & {name: 'name} | ~Var, rhs: 'rhs & 'rhs1} & 'b | Var & {lhs: Var & {name: 'name} | ~Var, rhs: 'rhs & 'rhs1} & 'c | {lhs: Var & {name: 'name} | ~Var, rhs: 'rhs & 'rhs1} & 'd & ~#Abs & ~#App & ~#Var) +//│ 'rhs0 :> Var & {name: string} | 'c | 'a | 'd | (forall 'e. 'e) | App & {lhs: 'rhs0, rhs: 'rhs0} +//│ 'e :> Abs & {lhs: Var & {name: 'name}, rhs: 'rhs0} +//│ 'name := string +//│ 'c :> Var & {name: string} +//│ <: 'rhs1 & (Abs & {name: anything} & 'a | App & {name: anything} & 'b | Var | {name: anything} & 'd & ~#Abs & ~#App & ~#Var) +//│ 'd <: 'rhs1 & (Abs & 'a | App & 'b | Var & 'c | ~#Abs & ~#App & ~#Var) +//│ 'b <: {lhs: 'rhs, rhs: 'rhs} +//│ 'rhs1 <: Abs & {lhs: Var, rhs: 'rhs1} | App & {lhs: 'rhs1, rhs: 'rhs1} | Var +//│ = [Function: subst] + +fun showSubst(t, n, v) = + concat8(showTerm(t), " [", n, " / ", showTerm(v), "]", " => ", showTerm(subst(t, n, v))) +//│ showSubst: ('rhs & 'rhs0, string, 'rhs0 & 'rhs1 & 'lhs & 'rhs2 & 'rhs & 'rhs3,) -> string +//│ where +//│ 'rhs3 <: Abs & {rhs: 'rhs3} | App & {lhs: 'rhs3, rhs: 'rhs3} | Var | ~Abs & ~App & ~Var +//│ 'rhs0 <: Abs & 'a | App & 'b | Var & 'c | 'd & ~#Abs & ~#App & ~#Var +//│ 'a <: 'lhs & 'rhs2 & 'rhs1 & (Abs & {lhs: Var & {name: string} | ~Var, rhs: 'rhs0 & 'rhs1} | App & {lhs: Var & {name: string} | ~Var, rhs: 'rhs0 & 'rhs1} & 'b | Var & {lhs: Var & {name: string} | ~Var, rhs: 'rhs0 & 'rhs1} & 'c | {lhs: Var & {name: string} | ~Var, rhs: 'rhs0 & 'rhs1} & 'd & ~#Abs & ~#App & ~#Var) +//│ 'c <: 'lhs & 'rhs2 & 'rhs1 & (Abs & {name: anything} & 'a | App & {name: anything} & 'b | Var | {name: anything} & 'd & ~#Abs & ~#App & ~#Var) +//│ 'd <: 'lhs & 'rhs2 & 'rhs1 & (Abs & 'a | App & 'b | Var & 'c | ~#Abs & ~#App & ~#Var) +//│ 'b <: {lhs: 'rhs0, rhs: 'rhs0} +//│ 'rhs1 <: Abs & {lhs: Var, rhs: 'rhs1} | App & {lhs: 'rhs1, rhs: 'rhs1} | Var +//│ 'lhs <: Abs & {lhs: 'rhs2, rhs: 'rhs2} | ~Abs +//│ 'rhs2 <: Abs & {lhs: 'rhs2, rhs: 'rhs2} | App & {lhs: 'lhs & 'rhs2, rhs: 'rhs2} | Var +//│ 'rhs <: Abs & {lhs: 'rhs, rhs: 'rhs} | App & {lhs: 'rhs & (Abs & {lhs: 'rhs, rhs: 'rhs} | ~#Abs), rhs: 'rhs} | Var +//│ = [Function: showSubst] + +showSubst(Var("x"), "x", Var("y")) +showSubst(Abs(Var("x"), Var("x")), "x", Var("z")) +showSubst(App(Var("x"), Var("y")), "x", Abs(Var("x"), Var("x"))) +showSubst(App(Abs(Var("x"), Var("x")), Var("x")), "x", Abs(Var("y"), Var("y"))) +showSubst(Abs(Var("x"), App(Var("x"), Var("y"))), "y", Var("x")) +//│ res: string +//│ = 'x [x / y] => y' +//│ res: string +//│ = '&x. x [x / z] => &x. x' +//│ res: string +//│ = '(x y) [x / &x. x] => ((&x. x) y)' +//│ res: string +//│ = '((&x. x) x) [x / &y. y] => ((&x. x) &y. y)' +//│ res: string +//│ = '&x. (x y) [y / x] => &z. (z x)' + +fun stepByValue(t) = + if t is + Var then None() + Abs then None() + App(lhs, rhs) and stepByValue(lhs) is + Some(lhs) then Some(App(lhs, rhs)) + None and stepByValue(rhs) is + Some(rhs) then Some(App(lhs, rhs)) + None and lhs is + Abs(Var(name), body) then Some(subst(body, name, rhs)) + _ then None() +//│ stepByValue: 'a -> (None | Some & {value: 'value}) +//│ where +//│ 'value :> 'value0 | App & {lhs: 'value, rhs: 'rhs} | App & {lhs: 'lhs, rhs: 'value} +//│ 'a <: Abs | App & {lhs: 'lhs, rhs: 'rhs} | Var +//│ 'lhs <: 'a & (Abs & {rhs: 'rhs0} | ~#Abs) +//│ 'rhs0 <: Abs & 'b | App & 'c | Var & 'd | 'e & ~#Abs & ~#App & ~#Var +//│ 'b :> Abs & {lhs: Var & {name: string}, rhs: 'value0} | 'f +//│ <: 'rhs1 & (Abs & {lhs: Var & {name: 'name} | ~Var, rhs: 'rhs2} | App & {lhs: Var & {name: 'name} | ~Var, rhs: 'rhs2} & 'c | Var & {lhs: Var & {name: 'name} | ~Var, rhs: 'rhs2} & 'd | {lhs: Var & {name: 'name} | ~Var, rhs: 'rhs2} & 'e & ~#Abs & ~#App & ~#Var) +//│ 'value0 :> Var & {name: string} | 'rhs | 'd | 'b | 'e | (forall 'f. 'f) | App & {lhs: 'value0, rhs: 'value0} +//│ 'f :> Abs & {lhs: Var & {name: 'name}, rhs: 'value0} +//│ 'name := string +//│ 'd :> Var & {name: string} +//│ <: 'rhs1 & (Abs & {name: anything} & 'b | App & {name: anything} & 'c | Var | {name: anything} & 'e & ~#Abs & ~#App & ~#Var) +//│ 'e <: 'rhs1 & (Abs & 'b | App & 'c | Var & 'd | ~#Abs & ~#App & ~#Var) +//│ 'c <: {lhs: 'rhs0, rhs: 'rhs0} +//│ 'rhs <: 'a & 'rhs2 & 'rhs3 +//│ 'rhs3 <: Abs & {rhs: 'rhs3} | App & {lhs: 'rhs3, rhs: 'rhs3} | Var | ~Abs & ~App & ~Var +//│ 'rhs2 <: 'rhs0 & 'rhs1 +//│ 'rhs1 <: Abs & {lhs: Var, rhs: 'rhs1} | App & {lhs: 'rhs1, rhs: 'rhs1} | Var +//│ = [Function: stepByValue] + +fun showStepByValue(t) = + concat3(showTerm(t), " => ", if stepByValue(t) is + Some(t) then showTerm(t) + None then "stuck" + ) +//│ showStepByValue: ('rhs & (Abs | App & 'a | Var)) -> string +//│ where +//│ 'a <: {lhs: 'lhs & 'rhs0 & (Abs & {rhs: 'rhs1} | Abs & ~#Abs | App & 'a | Var), rhs: 'rhs2 & 'lhs & 'rhs0 & 'rhs3 & (Abs | App & 'a | Var)} +//│ 'rhs3 <: Abs & {rhs: 'rhs3} | App & {lhs: 'rhs3, rhs: 'rhs3} | Var | ~Abs & ~App & ~Var +//│ 'rhs1 <: Abs & 'b | App & 'c | Var & 'd | 'e & ~#Abs & ~#App & ~#Var +//│ 'b <: 'rhs4 & 'lhs & 'rhs0 & (Abs & {lhs: Var & {name: string} | ~Var, rhs: 'rhs2} | App & {lhs: Var & {name: string} | ~Var, rhs: 'rhs2} & 'c | Var & {lhs: Var & {name: string} | ~Var, rhs: 'rhs2} & 'd | {lhs: Var & {name: string} | ~Var, rhs: 'rhs2} & 'e & ~#Abs & ~#App & ~#Var) +//│ 'd <: 'rhs4 & 'lhs & 'rhs0 & (Abs & {name: anything} & 'b | App & {name: anything} & 'c | Var | {name: anything} & 'e & ~#Abs & ~#App & ~#Var) +//│ 'e <: 'rhs4 & 'lhs & 'rhs0 & (Abs & 'b | App & 'c | Var & 'd | ~#Abs & ~#App & ~#Var) +//│ 'c <: {lhs: 'rhs1, rhs: 'rhs1} +//│ 'rhs2 <: 'rhs1 & 'rhs4 +//│ 'rhs4 <: Abs & {lhs: Var, rhs: 'rhs4} | App & {lhs: 'rhs4, rhs: 'rhs4} | Var +//│ 'lhs <: Abs & {lhs: 'rhs0, rhs: 'rhs0} | ~Abs +//│ 'rhs0 <: Abs & {lhs: 'rhs0, rhs: 'rhs0} | App & {lhs: 'lhs & 'rhs0, rhs: 'rhs0} | Var +//│ 'rhs <: Abs & {lhs: 'rhs, rhs: 'rhs} | App & {lhs: 'rhs & (Abs & {lhs: 'rhs, rhs: 'rhs} | ~#Abs), rhs: 'rhs} | Var +//│ = [Function: showStepByValue] + +showStepByValue(Var("x")) +showStepByValue(Abs(Var("x"), Var("y"))) +showStepByValue(App(Var("x"), Var("y"))) +showStepByValue(App(Abs(Var("x"), Var("x")), Var("y"))) +//│ res: string +//│ = 'x => stuck' +//│ res: string +//│ = '&x. y => stuck' +//│ res: string +//│ = '(x y) => stuck' +//│ res: string +//│ = '((&x. x) y) => y' + +fun equalTerm(a, b) = + if a is + Var(na) and b is Var(nb) then eq(na)(nb) + Abs(la, ra) and b is Abs(lb, rb) then equalTerm(la, lb) && equalTerm(ra, rb) + App(la, ra) and b is App(lb, rb) then equalTerm(la, lb) && equalTerm(ra, rb) + _ then false +//│ equalTerm: ('rhs, 'rhs0,) -> bool +//│ where +//│ 'rhs0 <: Abs & 'a | App & 'a | Var | ~Abs & ~App & ~Var +//│ 'a <: {lhs: 'rhs0, rhs: 'rhs0} +//│ 'rhs <: Abs & {lhs: 'rhs, rhs: 'rhs} | App & {lhs: 'rhs, rhs: 'rhs} | Var | ~Abs & ~App & ~Var +//│ = [Function: equalTerm] diff --git a/shared/src/test/diff/ucs/DirectLines.mls b/shared/src/test/diff/ucs/DirectLines.mls index a9c25364b..ec7cad090 100644 --- a/shared/src/test/diff/ucs/DirectLines.mls +++ b/shared/src/test/diff/ucs/DirectLines.mls @@ -33,7 +33,6 @@ fun f(x, allowNone) = //│ f: (anything, anything,) -> ("bad" | "good" | "okay") //│ = [Function: f1] -:w fun f(x, y, z) = if x == 0 then "x" @@ -45,12 +44,6 @@ fun f(x, y, z) = _ then "bruh" 3 then "y = 3" _ then "bruh" -//│ ╔══[WARNING] Found a duplicated else branch -//│ ║ l.47: _ then "bruh" -//│ ║ ^^^^^^ -//│ ╟── The first else branch was declared here. -//│ ║ l.45: _ then "bruh" -//│ ╙── ^^^^^^ //│ f: (number, number, number,) -> ("bruh" | "x" | "y = 1" | "y = 3" | "z = 0" | "z = 9") //│ = [Function: f2] @@ -63,11 +56,8 @@ fun f(a, b) = 2 then 2 _ then 7 else 3 -//│ ╔══[WARNING] Found a duplicated else branch -//│ ║ l.65: else 3 -//│ ║ ^ -//│ ╟── The first else branch was declared here. -//│ ║ l.64: _ then 7 -//│ ╙── ^ +//│ ╔══[WARNING] Found a redundant else branch +//│ ║ l.58: else 3 +//│ ╙── ^ //│ f: (number, number,) -> (0 | 1 | 2 | 7) //│ = [Function: f3] diff --git a/shared/src/test/diff/ucs/ElseIf.mls b/shared/src/test/diff/ucs/ElseIf.mls index 2628d58c7..d960bc750 100644 --- a/shared/src/test/diff/ucs/ElseIf.mls +++ b/shared/src/test/diff/ucs/ElseIf.mls @@ -20,31 +20,31 @@ fun f(x, y) = if x == _ then false //│ fun f: (number, number,) -> bool - - -// TODO use real booleans module True module False //│ module True() //│ module False() - :e :ge fun f(x, y) = if x is True and y is True then true False and y is False then false //│ ╔══[ERROR] The match is not exhaustive. -//│ ║ l.35: True and y is True then true +//│ ║ l.31: True and y is True then true //│ ║ ^^^^^^^^^ //│ ╟── The scrutinee at this position misses 1 case. -//│ ║ l.35: True and y is True then true +//│ ║ l.31: True and y is True then true //│ ║ ^ -//│ ╙── [Missing Case 1/1] `False` +//│ ╟── [Missing Case 1/1] `False` +//│ ╟── It first appears here. +//│ ║ l.32: False and y is False then false +//│ ╙── ^^^^^ //│ fun f: (anything, anything,) -> error //│ Code generation encountered an error: //│ if expression was not desugared +// The base case. fun f(x, y) = if x is True and y is True then true False and y is False then false @@ -52,59 +52,58 @@ fun f(x, y) = if x is False and y is True then true //│ fun f: (False | True, False | True,) -> bool -// FIXME +// Replace the `x is False` with `_` fun f(x, y) = if x is True and y is True then true False and y is False then false _ and y is True then true False then false -//│ ╔══[ERROR] The match is not exhaustive. -//│ ║ l.57: True and y is True then true -//│ ║ ^^^^^^^^^ -//│ ╟── The scrutinee at this position misses 1 case. -//│ ║ l.57: True and y is True then true -//│ ║ ^ -//│ ╙── [Missing Case 1/1] `False` -//│ fun f: (anything, anything,) -> error -//│ Code generation encountered an error: -//│ if expression was not desugared - -// TODO support `else if` +//│ fun f: (anything, False | True,) -> bool + +f(True, True) +f(True, False) +f(False, True) +f(False, False) +//│ bool +//│ res +//│ = true +//│ res +//│ = false +//│ res +//│ = true +//│ res +//│ = false + +// Test with real booleans +fun g(x, y) = if x is + true and y is true then true + false and y is false then false + _ and y is + true then true + false then false +//│ fun g: (anything, bool,) -> bool + +// Chained UCS terms fun f(x, y) = if x is True and y is True then true False and y is False then false else if y is True then true False then false -//│ ╔══[ERROR] The match is not exhaustive. -//│ ║ l.75: True and y is True then true -//│ ║ ^^^^^^^^^ -//│ ╟── The scrutinee at this position misses 1 case. -//│ ║ l.75: True and y is True then true -//│ ║ ^ -//│ ╙── [Missing Case 1/1] `False` -//│ fun f: (anything, anything,) -> error -//│ Code generation encountered an error: -//│ if expression was not desugared +//│ fun f: (anything, False | True,) -> bool -// TODO support `else if` fun f(x, y) = if x is True and y is True then true False and y is False then false else if y is True and x is False then true False and x is True then false -//│ ╔══[ERROR] The match is not exhaustive. -//│ ║ l.93: True and y is True then true -//│ ║ ^^^^^^^^^ -//│ ╟── The scrutinee at this position misses 1 case. -//│ ║ l.93: True and y is True then true -//│ ║ ^ -//│ ╙── [Missing Case 1/1] `False` -//│ fun f: (anything, anything,) -> error -//│ Code generation encountered an error: -//│ if expression was not desugared - - +//│ fun f: (False | True, False | True,) -> bool +fun h(x, y, p) = if + x and p(x) then 0 + y is + True then 1 + False then 2 +//│ fun h: (anything, False | True, true -> anything,) -> (0 | 1 | 2) diff --git a/shared/src/test/diff/ucs/Exhaustiveness.mls b/shared/src/test/diff/ucs/Exhaustiveness.mls index c56f14230..cd476faa2 100644 --- a/shared/src/test/diff/ucs/Exhaustiveness.mls +++ b/shared/src/test/diff/ucs/Exhaustiveness.mls @@ -1,15 +1,12 @@ -:NewParser +:NewDefs :NoJS class A() class B() class C() -//│ Defined class A -//│ Defined class B -//│ Defined class C -//│ A: () -> A -//│ B: () -> B -//│ C: () -> C +//│ class A() +//│ class B() +//│ class C() :e fun f(x, y) = @@ -23,11 +20,47 @@ fun f(x, y) = x is A then 4 //│ ╔══[ERROR] The match is not exhaustive. -//│ ║ l.23: x is +//│ ║ l.20: x is //│ ║ ^^^^ //│ ╟── The scrutinee at this position misses 2 cases. -//│ ║ l.23: x is +//│ ║ l.20: x is //│ ║ ^ //│ ╟── [Missing Case 1/2] `B` -//│ ╙── [Missing Case 2/2] `C` -//│ f: (anything, anything,) -> error +//│ ╟── It first appears here. +//│ ║ l.17: B then 1 +//│ ║ ^ +//│ ╟── [Missing Case 2/2] `C` +//│ ╟── It first appears here. +//│ ║ l.18: C then 2 +//│ ╙── ^ +//│ fun f: (anything, anything,) -> error + +:e +// These operators are uninterpreted. So, it's impossible to reason the +// exhaustiveness without SMT solvers. +type Tree[A] = Node[A] | Empty +module Empty { + fun contains(wanted) = false +} +class Node[A](value: int, left: Tree[A], right: Tree[A]) { + fun contains(wanted) = if wanted + <= value then left.find(wanted) + >= value then right.find(wanted) + == value then true +} +//│ ╔══[ERROR] The case when this is false is not handled: == (wanted,) (value,) +//│ ║ l.46: fun contains(wanted) = if wanted +//│ ║ ^^^^^^ +//│ ║ l.47: <= value then left.find(wanted) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.48: >= value then right.find(wanted) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.49: == value then true +//│ ╙── ^^^^^^^^^^^^ +//│ type Tree[A] = Node[A] | Empty +//│ module Empty() { +//│ fun contains: anything -> false +//│ } +//│ class Node[A](value: int, left: Tree[A], right: Tree[A]) { +//│ fun contains: anything -> error +//│ } diff --git a/shared/src/test/diff/ucs/Humiliation.mls b/shared/src/test/diff/ucs/Humiliation.mls index bb55ab0a1..72c293c0b 100644 --- a/shared/src/test/diff/ucs/Humiliation.mls +++ b/shared/src/test/diff/ucs/Humiliation.mls @@ -11,7 +11,7 @@ if 1 is 1 then 1 else 0 //│ = 1 fun test(x) = if x is 1 then 0 else 1 -//│ test: number -> (0 | 1) +//│ test: anything -> (0 | 1) //│ = [Function: test] // It should report duplicated branches. @@ -19,8 +19,13 @@ fun test(x) = if x is 1 then 0 else 1 fun testF(x) = if x is Foo(a) then a Foo(a) then a -//│ ╔══[WARNING] duplicated branch -//│ ╙── +//│ ╔══[WARNING] Found a duplicated branch +//│ ╟── This branch +//│ ║ l.21: Foo(a) then a +//│ ║ ^ +//│ ╟── is subsumed by the branch here. +//│ ║ l.20: Foo(a) then a +//│ ╙── ^ //│ testF: (Foo & {x: 'x}) -> 'x //│ = [Function: testF] @@ -47,7 +52,7 @@ fun f(x) = Pair(1, 1) then "ones" Pair(y, 1) then x _ then "nah" -//│ f: (Pair & {fst: number, snd: number} & 'a | ~Pair) -> ("nah" | "ones" | "zeros" | 'a) +//│ f: (Pair & 'a | ~Pair) -> ("nah" | "ones" | "zeros" | 'a) //│ = [Function: f] class Z() @@ -66,14 +71,14 @@ fun foo(x) = if x is Pair(Z(), Z()) then "zeros" Pair(O(), O()) then "ones" //│ ╔══[ERROR] The match is not exhaustive. -//│ ║ l.65: fun foo(x) = if x is +//│ ║ l.70: fun foo(x) = if x is //│ ║ ^^^^ //│ ╟── The scrutinee at this position misses 1 case. -//│ ║ l.66: Pair(Z(), Z()) then "zeros" +//│ ║ l.71: Pair(Z(), Z()) then "zeros" //│ ║ ^^^ //│ ╟── [Missing Case 1/1] `O` //│ ╟── It first appears here. -//│ ║ l.67: Pair(O(), O()) then "ones" +//│ ║ l.72: Pair(O(), O()) then "ones" //│ ╙── ^^^ //│ foo: anything -> error //│ Code generation encountered an error: @@ -86,14 +91,14 @@ fun foo(x) = if x is (Z(), Z()) then "zeros" (O(), O()) then "ones" //│ ╔══[ERROR] The match is not exhaustive. -//│ ║ l.85: fun foo(x) = if x is +//│ ║ l.90: fun foo(x) = if x is //│ ║ ^^^^ //│ ╟── The scrutinee at this position misses 1 case. -//│ ║ l.86: (Z(), Z()) then "zeros" +//│ ║ l.91: (Z(), Z()) then "zeros" //│ ║ ^^^ //│ ╟── [Missing Case 1/1] `O` //│ ╟── It first appears here. -//│ ║ l.87: (O(), O()) then "ones" +//│ ║ l.92: (O(), O()) then "ones" //│ ╙── ^^^ //│ foo: anything -> error //│ Code generation encountered an error: @@ -146,11 +151,9 @@ fun foo(x) = if x is //│ foo: (Pair & {fst: O | Z, snd: S & {pred: 'pred} | ~S}) -> ("???" | "zeros" | 'pred) //│ = [Function: foo5] -:re foo(Pair(Z(), Z())) //│ res: "???" | "zeros" -//│ Runtime error: -//│ Error: non-exhaustive case expression +//│ = '???' :e :ge @@ -159,14 +162,14 @@ fun foo(x) = if x is Pair(O(), O()) then "ones" Pair(y, O()) then x //│ ╔══[ERROR] The match is not exhaustive. -//│ ║ l.157: fun foo(x) = if x is +//│ ║ l.160: fun foo(x) = if x is //│ ║ ^^^^ //│ ╟── The scrutinee at this position misses 1 case. -//│ ║ l.158: Pair(Z(), Z()) then "zeros" +//│ ║ l.161: Pair(Z(), Z()) then "zeros" //│ ║ ^^^ //│ ╟── [Missing Case 1/1] `Z` //│ ╟── It first appears here. -//│ ║ l.158: Pair(Z(), Z()) then "zeros" +//│ ║ l.161: Pair(Z(), Z()) then "zeros" //│ ╙── ^^^ //│ foo: anything -> error //│ Code generation encountered an error: @@ -176,18 +179,9 @@ fun foo(x, y) = if x is Z() and y is O() then 0 else 1 //│ foo: (anything, anything,) -> (0 | 1) //│ = [Function: foo7] -:pe -fun foo(x, y) = if x is - Z() and y is O() then 0 else 1 -//│ ╔══[PARSE ERROR] Unexpected 'else' keyword here -//│ ║ l.181: Z() and y is O() then 0 else 1 -//│ ╙── ^^^^ -//│ foo: (Z, O,) -> 0 -//│ = [Function: foo8] - fun foo(x, y) = if x is Z() and y is O() then 0 else 1 //│ foo: (anything, anything,) -> (0 | 1) -//│ = [Function: foo9] +//│ = [Function: foo8] diff --git a/shared/src/test/diff/ucs/Hygiene.mls b/shared/src/test/diff/ucs/Hygiene.mls new file mode 100644 index 000000000..9dbe128a3 --- /dev/null +++ b/shared/src/test/diff/ucs/Hygiene.mls @@ -0,0 +1,24 @@ +:NewDefs + +class Some[T](value: T) +class Left[T](value: T) +class Right[T](value: T) +//│ class Some[T](value: T) +//│ class Left[T](value: T) +//│ class Right[T](value: T) + +// FIXME unhygienic, the `x` in the second branch shadows parameter `x` +fun foo(x) = if x is + Some(Left(y)) then x + Some(x) then x +//│ fun foo: forall 'value. Some['value & (Left[anything] | ~#Left)] -> 'value + +foo(Some(Left(1))) +//│ Left[1] +//│ res +//│ = Left {} + +foo(Some(2)) +//│ 2 +//│ res +//│ = 2 diff --git a/shared/src/test/diff/ucs/HygienicBindings.mls b/shared/src/test/diff/ucs/HygienicBindings.mls new file mode 100644 index 000000000..2471ff86a --- /dev/null +++ b/shared/src/test/diff/ucs/HygienicBindings.mls @@ -0,0 +1,164 @@ +:NewDefs + +type Option[out T] = None | Some[T] +module None +class Some[out T](value: T) +//│ type Option[T] = Some[T] | None +//│ module None() +//│ class Some[T](value: T) + +type Either[A, B] = Left[A] | Right[B] +class Left[A](leftValue: A) +class Right[B](rightValue: B) +//│ type Either[A, B] = Left[A] | Right[B] +//│ class Left[A](leftValue: A) +//│ class Right[B](rightValue: B) + +type List[out A] = Nil | Cons[A] +module Nil +class Cons[out A](head: A, tail: List[A]) +//│ type List[A] = Cons[A] | Nil +//│ module Nil() +//│ class Cons[A](head: A, tail: List[A]) + +fun h0(a) = + if + a is Some(Left(y)) then y + a is Some(Right(z)) then z + a is None then 0 +//│ fun h0: forall 'leftValue. (None | Some[Left['leftValue] | Right['leftValue]]) -> (0 | 'leftValue) + +// FIXME: Precise scrutinee identification (easy) +// This seems fine. But the subtrees are not merged. +fun h1(a) = + if + a is Some(x) and x is Left(y) then y + a is Some(y) and y is Right(z) then z + a is None then 0 +//│ fun h1: forall 'leftValue. (None | Some[Right['leftValue]]) -> (0 | 'leftValue) + +// This is the desugared version of the test case above. +fun h1'(a) = + if a is + Some then + let x = a.value + let y = a.value + if x is + Left then + let y = x.leftValue + y + _ then + if y is + Right then + let z = y.rightValue + z + None then 0 +//│ fun h1': forall 'leftValue. (None | Some[Right['leftValue]]) -> (0 | 'leftValue) + +// FIXME This seems fine but the desugared term does not merge the cases. +// See the example below. +fun h1''(a) = + if + a is Some(x) and x is Left(y) then y + a is Some(x) and x is Right(z) then z + a is None then 0 +//│ fun h1'': forall 'leftValue. (None | Some[Left['leftValue] | Right['leftValue]]) -> (0 | 'leftValue) + +// FIXME +h1(Some(Left(0))) +h1'(Some(Left(0))) +h1''(Some(Left(0))) +//│ ╔══[ERROR] Type mismatch in application: +//│ ║ l.68: h1(Some(Left(0))) +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ╟── application of type `Left[?A]` is not an instance of type `Right` +//│ ║ l.68: h1(Some(Left(0))) +//│ ║ ^^^^^^^ +//│ ╟── Note: constraint arises from class pattern: +//│ ║ l.36: a is Some(y) and y is Right(z) then z +//│ ║ ^^^^^ +//│ ╟── from reference: +//│ ║ l.35: a is Some(x) and x is Left(y) then y +//│ ║ ^ +//│ ╟── Note: type parameter T is defined at: +//│ ║ l.5: class Some[out T](value: T) +//│ ╙── ^ +//│ ╔══[ERROR] Type mismatch in application: +//│ ║ l.69: h1'(Some(Left(0))) +//│ ║ ^^^^^^^^^^^^^^^^^^ +//│ ╟── application of type `Left[?A]` is not an instance of type `Right` +//│ ║ l.69: h1'(Some(Left(0))) +//│ ║ ^^^^^^^ +//│ ╟── Note: constraint arises from class pattern: +//│ ║ l.52: Right then +//│ ║ ^^^^^ +//│ ╟── from field selection: +//│ ║ l.45: let y = a.value +//│ ║ ^^^^^^^ +//│ ╟── Note: type parameter T is defined at: +//│ ║ l.5: class Some[out T](value: T) +//│ ╙── ^ +//│ 0 +//│ res +//│ = 0 +//│ res +//│ = 0 +//│ res +//│ = 0 + +// FIXME: Precise scrutinee identification (hard) +fun h2(a) = + if + a is Some(x) and x is x' and x' is Left(y) then y + a is Some(y) and + let y' = y + y' is Right(z) then z + a is None then 0 +//│ ╔══[ERROR] identifier not found: y +//│ ║ l.114: let y' = y +//│ ╙── ^ +//│ ╔══[ERROR] identifier not found: y +//│ ║ l.114: let y' = y +//│ ╙── ^ +//│ fun h2: forall 'leftValue. (None | Some[Left['leftValue] | ~Left[anything]]) -> (0 | error | 'leftValue) +//│ Code generation encountered an error: +//│ unresolved symbol y + +// FIXME: Some results are wrong. +fun h3(x, y, f, p) = + if x is + _ and f(x) is y and p(x) then y + None then y + _ then "anyway" +h3("anything", "not me", _ => "should be me", _ => true) +h3(None, "should be me", _ => "not me", _ => false) +h3("anything", "anything", _ => "not me", _ => false) +//│ fun h3: forall 'a 'b. (None | 'a & ~#None, 'b, (None | 'a) -> anything, (None | 'a) -> anything,) -> ("anyway" | 'b) +//│ "anything" | "anyway" +//│ res +//│ = [Function: h3] +//│ res +//│ = 'not me' +//│ res +//│ = 'should be me' + +// FIXME: Some results are wrong. +fun h4(x, y, p) = + if x is + y and p(x) then y + None then y + _ then "default" +h4("should be me", "not me", _ => true) // WRONG! +h4(None, "not me", _ => true) // WRONG! +h4(None, "should be me", _ => false) +h4("anything", "not me", _ => false) +//│ fun h4: forall 'a 'b. (None | 'a & ~#None, 'b, (None | 'a) -> anything,) -> ("default" | 'b) +//│ "default" | "not me" +//│ res +//│ = [Function: h4] +//│ res +//│ = 'not me' +//│ res +//│ = 'not me' +//│ res +//│ = 'should be me' diff --git a/shared/src/test/diff/ucs/InterleavedLet.mls b/shared/src/test/diff/ucs/InterleavedLet.mls index 113e351a7..e89530159 100644 --- a/shared/src/test/diff/ucs/InterleavedLet.mls +++ b/shared/src/test/diff/ucs/InterleavedLet.mls @@ -39,15 +39,19 @@ fun q(x) = //│ q: Some -> 0 //│ = [Function: q] -// FIXME :w fun p(x, y) = if x is Some and y is None then 0 y is Some and x is Some then 1 x is Some and y is Some then 0 -//│ ╔══[WARNING] duplicated branch -//│ ╙── +//│ ╔══[WARNING] Found a duplicated branch +//│ ╟── This branch +//│ ║ l.47: x is Some and y is Some then 0 +//│ ║ ^ +//│ ╟── is subsumed by the branch here. +//│ ║ l.46: y is Some and x is Some then 1 +//│ ╙── ^ //│ p: (Some, None | Some,) -> (0 | 1) //│ = [Function: p] @@ -88,9 +92,9 @@ fun q(a) = let y = a + 1 then y //│ ╔══[PARSE ERROR] Expected an expression; found a 'then'/'else' clause instead -//│ ║ l.88: let y = a + 1 +//│ ║ l.92: let y = a + 1 //│ ║ ^^^^^ -//│ ║ l.89: then y +//│ ║ l.93: then y //│ ╙── ^^^^^^^^^^ //│ q: (Left & {leftValue: 'leftValue}) -> 'leftValue //│ = [Function: q1] @@ -239,7 +243,6 @@ showList(zeroToThree) //│ res: string //│ = '0, 1, 2, 3' -// FIXME: This needs lifting functions. fun mapPartition(f, xs) = if xs is Nil then Pair(Nil(), Nil()) @@ -249,28 +252,75 @@ fun mapPartition(f, xs) = let r = res.snd Left(v) then Pair(Cons(v, l), r) Right(v) then Pair(l, Cons(v, r)) -//│ mapPartition: ('head -> (Left & {leftValue: 'leftValue} | Right & {rightValue: 'rightValue}), 'tail,) -> (Pair & {fst: 'fst, snd: 'tail0}) +//│ mapPartition: ('head -> (Left & {leftValue: 'leftValue} | Right & {rightValue: 'rightValue}), 'tail,) -> (Pair & {fst: 'fst, snd: 'snd}) //│ where -//│ 'tail0 :> Cons & {head: 'rightValue, tail: 'tail0} | Nil -//│ 'fst :> Nil | Cons & {head: 'leftValue, tail: 'fst} +//│ 'snd :> Cons & {head: 'rightValue, tail: 'snd} +//│ 'fst :> Cons & {head: 'leftValue, tail: 'fst} //│ 'tail <: Cons & {head: 'head, tail: 'tail} | Nil //│ = [Function: mapPartition] -// FIXME: Something wrong with code generation. mapPartition(x => if x % 2 == 0 then Left(x) else Right(x), zeroToThree) //│ res: Pair & {fst: 'fst, snd: 'snd} //│ where //│ 'snd :> Cons & {head: 0 | 1 | 2 | 3, tail: 'snd} | Nil //│ 'fst :> Nil | Cons & {head: 0 | 1 | 2 | 3, tail: 'fst} -//│ Runtime error: -//│ RangeError: Maximum call stack size exceeded +//│ = Pair { +//│ fst: Cons { head: 0, tail: Cons { head: 2, tail: Nil {} } }, +//│ snd: Cons { head: 1, tail: Cons { head: 3, tail: Nil {} } } +//│ } + +// This should be the desugaring of the above: +fun mapPartition2(f, xs) = + if xs is + Nil then Pair(Nil(), Nil()) + Cons(x, xs) and mapPartition(f, xs) is res and res.fst is l and res.snd is r and f(x) is + Left(v) then Pair(Cons(v, l), r) + Right(v) then Pair(l, Cons(v, r)) +//│ mapPartition2: ('head -> (Left & {leftValue: 'leftValue} | Right & {rightValue: 'rightValue}) & 'head0 -> (Left & {leftValue: 'leftValue0} | Right & {rightValue: 'rightValue0}), Cons & {head: 'head0, tail: 'tail} | Nil,) -> (Pair & {fst: forall 'fst. Cons & {head: 'leftValue0, tail: forall 'fst. Nil | 'fst | Cons & {head: 'leftValue, tail: Nil | 'fst}} | Nil | 'fst | Cons & {head: 'leftValue, tail: Nil | 'fst}, snd: forall 'snd. Cons & {head: 'rightValue0, tail: forall 'snd. Nil | 'snd | Cons & {head: 'rightValue, tail: Nil | 'snd}} | Nil | 'snd | Cons & {head: 'rightValue, tail: Nil | 'snd}}) +//│ where +//│ 'snd :> Nil | Cons & {head: 'rightValue, tail: 'snd} +//│ 'fst :> Nil | Cons & {head: 'leftValue, tail: 'fst} +//│ 'tail <: Cons & {head: 'head, tail: 'tail} | Nil +//│ = [Function: mapPartition2] + +mapPartition2(x => if x % 2 == 0 then Left(x) else Right(x), zeroToThree) +//│ res: Pair & {fst: forall 'fst. Cons & {head: 0, tail: forall 'fst. Nil | 'fst | Cons & {head: 1 | 2 | 3, tail: Nil | 'fst}} | Nil | 'fst | Cons & {head: 1 | 2 | 3, tail: Nil | 'fst}, snd: forall 'fst. Cons & {head: 0, tail: forall 'fst. Nil | 'fst | Cons & {head: 1 | 2 | 3, tail: Nil | 'fst}} | Nil | 'fst | Cons & {head: 1 | 2 | 3, tail: Nil | 'fst}} +//│ where +//│ 'fst :> Nil | Cons & {head: 1 | 2 | 3, tail: 'fst} +//│ = Pair { +//│ fst: Cons { head: 0, tail: Cons { head: 2, tail: Nil {} } }, +//│ snd: Cons { head: 1, tail: Cons { head: 3, tail: Nil {} } } +//│ } + +fun log(x) = () +//│ log: anything -> () +//│ = [Function: log] fun mn(a) = if a is Some(x) and x is - Left(a) then "left-defined" - let y = x + 1 + Left(b) and b is + 0 then "b is 1" + let _ = log(b) + 1 then "b is 2" + 2 then "b is 3" Right(b) then "right-defined" None then "undefined" -//│ mn: (None | Some & {value: nothing}) -> ("left-defined" | "right-defined" | "undefined") +//│ mn: (None | Some & {value: Left & {leftValue: 0 | 1 | 2} | Right}) -> ("b is 1" | "b is 2" | "b is 3" | "right-defined" | "undefined") //│ = [Function: mn] + +mn(None()) +mn(Some(Left(0))) +mn(Some(Left(1))) +mn(Some(Left(2))) +mn(Some(Right(()))) +//│ res: "b is 1" | "b is 2" | "b is 3" | "right-defined" | "undefined" +//│ = 'undefined' +//│ res: "b is 1" | "b is 2" | "b is 3" | "right-defined" | "undefined" +//│ = 'b is 1' +//│ res: "b is 1" | "b is 2" | "b is 3" | "right-defined" | "undefined" +//│ = 'b is 2' +//│ res: "b is 1" | "b is 2" | "b is 3" | "right-defined" | "undefined" +//│ = 'b is 3' +//│ res: "b is 1" | "b is 2" | "b is 3" | "right-defined" | "undefined" +//│ = 'right-defined' diff --git a/shared/src/test/diff/ucs/JSON.mls b/shared/src/test/diff/ucs/JSON.mls new file mode 100644 index 000000000..ce5b47c5e --- /dev/null +++ b/shared/src/test/diff/ucs/JSON.mls @@ -0,0 +1,317 @@ +:NewParser +:NewDefs + +:escape +// We need to use some native methods on `String`. +let String: nothing +let asNativeString: anything => { length: int, charCodeAt: int => int, charAt: int => string, slice: int => string } = String +let StringInstance: { fromCharCode: int => string } = String +// We will validate our implementation with the built-in `JSON.parse`. +let JSON: { parse: string => anything, stringify: anything => string } +//│ let String: nothing +//│ let asNativeString: anything -> {charAt: int -> string, charCodeAt: int -> int, length: int, slice: int -> string} +//│ let StringInstance: {fromCharCode: int -> string} +//│ let JSON: {parse: string -> anything, stringify: anything -> string} +//│ String +//│ = +//│ asNativeString +//│ = [Function: String] +//│ StringInstance +//│ = [Function: String] +//│ JSON +//│ = + +JSON.parse("{ \"xs\": [1, 2, 3], \"yes\": true, \"no\": false, \"insane\": null }") +//│ anything +//│ res +//│ = { xs: [ 1, 2, 3 ], yes: true, no: false, insane: null } + +let getStringOf = toString +fun fromCharCode(n) = StringInstance.fromCharCode(n) +fun firstCharCode(s) = asNativeString(s).charCodeAt(0) +fun getCharAtIndex(s, i) = asNativeString(s).charAt(i) +fun strlen(s) = asNativeString(s).length +fun stringHead(s) = asNativeString(s).charAt(0) +fun stringTail(s) = asNativeString(s).slice(1) +//│ let getStringOf: anything -> string +//│ fun fromCharCode: int -> string +//│ fun firstCharCode: anything -> int +//│ fun getCharAtIndex: (anything, int,) -> string +//│ fun strlen: anything -> int +//│ fun stringHead: anything -> string +//│ fun stringTail: anything -> string +//│ getStringOf +//│ = [Function: toString] + +fun isWhiteSpace(ch) = + if (firstCharCode of ch) == + 9 then true // horizontal tab + 10 then true // linefeed + 32 then true // space + _ then false +//│ fun isWhiteSpace: anything -> bool + +fun isDigit(ch) = + let n = firstCharCode of ch + if 48 <= n and n <= 57 then true else false +//│ fun isDigit: anything -> bool + +fun isAlphabet(ch) = + let n = firstCharCode of ch + if n <= + 90 and n >= 65 then true + 122 and n >= 97 then true + else false +//│ fun isAlphabet: anything -> bool + +fun concat2(a, b) = concat(a)(b) +fun concat3(a, b, c) = concat2(a, concat2(b, c)) +fun concat4(a, b, c, d) = concat2(a, concat3(b, c, d)) +fun concat5(a, b, c, d, e) = concat2(a, concat4(b, c, d, e)) +fun concat6(a, b, c, d, e, f) = concat2(a, concat5(b, c, d, e, f)) +fun concat7(a, b, c, d, e, f, g) = concat2(a, concat6(b, c, d, e, f, g)) +fun concat8(a, b, c, d, e, f, g, h) = concat2(a, concat7(b, c, d, e, f, g, h)) +fun par(a) = concat3("(", a, ")") +//│ fun concat2: (string, string,) -> string +//│ fun concat3: (string, string, string,) -> string +//│ fun concat4: (string, string, string, string,) -> string +//│ fun concat5: (string, string, string, string, string,) -> string +//│ fun concat6: (string, string, string, string, string, string,) -> string +//│ fun concat7: (string, string, string, string, string, string, string,) -> string +//│ fun concat8: (string, string, string, string, string, string, string, string,) -> string +//│ fun par: string -> string + +type Option[A] = Some[A] | None +module None +class Some[A](value: A) +//│ type Option[A] = Some[A] | None +//│ module None() +//│ class Some[A](value: A) + +type List[A] = Cons[A] | Nil +module Nil +class Cons[A](head: A, tail: List[A]) +fun listConcat(xs, ys) = + if xs is + Nil then ys + Cons(x, xs') then Cons(x, listConcat(xs', ys)) +fun listJoin(xs, sep) = + if xs is + Nil then "" + Cons(x, xs') and xs' is + Nil then toString(x) + _ then concat3(toString(x), sep, listJoin(xs', sep)) +//│ type List[A] = Cons[A] | Nil +//│ module Nil() +//│ class Cons[A](head: A, tail: List[A]) +//│ fun listConcat: forall 'A 'A0 'a. (Cons['A] | Nil, List['A0] & 'a,) -> (Cons['A0] | 'a) +//│ fun listJoin: forall 'A1. (Cons['A1] | Nil, string,) -> string +//│ where +//│ 'A <: 'A0 + +type TreeMap[A] = Node[A] | Empty +module Empty +class Node[A](key: string, value: A, left: TreeMap[A], right: TreeMap[A]) +fun insert(t, k, v) = + if t is + Node(k', _, l, r) and + slt(k)(k') then Node(k', v, insert(l, k, v), r) + sgt(k)(k') then Node(k', v, l, insert(r, k, v)) + _ then Node(k, v, l, r) + Empty then Node(k, v, Empty, Empty) +fun find(t, k) = + if t is + Node(k', v, l, r) and + slt(k)(k') then find(l, k) + sgt(k)(k') then find(r, k) + _ then Some(v) + Empty then None +fun traverse(t, f) = + if t is + Empty then Nil + Node(key, value, left, right) then + listConcat(traverse(left, f), Cons(f(key, value), traverse(right, f))) +//│ type TreeMap[A] = Node[A] | Empty +//│ module Empty() +//│ class Node[A](key: string, value: A, left: TreeMap[A], right: TreeMap[A]) +//│ fun insert: forall 'A. (Empty | Node['A], string, 'A,) -> Node['A] +//│ fun find: forall 'A0. (Empty | Node['A0], string,) -> (None | Some['A0]) +//│ fun traverse: forall 'A1 'A2. (Empty | Node['A1], (string, 'A1,) -> 'A2,) -> (Cons['A2] | Nil) + +type JsonValue = JsonNull | JsonNumber | JsonString | JsonBoolean | JsonObject | JsonArray +module JsonNull { + fun toString() = "null" +} +class JsonBoolean(value: bool) { + fun toString() = getStringOf(value) +} +class JsonNumber(value: number) { + fun toString() = getStringOf(value) +} +class JsonString(value: string) { + fun toString() = JSON.stringify(value) +} +class JsonObject(entries: TreeMap[JsonValue]) { + fun toString() = + if entries is Empty then "{}" + else concat3("{ ", listJoin(traverse(entries, (k, v) => concat3(k, ": ", getStringOf(v))), ", "), " }") +} +class JsonArray(elements: List[JsonValue]) { + fun toString() = concat3("[", listJoin(elements, ", "), "]") +} +//│ type JsonValue = JsonArray | JsonBoolean | JsonNumber | JsonObject | JsonString | JsonNull +//│ module JsonNull() { +//│ fun toString: () -> "null" +//│ } +//│ class JsonBoolean(value: bool) { +//│ fun toString: () -> string +//│ } +//│ class JsonNumber(value: number) { +//│ fun toString: () -> string +//│ } +//│ class JsonString(value: string) { +//│ fun toString: () -> string +//│ } +//│ class JsonObject(entries: TreeMap[JsonValue]) { +//│ fun toString: () -> string +//│ } +//│ class JsonArray(elements: List[JsonValue]) { +//│ fun toString: () -> string +//│ } + +toString of JsonNull +toString of JsonBoolean(true) +toString of JsonBoolean(false) +toString of JsonNumber(42) +toString of JsonArray of Nil +toString of JsonArray of Cons(JsonNumber(0), Cons(JsonNull, Cons(JsonNumber(1), Nil))) +toString of JsonObject of Empty +toString of JsonObject of insert(Empty, "hello", JsonString("world")) +//│ string +//│ res +//│ = 'null' +//│ res +//│ = 'true' +//│ res +//│ = 'false' +//│ res +//│ = '42' +//│ res +//│ = '[]' +//│ res +//│ = '[0, null, 1]' +//│ res +//│ = '{}' +//│ res +//│ = '{ hello: "world" }' + +class Scanner(source: string, at: int) { + fun peek: Option[string] = + if at < strlen(source) then Some(getCharAtIndex(source, at)) else None + fun advance: Scanner = + if at < strlen(source) then Scanner(source, at + 1) else this +} +fun scan(source) = Scanner(source, 0) +fun skipWhiteSpace(s: Scanner) = + if s.peek is Some(ch) and isWhiteSpace(ch) then + skipWhiteSpace(s.advance) + else + s +//│ class Scanner(source: string, at: int) { +//│ fun advance: Scanner +//│ fun peek: Option[string] +//│ } +//│ fun scan: string -> Scanner +//│ fun skipWhiteSpace: (s: Scanner,) -> Scanner + +type ParseResult[T] = ParseSuccess[T] | ParseFailure +class ParseSuccess[T](value: T, scanner: Scanner) { + fun toString() = concat2("Success: ", getStringOf(value)) +} +class ParseFailure(message: string, scanner: Scanner) { + fun toString() = concat4("Failure at ", getStringOf(scanner.at), ": ", message) +} +//│ type ParseResult[T] = ParseFailure | ParseSuccess[T] +//│ class ParseSuccess[T](value: T, scanner: Scanner) { +//│ fun toString: () -> string +//│ } +//│ class ParseFailure(message: string, scanner: Scanner) { +//│ fun toString: () -> string +//│ } + +fun expect(scanner, ch) = + if skipWhiteSpace(scanner).peek is + Some(ch') and + eq(ch)(ch') then ParseSuccess((), scanner.advance) + else ParseFailure(concat4("expect '", ch, "' but found ", ch'), scanner) + None then ParseFailure(concat3("expect '", ch, "' but found EOF"), scanner) +//│ fun expect: (Scanner & {advance: Scanner}, string,) -> (ParseFailure | ParseSuccess[()]) + +fun expectWord(scanner, word, result) = + if + strlen(word) > 0 and + let head = stringHead(word) + let tail = stringTail(word) + expect(scanner, head) is + ParseSuccess(_, scanner) then expectWord(scanner, tail, result) + ParseFailure(m, s) then ParseFailure(m, s) + scanner.peek is + Some(ch) and isAlphabet(ch) then + ParseFailure(concat3("there should not be other alphabets after\"", word, "\""), scanner) + else + ParseSuccess(result, scanner) +//│ fun expectWord: forall 'T. (Scanner & {peek: anything, advance: Scanner}, string, 'T,) -> (ParseFailure | ParseSuccess['T]) + +// If we put this function together with the next block, there will be type +// mismatch errors. +fun parseMatched(scanner, closingSymbol, parse, fn) = + if parse(scanner.advance) is + ParseSuccess(outcome, scanner) and expect(scanner, closingSymbol) is + ParseSuccess(_, scanner) then ParseSuccess(fn(outcome), scanner) + ParseFailure(message, scanner) then ParseFailure(message, scanner) + ParseFailure(message, scanner) then ParseFailure(message, scanner) +//│ fun parseMatched: forall 'advance 'value 'T. ({advance: 'advance}, string, 'advance -> (ParseFailure | ParseSuccess['value]), 'value -> 'T,) -> (ParseFailure | ParseSuccess['T]) + +:ng +fun parseEntries(scanner): ParseResult[TreeMap[JsonValue]] = error +fun parseElements(scanner): ParseResult[List[JsonValue]] = + let scanner' = skipWhiteSpace(scanner) + if scanner'.peek is + Some(ch) and + eq(ch)("]") then ParseSuccess(Nil, scanner') + parse(scanner') is + ParseSuccess(head, scanner') and scanner'.peek is + Some(ch) and eq(ch)(",") and parseElements(scanner'.advance) is + ParseSuccess(tail, scanner') then ParseSuccess(Cons(head, tail), scanner') + ParseFailure(m, s) then ParseFailure(m, s) + _ then ParseFailure("expect ']' or ',' instead of EOF", scanner') + ParseFailure(m, s) then ParseFailure(m, s) + None then ParseFailure("unexpected EOF", scanner) +fun parseStringContent(scanner): ParseResult[string] = error +fun parseNumber(scanner): ParseResult[JsonNumber] = error +fun parse(scanner) = + let scanner' = skipWhiteSpace(scanner) + if scanner'.peek is + None then ParseFailure("expect a JSON value instead of EOF", scanner') + Some(ch) and + eq(ch)("{") then parseMatched(scanner', "}", parseEntries, JsonObject) + eq(ch)("[") then parseMatched(scanner', "]", parseElements, JsonArray) + eq(ch)("\"") then parseMatched(scanner', "\"", parseStringContent, JsonString) + eq(ch)("-") then parseNumber(scanner') + eq(ch)("t") then expectWord(scanner', "true", JsonBoolean(true)) + eq(ch)("f") then expectWord(scanner', "false", JsonBoolean(false)) + eq(ch)("n") then expectWord(scanner', "null", JsonNull) + else + ParseFailure(concat3("unrecognized character '", ch, "'"), scanner) +//│ fun parseEntries: anything -> ParseResult[TreeMap[JsonValue]] +//│ fun parseElements: Scanner -> ParseResult[List[JsonValue]] +//│ fun parseStringContent: anything -> ParseResult[string] +//│ fun parseNumber: anything -> ParseResult[JsonNumber] +//│ fun parse: Scanner -> (ParseFailure | ParseSuccess[JsonArray | JsonBoolean | JsonNull | JsonObject | JsonString] | ParseResult[JsonNumber]) + +:ng +toString of parse of scan of " true" +toString of parse of scan of " false" +toString of parse of scan of " null" +toString of parse of scan of "[null]" +//│ string diff --git a/shared/src/test/diff/ucs/LeadingAnd.mls b/shared/src/test/diff/ucs/LeadingAnd.mls new file mode 100644 index 000000000..194cbe59d --- /dev/null +++ b/shared/src/test/diff/ucs/LeadingAnd.mls @@ -0,0 +1,52 @@ +:NewDefs + + + +class Some[T](value: T) +//│ class Some[T](value: T) + + + +// TODO +fun f(a, b) = if a is + Some(av) + and b is Some(bv) then av + bv +//│ ╔══[ERROR] Cannot find operator `and` in the context +//│ ║ l.13: and b is Some(bv) then av + bv +//│ ╙── ^^^ +//│ fun f: (anything, anything,) -> error +//│ Code generation encountered an error: +//│ if expression was not desugared + +:p +fun f(a, b) = if a is Some(av) + and b is Some(bv) + then av + bv +//│ |#fun| |f|(|a|,| |b|)| |#=| |#if| |a| |is| |Some|(|av|)|→|and| |b| |is| |Some|(|bv|)|↵|#then| |av| |+| |bv|←| +//│ Parsed: fun f = (a, b,) => if a is Some (av,) ‹· and (is (b,) (Some (bv,),)) then + (av,) (bv,)›; +//│ fun f: (Some[int], Some[int],) -> int + +// TODO +:p +fun f(a, b) = if a is + Some(av) + and b is Some(bv) + then av + bv +//│ |#fun| |f|(|a|,| |b|)| |#=| |#if| |a| |is|→|Some|(|av|)|→|and| |b| |is| |Some|(|bv|)|↵|#then| |av| |+| |bv|←|←| +//│ Parsed: fun f = (a, b,) => if a is ‹Some (av,) ‹· and (is (b,) (Some (bv,),)) then + (av,) (bv,)››; +//│ ╔══[ERROR] Cannot find operator `and` in the context +//│ ║ l.33: and b is Some(bv) +//│ ╙── ^^^ +//│ fun f: (anything, anything,) -> error +//│ Code generation encountered an error: +//│ if expression was not desugared + + + +// FIXME (parser) +fun f(a, b) = if a is + Some(av) + and b is Some(bv) then av + bv + +//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing + diff --git a/shared/src/test/diff/ucs/LitUCS.mls b/shared/src/test/diff/ucs/LitUCS.mls index e6fd4f419..ea76bb848 100644 --- a/shared/src/test/diff/ucs/LitUCS.mls +++ b/shared/src/test/diff/ucs/LitUCS.mls @@ -1,38 +1,60 @@ :NewDefs - module A //│ module A() -// FIXME +// This one is easy to fix but what about the next one? +// The following example can better reveal the essence of the problem. fun test(x: 0 | A) = if x is 0 then 0 A then A -//│ ╔══[ERROR] Type mismatch in application: -//│ ║ l.8: fun test(x: 0 | A) = if x is -//│ ║ ^ -//│ ╟── type `A` is not an instance of type `number` -//│ ║ l.8: fun test(x: 0 | A) = if x is -//│ ║ ^ -//│ ╟── but it flows into reference with expected type `number` -//│ ║ l.8: fun test(x: 0 | A) = if x is -//│ ╙── ^ +//│ fun test: (x: 0 | A,) -> (0 | A) + +:e +// case === (x,) (0,) of { true => 0; _ => case x of { A => A } } +fun test(x: 0 | A) = + if + x === 0 then 0 + x is A then A //│ ╔══[ERROR] Type mismatch in `case` expression: -//│ ║ l.8: fun test(x: 0 | A) = if x is -//│ ║ ^^^^ -//│ ║ l.9: 0 then 0 -//│ ║ ^^^^^^^^^^ -//│ ║ l.10: A then A -//│ ║ ^^^^^^^^^^ +//│ ║ l.18: x is A then A +//│ ║ ^^^^^^^^^^^^^ //│ ╟── type `0` is not an instance of type `A` -//│ ║ l.8: fun test(x: 0 | A) = if x is -//│ ║ ^ +//│ ║ l.15: fun test(x: 0 | A) = +//│ ║ ^ //│ ╟── but it flows into reference with expected type `A` -//│ ║ l.8: fun test(x: 0 | A) = if x is -//│ ║ ^ +//│ ║ l.18: x is A then A +//│ ║ ^ //│ ╟── Note: constraint arises from class pattern: -//│ ║ l.10: A then A -//│ ╙── ^ +//│ ║ l.18: x is A then A +//│ ╙── ^ //│ fun test: (x: 0 | A,) -> (0 | A) +fun test2(x) = + if + x === 0 then 0 + x is A then A +//│ fun test2: (A & Eql[0]) -> (0 | A) + +:e +test2(0) +//│ ╔══[ERROR] Type mismatch in application: +//│ ║ l.40: test2(0) +//│ ║ ^^^^^^^^ +//│ ╟── integer literal of type `0` is not an instance of type `A` +//│ ║ l.40: test2(0) +//│ ║ ^ +//│ ╟── Note: constraint arises from class pattern: +//│ ║ l.36: x is A then A +//│ ║ ^ +//│ ╟── from reference: +//│ ║ l.36: x is A then A +//│ ╙── ^ +//│ 0 | A | error +//│ res +//│ = 0 +test2(A) +//│ 0 | A +//│ res +//│ = A { class: [class A] } diff --git a/shared/src/test/diff/ucs/MultiwayIf.mls b/shared/src/test/diff/ucs/MultiwayIf.mls index 6a59d9b55..e7d8c01eb 100644 --- a/shared/src/test/diff/ucs/MultiwayIf.mls +++ b/shared/src/test/diff/ucs/MultiwayIf.mls @@ -10,7 +10,6 @@ fun f(x) = //│ = [Function: f] -// FIXME fun f(x) = if x > 0 and @@ -18,16 +17,25 @@ fun f(x) = _ then false x == 0 then true _ then false -//│ ╔══[WARNING] Found a duplicated else branch -//│ ║ l.20: _ then false -//│ ║ ^^^^^ -//│ ╟── The first else branch was declared here. -//│ ║ l.18: _ then false -//│ ╙── ^^^^^ //│ f: int -> bool //│ = [Function: f1] -// FIXME +f(0) +f(2) +f(3) +f(0 - 1) +f(0 - 2) +//│ res: bool +//│ = true +//│ res: bool +//│ = true +//│ res: bool +//│ = false +//│ res: bool +//│ = false +//│ res: bool +//│ = false + fun f(x) = if x > 0 and @@ -35,13 +43,18 @@ fun f(x) = else false x == 0 then true else false -//│ ╔══[WARNING] Found a duplicated else branch -//│ ║ l.37: else false -//│ ║ ^^^^^ -//│ ╟── The first else branch was declared here. -//│ ║ l.35: else false -//│ ╙── ^^^^^ //│ f: int -> bool //│ = [Function: f2] - +f(0) +f(2) +f(1) +f(0 - 1) +//│ res: bool +//│ = true +//│ res: bool +//│ = true +//│ res: bool +//│ = false +//│ res: bool +//│ = false diff --git a/shared/src/test/diff/ucs/NestedBranches.mls b/shared/src/test/diff/ucs/NestedBranches.mls index af2293b33..fca44522c 100644 --- a/shared/src/test/diff/ucs/NestedBranches.mls +++ b/shared/src/test/diff/ucs/NestedBranches.mls @@ -1,88 +1,82 @@ :NewParser - - -class Option -class Some(value): Option -class None: Option -class Either -class Left(leftValue): Either -class Right(rightValue): Either -class List -class Nil: List -class Cons(head, tail): List -class Pair(fst, snd) -//│ Defined class Option -//│ Defined class Some -//│ Defined class None -//│ Defined class Either -//│ Defined class Left -//│ Defined class Right -//│ Defined class List -//│ Defined class Nil -//│ Defined class Cons -//│ Defined class Pair -//│ Option: () -> Option -//│ = [Function: Option1] -//│ Some: 'value -> (Some & {value: 'value}) -//│ = [Function: Some1] -//│ None: () -> None -//│ = [Function: None1] -//│ Either: () -> Either -//│ = [Function: Either1] -//│ Left: 'leftValue -> (Left & {leftValue: 'leftValue}) -//│ = [Function: Left1] -//│ Right: 'rightValue -> (Right & {rightValue: 'rightValue}) -//│ = [Function: Right1] -//│ List: () -> List -//│ = [Function: List1] -//│ Nil: () -> Nil -//│ = [Function: Nil1] -//│ Cons: ('head, 'tail,) -> (Cons & {head: 'head, tail: 'tail}) -//│ = [Function: Cons1] -//│ Pair: ('fst, 'snd,) -> (Pair & {fst: 'fst, snd: 'snd}) -//│ = [Function: Pair1] - - +:NewDefs + +class Some[A](value: A) +module None +class Left[A](leftValue: A) +class Right[A](rightValue: A) +module Nil +class Cons[A](head: A, tail: Cons[A] | Nil) +class Pair[A, B](fst: A, snd: B) +//│ class Some[A](value: A) +//│ module None() +//│ class Left[A](leftValue: A) +//│ class Right[A](rightValue: A) +//│ module Nil() +//│ class Cons[A](head: A, tail: Cons[A] | Nil) +//│ class Pair[A, B](fst: A, snd: B) fun optionApply(x, y, f) = if x is Some(xv) and y is Some(yv) then Some(f(xv, yv)) - None() then None() - None() then None() -//│ optionApply: (None | Some & {value: 'value}, None | Some & {value: 'value0}, ('value, 'value0,) -> 'value1,) -> (None | Some & {value: 'value1}) -//│ = [Function: optionApply] + None then None + None then None +//│ fun optionApply: forall 'value 'value0 'A. (None | Some['value], None | Some['value0], ('value, 'value0,) -> 'A,) -> (None | Some['A]) +let zeroToThree = Cons(0, Cons(1, Cons(2, Cons(3, Nil)))) +//│ let zeroToThree: Cons['A] +//│ where +//│ 'A :> 0 | 1 | 2 | 3 +//│ zeroToThree +//│ = Cons {} +fun f(x) = if x % 2 == 0 then Left(x) else Right(x) +//│ fun f: forall 'A. (int & 'A) -> (Left['A] | Right['A]) fun mapPartition(f, xs) = if xs is - Nil then Pair(Nil(), Nil()) + Nil then Pair(Nil, Nil) Cons(x, xs) and mapPartition(f, xs) is Pair(l, r) and f(x) is Left(v) then Pair(Cons(v, l), r) Right(v) then Pair(l, Cons(v, r)) -//│ mapPartition: ('head -> (Left & {leftValue: 'leftValue} | Right & {rightValue: 'rightValue}), 'tail,) -> (Pair & {fst: 'fst, snd: 'tail0}) +//│ fun mapPartition: forall 'head 'A 'A0. ('head -> (Left['A] | Right['A0]), Cons['head] | Nil,) -> Pair[Cons['A] | Nil, Cons['A0] | Nil] + +mapPartition(x => Left(x + 1), zeroToThree) +//│ Pair[Cons['A] | Nil, Cons['A0] | Nil] +//│ where +//│ 'A :> int +//│ res +//│ = Pair {} + +mapPartition(f, zeroToThree) +//│ Pair[Cons['A] | Nil, Cons['A0] | Nil] //│ where -//│ 'tail0 :> Cons & {head: 'rightValue, tail: 'tail0} | Nil -//│ 'fst :> Nil | Cons & {head: 'leftValue, tail: 'fst} -//│ 'tail <: Cons & {head: 'head, tail: 'tail} | Nil -//│ = [Function: mapPartition] +//│ 'A0 :> 0 | 1 | 2 | 3 +//│ 'A :> 0 | 1 | 2 | 3 +//│ res +//│ = Pair {} + fun mapPartition(f, xs) = if xs is - Nil then Pair(Nil(), Nil()) + Nil then Pair(Nil, Nil) Cons(x, xs) and mapPartition(f, xs) is Pair(l, r) and f(x) is Left(v) then Pair(Cons(v, l), r) Right(v) then Pair(l, Cons(v, r)) -//│ mapPartition: ('head -> (Left & {leftValue: 'leftValue} | Right & {rightValue: 'rightValue}), 'tail,) -> (Pair & {fst: 'fst, snd: 'tail0}) +//│ fun mapPartition: forall 'A 'head 'A0. ('head -> (Left['A0] | Right['A]), Cons['head] | Nil,) -> Pair[Cons['A0] | Nil, Cons['A] | Nil] + +mapPartition(f, zeroToThree) +//│ Pair[Cons['A] | Nil, Cons['A0] | Nil] //│ where -//│ 'tail0 :> Cons & {head: 'rightValue, tail: 'tail0} | Nil -//│ 'fst :> Nil | Cons & {head: 'leftValue, tail: 'fst} -//│ 'tail <: Cons & {head: 'head, tail: 'tail} | Nil -//│ = [Function: mapPartition1] +//│ 'A0 :> 0 | 1 | 2 | 3 +//│ 'A :> 0 | 1 | 2 | 3 +//│ res +//│ = Pair {} + fun mapPartition(f, xs) = if xs is Nil then - Pair(Nil(), Nil()) + Pair(Nil, Nil) Cons(x, xs) and mapPartition(f, xs) is Pair(l, r) and @@ -91,27 +85,35 @@ fun mapPartition(f, xs) = if xs is Pair(Cons(v, l), r) Right(v) then Pair(l, Cons(v, r)) -//│ mapPartition: ('head -> (Left & {leftValue: 'leftValue} | Right & {rightValue: 'rightValue}), 'tail,) -> (Pair & {fst: 'fst, snd: 'tail0}) -//│ where -//│ 'tail0 :> Cons & {head: 'rightValue, tail: 'tail0} | Nil -//│ 'fst :> Nil | Cons & {head: 'leftValue, tail: 'fst} -//│ 'tail <: Cons & {head: 'head, tail: 'tail} | Nil -//│ = [Function: mapPartition2] +//│ fun mapPartition: forall 'head 'A 'A0. ('head -> (Left['A] | Right['A0]), Cons['head] | Nil,) -> Pair[Cons['A] | Nil, Cons['A0] | Nil] +mapPartition(f, zeroToThree) +//│ Pair[Cons['A] | Nil, Cons['A0] | Nil] +//│ where +//│ 'A0 :> 0 | 1 | 2 | 3 +//│ 'A :> 0 | 1 | 2 | 3 +//│ res +//│ = Pair {} // TODO make this one work (needs tuple support) fun mapPartition(f, xs) = if xs is - Nil then (Nil(), Nil()) + Nil then (Nil, Nil) Cons(x, xs) and mapPartition(f, xs) is (l, r) and f(x) is Left(v) then (Cons(v, l), r) Right(v) then (l, Cons(v, r)) //│ ╔══[ERROR] The case when this is false is not handled: is (mapPartition (f, xs,),) (l, r,) -//│ ║ l.105: Cons(x, xs) and mapPartition(f, xs) is (l, r) and f(x) is +//│ ║ l.101: Cons(x, xs) and mapPartition(f, xs) is (l, r) and f(x) is //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ mapPartition: (anything, anything,) -> error +//│ fun mapPartition: (anything, anything,) -> error //│ Code generation encountered an error: //│ if expression was not desugared +// TODO +mapPartition(f, zeroToThree) +//│ error +//│ res +//│ Runtime error: +//│ ReferenceError: mapPartition3 is not defined // * Vertical alignment is not allowed! (good) @@ -120,24 +122,20 @@ fun mapPartition(f, xs) = if xs is :e :ge fun mapPartition(f, xs) = if xs is - Nil then (Nil(), Nil()) + Nil then (Nil, Nil) Cons(x, xs) and mapPartition(f, xs) is (l, r) and f(x) is Left(v) then (Cons(v, l), r) Right(v) then (l, Cons(v, r)) //│ ╔══[PARSE ERROR] Unexpected 'then' keyword here -//│ ║ l.126: Right(v) then (l, Cons(v, r)) +//│ ║ l.128: Right(v) then (l, Cons(v, r)) //│ ╙── ^^^^ //│ ╔══[WARNING] Paren-less applications should use the 'of' keyword -//│ ║ l.125: and f(x) is Left(v) then (Cons(v, l), r) +//│ ║ l.127: and f(x) is Left(v) then (Cons(v, l), r) //│ ║ ^^^^^^^^^^^^^^^ -//│ ║ l.126: Right(v) then (l, Cons(v, r)) +//│ ║ l.128: Right(v) then (l, Cons(v, r)) //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╔══[ERROR] type identifier not found: Tuple#2 //│ ╙── -//│ mapPartition: (anything, 'tail,) -> ((Nil, Nil,) | error) -//│ where -//│ 'tail <: Cons & {tail: 'tail} | Nil +//│ fun mapPartition: forall 'A. (anything, Cons['A] | Nil,) -> ((Nil, Nil,) | error) //│ Code generation encountered an error: //│ unknown match case: Tuple#2 - - diff --git a/shared/src/test/diff/ucs/NestedPattern.mls b/shared/src/test/diff/ucs/NestedPattern.mls index 469650599..5d9b919bd 100644 --- a/shared/src/test/diff/ucs/NestedPattern.mls +++ b/shared/src/test/diff/ucs/NestedPattern.mls @@ -68,3 +68,16 @@ fun f(p) = //│ ╙── //│ f: (None | Some) -> (0 | error) +class Union(a, b) +//│ Defined class Union +//│ Union: ('a, 'b,) -> (Union & {a: 'a, b: 'b}) + +// Name conflict between the scrutinee and the positionals. +// Desugar result: let tmp13 = x in case tmp13 of { Union => let x = (tmp13).a in let y = (tmp13).b in x } +fun hmm(x) = + if x is Union(x, y) then x +//│ hmm: (Union & {a: 'a}) -> 'a + +hmm(Union(1, 2)) +//│ res: 1 + diff --git a/shared/src/test/diff/ucs/Or.mls b/shared/src/test/diff/ucs/Or.mls new file mode 100644 index 000000000..334ecb9af --- /dev/null +++ b/shared/src/test/diff/ucs/Or.mls @@ -0,0 +1,21 @@ +:NewDefs + + +class Some[T](value: T) +//│ class Some[T](value: T) + + +// TODO support `or` in UCS +fun f(a, b) = if a is + Some(v) + and b is Some(v') then v + v' + or b is Some(v) then v + else 0 +//│ ╔══[ERROR] Cannot find operator `and` in the context +//│ ║ l.11: and b is Some(v') then v + v' +//│ ╙── ^^^ +//│ fun f: (anything, anything,) -> error +//│ Code generation encountered an error: +//│ if expression was not desugared + + diff --git a/shared/src/test/diff/ucs/OverlappedBranches.mls b/shared/src/test/diff/ucs/OverlappedBranches.mls new file mode 100644 index 000000000..1bac22866 --- /dev/null +++ b/shared/src/test/diff/ucs/OverlappedBranches.mls @@ -0,0 +1,100 @@ +:NewParser + +class Base +class Derived1 extends Base +class Derived2 extends Base +class Derived3 extends Derived2 +//│ Defined class Base +//│ Defined class Derived1 +//│ Defined class Derived2 +//│ Defined class Derived3 +//│ Base: () -> Base +//│ = [Function: Base1] +//│ Derived1: () -> Derived1 +//│ = [Function: Derived11] +//│ Derived2: () -> Derived2 +//│ = [Function: Derived21] +//│ Derived3: () -> Derived3 +//│ = [Function: Derived31] + +// The very basic case. +:w +fun f1(x) = if x is + Base then "b" + Derived1 then "d1" + Derived2 then "d2" +//│ ╔══[WARNING] Found a duplicated branch +//│ ╟── This branch +//│ ║ l.24: Derived1 then "d1" +//│ ║ ^^^^ +//│ ╟── is subsumed by the branch here. +//│ ║ l.23: Base then "b" +//│ ╙── ^^^ +//│ ╔══[WARNING] Found a duplicated branch +//│ ╟── This branch +//│ ║ l.25: Derived2 then "d2" +//│ ║ ^^^^ +//│ ╟── is subsumed by the branch here. +//│ ║ l.23: Base then "b" +//│ ╙── ^^^ +//│ f1: Base -> "b" +//│ = [Function: f1] + +f1(Base()) +f1(Derived1()) +f1(Derived2()) +//│ res: "b" +//│ = 'b' +//│ res: "b" +//│ = 'b' +//│ res: "b" +//│ = 'b' + +// Decision paths: +// + «x is Base» and «p (x,)» => "b and p" +// + «x is Derived1» => "d1" +// + «x is Derived2» => "d2" +// + => "otherwise" +// The case tree: +// «x» match +// case Base => +// if «p (x,)» +// «"b and p"» +// else +// «x» match +// case Derived1 => +// «"d1"» +// case Derived2 => +// «"d2"» +// default +// «"otherwise"» +// default +// «"otherwise"» +fun f2(x, p) = if x is + Base and p(x) then "b and p" + Derived1 then "d1" + Derived2 then "d2" + else "otherwise" +//│ f2: (Base & 'a | ~Base, 'a -> anything,) -> ("b and p" | "d1" | "d2" | "otherwise") +//│ = [Function: f2] + +f2(Base(), _ => true) // => b and p +f2(Base(), _ => false) // otherwise +//│ res: "b and p" | "d1" | "d2" | "otherwise" +//│ = 'b and p' +//│ res: "b and p" | "d1" | "d2" | "otherwise" +//│ = 'otherwise' + +f2(Derived1(), _ => true) // => b and p +f2(Derived2(), _ => true) // => b and p +//│ res: "b and p" | "d1" | "d2" | "otherwise" +//│ = 'b and p' +//│ res: "b and p" | "d1" | "d2" | "otherwise" +//│ = 'b and p' + +f2(Derived1(), _ => false) // => d1 +f2(Derived2(), _ => false) // => d2 +//│ res: "b and p" | "d1" | "d2" | "otherwise" +//│ = 'd1' +//│ res: "b and p" | "d1" | "d2" | "otherwise" +//│ = 'd2' diff --git a/shared/src/test/diff/ucs/ParseFailures.mls b/shared/src/test/diff/ucs/ParseFailures.mls new file mode 100644 index 000000000..2e9428c8b --- /dev/null +++ b/shared/src/test/diff/ucs/ParseFailures.mls @@ -0,0 +1,39 @@ +:NewDefs +:NoJS + +// FIXME +type Tree[A] = Node[A] | Empty +module Empty { + fun contains(wanted) = false +} +class Node[A](value: int, left: Tree[A], right: Tree[A]) { + fun contains(wanted) = if wanted + <= value then left.find(wanted) + >= value then right.find(wanted) + else true +} +//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing + +// FIXME +type Tree[A] = Node[A] | Empty +module Empty { + fun contains(wanted) = false +} +class Node[A](value: int, left: Tree[A], right: Tree[A]) { + fun contains(wanted) = if wanted + <= value then left.find(wanted) + >= value then right.find(wanted) + _ true +} +//│ /!!!\ Uncaught error: scala.NotImplementedError: an implementation is missing + +// FIXME +fun foo(x, y) = if x is + Z() and y is O() then 0 else 1 +//│ ╔══[PARSE ERROR] Unexpected 'else' keyword here +//│ ║ l.32: Z() and y is O() then 0 else 1 +//│ ╙── ^^^^ +//│ ╔══[ERROR] Illegal pattern `Z` +//│ ║ l.32: Z() and y is O() then 0 else 1 +//│ ╙── ^ +//│ fun foo: (anything, anything,) -> error diff --git a/shared/src/test/diff/ucs/PlainConditionals.mls b/shared/src/test/diff/ucs/PlainConditionals.mls index a8bfb4348..21c0a7d65 100644 --- a/shared/src/test/diff/ucs/PlainConditionals.mls +++ b/shared/src/test/diff/ucs/PlainConditionals.mls @@ -8,10 +8,18 @@ class Pair(fst, snd) +Pair(0, 1) is Pair +//│ res: bool +//│ = true + Pair(0, 1) is Pair(a, b) //│ res: bool //│ = true +Pair(0, 1) is Pair(0, _) +//│ res: bool +//│ = true + if Pair(0, 1) is Pair(a, b) then true else false //│ res: bool //│ = true @@ -40,4 +48,44 @@ fun foo(x) = if x is Pair(a, b) then a > b else false //│ = [Function: foo2] +// TODO proper error +fun foo(x) = x is + Pair + Int +//│ ╔══[ERROR] illegal pattern +//│ ║ l.53: Pair +//│ ║ ^^^^ +//│ ║ l.54: Int +//│ ╙── ^^^^^ +//│ foo: anything -> error +//│ Code generation encountered an error: +//│ if expression was not desugared + +// TODO proper error +fun foo(x) = x is + Pair(a, b) and a > b + Int +//│ ╔══[ERROR] illegal pattern +//│ ║ l.66: Pair(a, b) and a > b +//│ ║ ^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.67: Int +//│ ╙── ^^^^^ +//│ foo: anything -> error +//│ Code generation encountered an error: +//│ if expression was not desugared + +// TODO support `|` +fun foo(x) = x is Pair(a, b) | Int +fun foo(x) = x is (Pair(a, b) and a > b) | Int +//│ ╔══[ERROR] Cannot find operator `|` in the context +//│ ║ l.78: fun foo(x) = x is Pair(a, b) | Int +//│ ╙── ^ +//│ foo: anything -> error +//│ ╔══[ERROR] Cannot find operator `|` in the context +//│ ║ l.79: fun foo(x) = x is (Pair(a, b) and a > b) | Int +//│ ╙── ^ +//│ foo: anything -> error +//│ Code generation encountered an error: +//│ if expression was not desugared + diff --git a/shared/src/test/diff/ucs/SimpleUCS.mls b/shared/src/test/diff/ucs/SimpleUCS.mls index 0155bffbe..f30b1e29e 100644 --- a/shared/src/test/diff/ucs/SimpleUCS.mls +++ b/shared/src/test/diff/ucs/SimpleUCS.mls @@ -297,7 +297,6 @@ fun g(a, b) = //│ g: (int, None | Some & {value: int},) -> int //│ = [Function: g1] -// TODO: Fix the NaN. g(5, None()) g(5, Some(7)) g(0 - 5, None()) @@ -307,7 +306,7 @@ g(0 - 5, Some(9)) //│ res: int //│ = 35 //│ res: int -//│ = NaN +//│ = 25 //│ res: int //│ = 4 @@ -355,7 +354,7 @@ fun f(x) = 0 :: Nil() then "oh" //│ ╔══[ERROR] Cannot find operator `::` in the context -//│ ║ l.355: 0 :: +//│ ║ l.354: 0 :: //│ ╙── ^^ //│ f: anything -> error //│ Code generation encountered an error: diff --git a/shared/src/test/diff/ucs/SplitAfterOp.mls b/shared/src/test/diff/ucs/SplitAfterOp.mls index b8d8df041..3f0a23474 100644 --- a/shared/src/test/diff/ucs/SplitAfterOp.mls +++ b/shared/src/test/diff/ucs/SplitAfterOp.mls @@ -5,9 +5,11 @@ fun f(x, b) = if x == 0 and b then 0 -//│ ╔══[ERROR] The case when this is false is not handled: b +//│ ╔══[ERROR] The case when this is false is not handled: == (x,) (0,) +//│ ║ l.6: if x == +//│ ║ ^^^^^ //│ ║ l.7: 0 and b then 0 -//│ ╙── ^ +//│ ╙── ^^^^^^ //│ f: (anything, anything,) -> error //│ Code generation encountered an error: //│ if expression was not desugared @@ -18,11 +20,11 @@ if x == y + 5 then 0 7 then 0 //│ ╔══[ERROR] The case when this is false is not handled: + (== (x,) (y,),) (7,) -//│ ║ l.17: if x == y + +//│ ║ l.19: if x == y + //│ ║ ^^^^^^^^ -//│ ║ l.18: 5 then 0 +//│ ║ l.20: 5 then 0 //│ ║ ^^^^^^^^^^ -//│ ║ l.19: 7 then 0 +//│ ║ l.21: 7 then 0 //│ ╙── ^^^^ //│ res: error //│ Code generation encountered an error: @@ -34,11 +36,11 @@ if x == y * 5 then 0 6 + 7 then 0 //│ ╔══[ERROR] The case when this is false is not handled: * (== (x,) (y,),) (+ (6,) (7,),) -//│ ║ l.33: if x == y * +//│ ║ l.35: if x == y * //│ ║ ^^^^^^^^ -//│ ║ l.34: 5 then 0 +//│ ║ l.36: 5 then 0 //│ ║ ^^^^^^^^^^ -//│ ║ l.35: 6 + 7 then 0 +//│ ║ l.37: 6 + 7 then 0 //│ ╙── ^^^^^^^ //│ res: error //│ Code generation encountered an error: @@ -51,13 +53,13 @@ if x == 5 then 0 7 then 0 //│ ╔══[ERROR] The case when this is false is not handled: + (== (x,) (y,),) (7,) -//│ ║ l.49: if x == +//│ ║ l.51: if x == //│ ║ ^^^^ -//│ ║ l.50: y + +//│ ║ l.52: y + //│ ║ ^^^^^ -//│ ║ l.51: 5 then 0 +//│ ║ l.53: 5 then 0 //│ ║ ^^^^^^^^^^^^ -//│ ║ l.52: 7 then 0 +//│ ║ l.54: 7 then 0 //│ ╙── ^^^^^ //│ res: error //│ Code generation encountered an error: @@ -67,9 +69,11 @@ if x == :ge if x == 1 and b then 0 -//│ ╔══[ERROR] The case when this is false is not handled: b -//│ ║ l.69: 1 and b then 0 -//│ ╙── ^ +//│ ╔══[ERROR] The case when this is false is not handled: == (x,) (1,) +//│ ║ l.70: if x == +//│ ║ ^^^^ +//│ ║ l.71: 1 and b then 0 +//│ ╙── ^^^^ //│ res: error //│ Code generation encountered an error: //│ if expression was not desugared @@ -82,11 +86,11 @@ fun toEnglish(x) = true then "t" 0 then "z" //│ ╔══[ERROR] The case when this is false is not handled: == (x,) (0,) -//│ ║ l.81: if x == +//│ ║ l.85: if x == //│ ║ ^^^^ -//│ ║ l.82: true then "t" +//│ ║ l.86: true then "t" //│ ║ ^^^^^^^^^^^^^^^^^ -//│ ║ l.83: 0 then "z" +//│ ║ l.87: 0 then "z" //│ ╙── ^^^^^^ //│ toEnglish: anything -> error //│ Code generation encountered an error: @@ -99,11 +103,11 @@ fun toEnglish(x) = 0 then "z" true then "t" //│ ╔══[ERROR] The case when this is false is not handled: == (x,) (true,) -//│ ║ l.98: if x == -//│ ║ ^^^^ -//│ ║ l.99: 0 then "z" -//│ ║ ^^^^^^^^^^^^^^ -//│ ║ l.100: true then "t" +//│ ║ l.102: if x == +//│ ║ ^^^^ +//│ ║ l.103: 0 then "z" +//│ ║ ^^^^^^^^^^^^^^ +//│ ║ l.104: true then "t" //│ ╙── ^^^^^^^^ //│ toEnglish: anything -> error //│ Code generation encountered an error: @@ -116,11 +120,11 @@ fun toEnglish(x) = 1 then "o" 0 then "z" //│ ╔══[ERROR] The case when this is false is not handled: == (x,) (0,) -//│ ║ l.115: if x == +//│ ║ l.119: if x == //│ ║ ^^^^ -//│ ║ l.116: 1 then "o" +//│ ║ l.120: 1 then "o" //│ ║ ^^^^^^^^^^^^^^ -//│ ║ l.117: 0 then "z" +//│ ║ l.121: 0 then "z" //│ ╙── ^^^^^^ //│ toEnglish: anything -> error //│ Code generation encountered an error: @@ -140,23 +144,23 @@ fun toEnglish(x) = if x == else 1 //│ ╔══[PARSE ERROR] Unexpected indented block in expression position -//│ ║ l.141: else 1 +//│ ║ l.145: else 1 //│ ╙── ^^^^ //│ ╔══[PARSE ERROR] Unexpected end of indented block; an expression was expected here -//│ ║ l.141: else 1 +//│ ║ l.145: else 1 //│ ╙── ^ //│ ╔══[PARSE ERROR] Expected 'then'/'else' clause; found operator application instead -//│ ║ l.140: if x == +//│ ║ l.144: if x == //│ ║ ^^^^ -//│ ║ l.141: else 1 +//│ ║ l.145: else 1 //│ ║ ^^^^ //│ ╟── Note: 'if' expression started here: -//│ ║ l.140: if x == +//│ ║ l.144: if x == //│ ╙── ^^ //│ ╔══[ERROR] The case when this is false is not handled: == (x,) (undefined,) -//│ ║ l.140: if x == +//│ ║ l.144: if x == //│ ║ ^^^^ -//│ ║ l.141: else 1 +//│ ║ l.145: else 1 //│ ╙── ^^^^ //│ toEnglish: anything -> error //│ Code generation encountered an error: diff --git a/shared/src/test/diff/ucs/SplitAroundOp.mls b/shared/src/test/diff/ucs/SplitAroundOp.mls index 1ff84e7f3..86dfebdc7 100644 --- a/shared/src/test/diff/ucs/SplitAroundOp.mls +++ b/shared/src/test/diff/ucs/SplitAroundOp.mls @@ -1,119 +1,51 @@ :NewParser +:NewDefs -// Why? Can the type of `x` be `number | string`? -:e fun f(x, b) = if x - == + === 0 and b then "n0" 1 and b then "n1" 2 then "n2" - == + === "0" then "s0" "1" then "s1" "2" then "s2" else ":p" -//│ ╔══[ERROR] Type mismatch in operator application: -//│ ║ l.6: if x -//│ ║ ^ -//│ ║ l.7: == -//│ ║ ^^^^^^ -//│ ║ l.8: 0 and b then "n0" -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.9: 1 and b then "n1" -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.10: 2 then "n2" -//│ ║ ^^^^^^^^^^^^^^^^^ -//│ ║ l.11: == -//│ ║ ^^^^^^ -//│ ║ l.12: "0" then "s0" -//│ ║ ^^^^^^^^^ -//│ ╟── string literal of type `"0"` is not an instance of type `number` -//│ ║ l.12: "0" then "s0" -//│ ╙── ^^^ -//│ ╔══[ERROR] Type mismatch in operator application: -//│ ║ l.6: if x -//│ ║ ^ -//│ ║ l.7: == -//│ ║ ^^^^^^ -//│ ║ l.8: 0 and b then "n0" -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.9: 1 and b then "n1" -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.10: 2 then "n2" -//│ ║ ^^^^^^^^^^^^^^^^^ -//│ ║ l.11: == -//│ ║ ^^^^^^ -//│ ║ l.12: "0" then "s0" -//│ ║ ^^^^^^^^^^^^^^^^^^^ -//│ ║ l.13: "1" then "s1" -//│ ║ ^^^^^^^^^ -//│ ╟── string literal of type `"1"` is not an instance of type `number` -//│ ║ l.13: "1" then "s1" -//│ ╙── ^^^ -//│ ╔══[ERROR] Type mismatch in operator application: -//│ ║ l.6: if x -//│ ║ ^ -//│ ║ l.7: == -//│ ║ ^^^^^^ -//│ ║ l.8: 0 and b then "n0" -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.9: 1 and b then "n1" -//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ -//│ ║ l.10: 2 then "n2" -//│ ║ ^^^^^^^^^^^^^^^^^ -//│ ║ l.11: == -//│ ║ ^^^^^^ -//│ ║ l.12: "0" then "s0" -//│ ║ ^^^^^^^^^^^^^^^^^^^ -//│ ║ l.13: "1" then "s1" -//│ ║ ^^^^^^^^^^^^^^^^^^^ -//│ ║ l.14: "2" then "s2" -//│ ║ ^^^^^^^^^ -//│ ╟── string literal of type `"2"` is not an instance of type `number` -//│ ║ l.14: "2" then "s2" -//│ ╙── ^^^ -//│ f: (number, anything,) -> (":p" | "n0" | "n1" | "n2" | "s0" | "s1" | "s2") -//│ = [Function: f] +//│ fun f: (Eql["0" | "1" | "2" | 0 | 1 | 2], anything,) -> (":p" | "n0" | "n1" | "n2" | "s0" | "s1" | "s2") fun f(x, y, a, b) = - if x == 0 + if x === 0 and - y == 0 then "x, y" - a == 0 then "x, a" - b == 0 then "x, b" + y === 0 then "x, y" + a === 0 then "x, a" + b === 0 then "x, b" else "nah" -//│ f: (number, number, number, number,) -> ("nah" | "x, a" | "x, b" | "x, y") -//│ = [Function: f1] +//│ fun f: (Eql[0], Eql[0], Eql[0], Eql[0],) -> ("nah" | "x, a" | "x, b" | "x, y") class A() class B() -//│ Defined class A -//│ Defined class B -//│ A: () -> A -//│ = [Function: A1] -//│ B: () -> B -//│ = [Function: B1] +//│ class A() +//│ class B() fun f(x) = if x is A() then 0 B() then 1 -//│ f: (A | B) -> (0 | 1) -//│ = [Function: f2] +//│ fun f: (A | B) -> (0 | 1) // It fails because we interpret == as a constructor. :e :ge if x is A() - == 0 then 0 + === 0 then 0 > 0 then 1 < 0 then 2 -//│ ╔══[ERROR] Cannot find operator `==` in the context -//│ ║ l.111: == 0 then 0 -//│ ╙── ^^ -//│ res: error +//│ ╔══[ERROR] Cannot find operator `===` in the context +//│ ║ l.43: === 0 then 0 +//│ ╙── ^^^ +//│ error //│ Code generation encountered an error: //│ if expression was not desugared diff --git a/shared/src/test/diff/ucs/SplitOps.mls b/shared/src/test/diff/ucs/SplitOps.mls index 37943bd5a..6aee50f35 100644 --- a/shared/src/test/diff/ucs/SplitOps.mls +++ b/shared/src/test/diff/ucs/SplitOps.mls @@ -86,7 +86,6 @@ class C() //│ C: () -> C //│ = [Function: C1] -// * FIXME: the missing otherwise is for `a == 0` :p :e :ge @@ -97,9 +96,11 @@ fun f(a, b, c) = //│ Parsed: fun f = (a, b, c,) => {if a ‹· == (and (and (0,) (is (b,) (B (),),),) (is (c,) (C (),),)) then 0›}; //│ Desugared: rec def f: (a, b, c,) => {if a ‹· == (and (and (0,) (is (b,) (B (),),),) (is (c,) (C (),),)) then 0›} //│ AST: Def(true, f, Lam(Tup(_: Var(a), _: Var(b), _: Var(c)), Blk(...)), true) -//│ ╔══[ERROR] The case when this is false is not handled: and (is (b,) (B (),),) (is (c,) (C (),),) -//│ ║ l.95: == 0 and b is B() and c is C() then 0 -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[ERROR] The case when this is false is not handled: == (a,) (0,) +//│ ║ l.93: if a +//│ ║ ^ +//│ ║ l.94: == 0 and b is B() and c is C() then 0 +//│ ╙── ^^^^^^^^ //│ f: (anything, anything, anything,) -> error //│ Code generation encountered an error: //│ if expression was not desugared diff --git a/shared/src/test/diff/ucs/Tree.mls b/shared/src/test/diff/ucs/Tree.mls new file mode 100644 index 000000000..c4af73ef9 --- /dev/null +++ b/shared/src/test/diff/ucs/Tree.mls @@ -0,0 +1,42 @@ +:NewDefs + +type Option[A] = Some[A] | None +class Some[A](value: A) +module None +//│ type Option[A] = Some[A] | None +//│ class Some[A](value: A) +//│ module None() + +type Tree[A] = Node[A] | Empty +module Empty +class Node[A](value: int, left: Tree[A], right: Tree[A]) +//│ type Tree[A] = Node[A] | Empty +//│ module Empty() +//│ class Node[A](value: int, left: Tree[A], right: Tree[A]) + +fun find(t, v) = if t is + Node(v', l, r) and + v < v' then find(l, v) + v > v' then find(r, v) + _ then Some(v) + Empty then None +//│ fun find: forall 'A 'A0. (Empty | Node['A], number & 'A0,) -> (None | Some['A0]) + +fun insert(t, v) = if t is + Node(v', l, r) and + v < v' then Node(v', insert(l, v), r) + v > v' then Node(v', l, insert(r, v)) + _ then t + Empty then Node(v, Empty, Empty) +//│ fun insert: forall 'A. (Empty | Node['A], int,) -> Node['A] + +find(Empty, 0) +find(Node(0, Empty, Empty), 0) +find(Node(1, Empty, Empty), 0) +//│ None | Some[0] +//│ res +//│ = None { class: [class None] } +//│ res +//│ = Some {} +//│ res +//│ = None { class: [class None] } diff --git a/shared/src/test/diff/ucs/TrivialIf.mls b/shared/src/test/diff/ucs/TrivialIf.mls index 57c922662..674a24768 100644 --- a/shared/src/test/diff/ucs/TrivialIf.mls +++ b/shared/src/test/diff/ucs/TrivialIf.mls @@ -43,9 +43,9 @@ map(None(), inc) :e fun f(a, b) = if a and b then 0 -//│ ╔══[ERROR] The case when this is false is not handled: b +//│ ╔══[ERROR] The case when this is false is not handled: a //│ ║ l.45: fun f(a, b) = if a and b then 0 -//│ ╙── ^ +//│ ╙── ^ //│ f: (anything, anything,) -> error :e @@ -55,4 +55,26 @@ fun f(x, y) = //│ ╔══[ERROR] The case when this is false is not handled: == (x,) (+ (y,) (7,),) //│ ║ l.54: else if x == y + 7 then 0 //│ ╙── ^^^^^^^^^^ -//│ f: (number, int,) -> (0 | error) +//│ f: (anything, anything,) -> error + + + +if 42 is n then n + 1 +//│ res: int + +:w +if 42 is n then n + 1 else 0 +//│ ╔══[WARNING] Found a redundant else branch +//│ ║ l.66: if 42 is n then n + 1 else 0 +//│ ╙── ^ +//│ res: int + + +if Some(42) is Some(n) then n + 1 +//│ res: int + +// TODO warn useless `else` +if Some(42) is Some(n) then n + 1 else 0 +//│ res: int + + diff --git a/shared/src/test/diff/ucs/WeirdIf.mls b/shared/src/test/diff/ucs/WeirdIf.mls index 708280aa3..bda4d5a85 100644 --- a/shared/src/test/diff/ucs/WeirdIf.mls +++ b/shared/src/test/diff/ucs/WeirdIf.mls @@ -6,24 +6,44 @@ if _ then 0 else 0 else 1 -//│ ╔══[WARNING] duplicated branch -//│ ╙── -//│ ╔══[WARNING] duplicated branch -//│ ╙── +//│ ╔══[WARNING] Found a duplicated branch +//│ ╟── This branch +//│ ║ l.7: else 0 +//│ ║ ^ +//│ ╟── is subsumed by the branch here. +//│ ║ l.6: _ then 0 +//│ ╙── ^ +//│ ╔══[WARNING] Found a duplicated branch +//│ ╟── This branch +//│ ║ l.8: else 1 +//│ ║ ^ +//│ ╟── is subsumed by the branch here. +//│ ║ l.6: _ then 0 +//│ ╙── ^ //│ res: 0 //│ = 0 :w if else 0 else 1 -//│ ╔══[WARNING] duplicated branch -//│ ╙── +//│ ╔══[WARNING] Found a duplicated branch +//│ ╟── This branch +//│ ║ l.27: if else 0 else 1 +//│ ║ ^ +//│ ╟── is subsumed by the branch here. +//│ ║ l.27: if else 0 else 1 +//│ ╙── ^ //│ res: 0 //│ = 0 :w fun f(x) = if x is else 0 else 1 -//│ ╔══[WARNING] duplicated branch -//│ ╙── +//│ ╔══[WARNING] Found a duplicated branch +//│ ╟── This branch +//│ ║ l.39: fun f(x) = if x is else 0 else 1 +//│ ║ ^ +//│ ╟── is subsumed by the branch here. +//│ ║ l.39: fun f(x) = if x is else 0 else 1 +//│ ╙── ^ //│ f: anything -> 0 //│ = [Function: f] @@ -36,7 +56,7 @@ fun f(x) = if x is else 0 if true then 0 //│ ╔══[ERROR] The case when this is false is not handled: true -//│ ║ l.36: if true +//│ ║ l.56: if true //│ ╙── ^^^^ //│ res: error //│ Code generation encountered an error: @@ -50,23 +70,23 @@ fun f(x) = if x == else "bruh" //│ ╔══[PARSE ERROR] Unexpected indented block in expression position -//│ ║ l.51: else "bruh" +//│ ║ l.71: else "bruh" //│ ╙── ^^^^ //│ ╔══[PARSE ERROR] Unexpected end of indented block; an expression was expected here -//│ ║ l.51: else "bruh" +//│ ║ l.71: else "bruh" //│ ╙── ^ //│ ╔══[PARSE ERROR] Expected 'then'/'else' clause; found operator application instead -//│ ║ l.50: if x == +//│ ║ l.70: if x == //│ ║ ^^^^ -//│ ║ l.51: else "bruh" +//│ ║ l.71: else "bruh" //│ ║ ^^^^ //│ ╟── Note: 'if' expression started here: -//│ ║ l.50: if x == +//│ ║ l.70: if x == //│ ╙── ^^ //│ ╔══[ERROR] The case when this is false is not handled: == (x,) (undefined,) -//│ ║ l.50: if x == +//│ ║ l.70: if x == //│ ║ ^^^^ -//│ ║ l.51: else "bruh" +//│ ║ l.71: else "bruh" //│ ╙── ^^^^ //│ f: anything -> error //│ Code generation encountered an error: @@ -79,20 +99,16 @@ fun f(x) = //│ f: anything -> "bruh" //│ = [Function: f3] -:e -:ge -// Hmmmmmm, this one is valid but how to get it work? fun boolToStr(x) = if x is true then "yah" false then "nah" -//│ ╔══[ERROR] The case when this is false is not handled: == (x,) (false,) -//│ ║ l.86: if x is -//│ ║ ^^^^ -//│ ║ l.87: true then "yah" -//│ ║ ^^^^^^^^^^^^^^^^^^^ -//│ ║ l.88: false then "nah" -//│ ╙── ^^^^^^^^^ -//│ boolToStr: anything -> error -//│ Code generation encountered an error: -//│ if expression was not desugared +//│ boolToStr: bool -> ("nah" | "yah") +//│ = [Function: boolToStr] + +boolToStr of true +boolToStr of false +//│ res: "nah" | "yah" +//│ = 'yah' +//│ res: "nah" | "yah" +//│ = 'nah' diff --git a/shared/src/test/diff/ucs/Wildcard.mls b/shared/src/test/diff/ucs/Wildcard.mls new file mode 100644 index 000000000..470632a4c --- /dev/null +++ b/shared/src/test/diff/ucs/Wildcard.mls @@ -0,0 +1,261 @@ +:NewParser +:NewDefs + +type Option[T] = None | Some[T] +module None +class Some[T](value: T) +//│ type Option[T] = Some[T] | None +//│ module None() +//│ class Some[T](value: T) + +type Either[A, B] = Left[A] | Right[B] +class Left[A](leftValue: A) +class Right[B](rightValue: B) +//│ type Either[A, B] = Left[A] | Right[B] +//│ class Left[A](leftValue: A) +//│ class Right[B](rightValue: B) + +fun w1(x, e_0, e_1) = + if x is + Left(None) then "Left of None" + Right(None) then "Right of None" + _ and e_0 is y_0 and x is + Left(Some(lv)) then concat("Left of Some of ")(toString(lv)) + _ and e_1 is y_1 and x is + Right(Some(rv)) then concat("Right of Some of ")(toString(rv)) +//│ fun w1: (Left[None | Some[anything]] | Right[None | Some[anything]], anything, anything,) -> string + +w1(Left(None), "a", "b") +w1(Right(None), "a", "b") +w1(Left(Some(0)), "a", "b") +w1(Right(Some(0)), "a", "b") +//│ string +//│ res +//│ = 'Left of None' +//│ res +//│ = 'Right of None' +//│ res +//│ = 'Left of Some of 0' +//│ res +//│ = 'Right of Some of 0' + +fun w2(x, p) = + if x is + Some then 1 + _ and p(x) then 2 + None then 3 + _ then 4 +//│ fun w2: forall 'a. (None | Some[anything] | 'a & ~#None & ~#Some, (None | 'a) -> anything,) -> (1 | 2 | 3 | 4) + +w2(Some(0), x => true) +w2(None, x => true) +w2(None, x => false) +w2(0, x => false) +//│ 1 | 2 | 3 | 4 +//│ res +//│ = 1 +//│ res +//│ = 2 +//│ res +//│ = 3 +//│ res +//│ = 4 + +fun w3(x, p) = if x is + _ and p(x) then "r1" + Some(xv) then concat("r2: ")(toString(xv)) + None then "r3" + _ then "r4" +//│ fun w3: forall 'a. (None | Some[anything] | 'a & ~#None & ~#Some, (None | Some[nothing] | 'a) -> anything,) -> string + +// Expect "r1" +w3(0, _ => true) +w3(None, _ => true) +w3(Some(0), _ => true) +//│ string +//│ res +//│ = 'r1' +//│ res +//│ = 'r1' +//│ res +//│ = 'r1' + +// Expect "r2" +w3(Some(0), _ => false) +//│ string +//│ res +//│ = 'r2: 0' + +// Expect "r3" +w3(None, _ => false) +//│ string +//│ res +//│ = 'r3' + +// Expect "r4" +w3(0, _ => false) +//│ string +//│ res +//│ = 'r4' + +:w +// Decision paths: +// + «tmp2 @ f (x,) is any => 0 +// + => 1 +fun w3_1(x, f) = + if f(x) is _ then 0 else 1 +//│ ╔══[WARNING] Found a redundant else branch +//│ ║ l.106: if f(x) is _ then 0 else 1 +//│ ╙── ^ +//│ fun w3_1: forall 'a. ('a, 'a -> anything,) -> 0 + +w3_1(0, _ => true) +w3_1(0, _ => false) +//│ 0 +//│ res +//│ = 0 +//│ res +//│ = 0 + +:w +fun w3_1_1(x, f) = + if f(x) is a then a else 0 +//│ ╔══[WARNING] Found a redundant else branch +//│ ║ l.122: if f(x) is a then a else 0 +//│ ╙── ^ +//│ fun w3_1_1: forall 'a 'b. ('a, 'a -> 'b,) -> 'b + +w3_1_1(0, x => x) +w3_1_1(0, x => x + 1) +//│ int +//│ res +//│ = 0 +//│ res +//│ = 1 + +// Decision paths: +// + «a = x» and «p (x,)» => "r1" +// + «x is Some» => concat ("r2: ",) (toString (xv,),) +// + «x is None» => "r3" +fun w4(x, p) = if x is + a and p(x) then "r1" + Some(xv) then concat("r2: ")(toString(xv)) + None then "r3" + _ then "r4" +//│ fun w4: forall 'a. (None | Some[anything] | 'a & ~#None & ~#Some, (None | Some[nothing] | 'a) -> anything,) -> string + + +// Expect "r1" +w4(0, _ => true) +w4(None, _ => true) +w4(Some(0), _ => true) +//│ string +//│ res +//│ = 'r1' +//│ res +//│ = 'r1' +//│ res +//│ = 'r1' + +// Expect "r2" +w4(Some(0), _ => false) +//│ string +//│ res +//│ = 'r2: 0' + +// Expect "r3" +w4(None, _ => false) +//│ string +//│ res +//│ = 'r3' + +// Expect "r4" +w4(0, _ => false) +//│ string +//│ res +//│ = 'r4' + +class Alpha +class Beta +class Gamma +class Delta +//│ class Alpha() +//│ class Beta() +//│ class Gamma() +//│ class Delta() + +// This should generate only one case expression instead of a chain of case +// expressions. DO check the desugared term! +fun w5(y) = + if y is + Alpha then "alpha" + _ and y is + Beta then "beta" + _ and y is + Gamma then "gamma" + _ and y is + Delta then "delta" + _ then "unknown" +//│ fun w5: anything -> ("alpha" | "beta" | "delta" | "gamma" | "unknown") + +w5(0) +w5(Alpha()) +w5(Beta()) +w5(Gamma()) +w5(Delta()) +//│ "alpha" | "beta" | "delta" | "gamma" | "unknown" +//│ res +//│ = 'unknown' +//│ res +//│ = 'alpha' +//│ res +//│ = 'beta' +//│ res +//│ = 'gamma' +//│ res +//│ = 'delta' + +fun w6(x, y) = + if x is + _ and y is + Some(z) then z + None then 0 + else x +//│ fun w6: forall 'value. ('value, Some['value] | ~Some[anything],) -> (0 | 'value) + +w6("42", Some(42)) +w6("42", None) +w6("42", "42") +//│ "42" | 0 +//│ res +//│ = 42 +//│ res +//│ = 0 +//│ res +//│ = '42' + +// FIXME +// Should report warnings. +fun w7(x, f) = + if x is + _ and f(x) is + Some(v) then v + None then x + Left(x) then x + 1 + Right(x) then x + 2 +//│ fun w7: forall 'a 'value. (Left[int] | Right[int] | 'a & ~#Left & ~#Right, 'a -> (None | Some['value]),) -> (int | 'value | 'a) + +// The results are wrong: +w7(Left(99), _ => Some(0)) // => 0 +w7(Left(99), _ => None) // => Left(99) +w7(Right(99), _ => Some(0)) // => 0 +w7(Right(99), _ => None) // => Right(99) +//│ int +//│ res +//│ = 100 +//│ res +//│ = 100 +//│ res +//│ = 101 +//│ res +//│ = 101 diff --git a/shared/src/test/diff/ucs/zipWith.mls b/shared/src/test/diff/ucs/zipWith.mls new file mode 100644 index 000000000..61edd35e1 --- /dev/null +++ b/shared/src/test/diff/ucs/zipWith.mls @@ -0,0 +1,207 @@ +:NewDefs + + + +:escape +let nothing: nothing +//│ let nothing: nothing +//│ nothing +//│ = + +module None { + fun value = nothing +} +class Some[out A](value: A) +//│ module None() { +//│ fun value: nothing +//│ } +//│ class Some[A](value: A) + +type List[out A] = Cons[A] | Nil +module Nil { + fun toArray = [] +} +class Cons[out A](head: A, tail: List[A]) { + fun toArray: Array[anything] + fun toArray = [head, tail.toArray] +} +//│ type List[A] = Cons[A] | Nil +//│ module Nil() { +//│ fun toArray: () +//│ } +//│ class Cons[A](head: A, tail: List[A]) { +//│ fun toArray: Array[anything] +//│ } + +fun pairup(x, y) = [x, y] +//│ fun pairup: forall 'a 'b. ('a, 'b,) -> ('a, 'b,) + + + +// FIXME parsing +fun zipWith_wrong(f, xs, ys) = + if xs is Cons(x, xs) + and ys is Cons(y, ys) + and zipWith_wrong(f, xs, ys) is Some(tail) + then Some(Cons(f(x, y), tail)) + else None +//│ ╔══[PARSE ERROR] Expected 'then'/'else' clause; found operator application followed by newline instead +//│ ║ l.43: if xs is Cons(x, xs) +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ║ l.44: and ys is Cons(y, ys) +//│ ║ ^^ +//│ ╟── Note: 'if' expression started here: +//│ ║ l.43: if xs is Cons(x, xs) +//│ ╙── ^^ +//│ ╔══[PARSE ERROR] Unexpected operator in expression position +//│ ║ l.44: and ys is Cons(y, ys) +//│ ╙── ^^^ +//│ ╔══[PARSE ERROR] Unexpected operator in expression position +//│ ║ l.45: and zipWith_wrong(f, xs, ys) is Some(tail) +//│ ╙── ^^^ +//│ ╔══[PARSE ERROR] Expected an expression; found a 'then'/'else' clause instead +//│ ║ l.43: if xs is Cons(x, xs) +//│ ║ ^^^^^^^^^^^^^^^^^ +//│ ║ l.44: and ys is Cons(y, ys) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.45: and zipWith_wrong(f, xs, ys) is Some(tail) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.46: then Some(Cons(f(x, y), tail)) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ║ l.47: else None +//│ ╙── ^^^^^^^^^^^ +//│ fun zipWith_wrong: (anything, anything, anything,) -> undefined + + +// FIXME parsing +fun zipWith_wrong(f, xs, ys) = + if xs is Cons(x, xs) + and ys is Cons(y, ys) + and zipWith_wrong(f, xs, ys) is Some(tail) then Some(Cons(f(x, y), tail)) + else None +//│ ╔══[ERROR] illegal pattern +//│ ║ l.78: if xs is Cons(x, xs) +//│ ║ ^^^^^^^^^^^ +//│ ║ l.79: and ys is Cons(y, ys) +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ fun zipWith_wrong: (anything, anything, anything,) -> error +//│ Code generation encountered an error: +//│ if expression was not desugared + + +fun zipWith_wrong(f, xs, ys) = + if xs is Cons(x, xs) + and ys is Cons(y, ys) and zipWith_wrong(f, xs, ys) is Some(tail) then Some(Cons(f(x, y), tail)) + else None +//│ fun zipWith_wrong: forall 'head 'head0 'A. (('head, 'head0,) -> 'A, Cons['head] | ~Cons[anything], Cons['head0] | ~Cons[anything],) -> (None | Some[Cons['A]]) + +// * Notice the result is wrong (duh) +zipWith_wrong(pairup, Nil, Nil) +//│ None | Some[Cons[(nothing, nothing,)]] +//│ res +//│ = None { class: [class None] } + + + +fun zipWith(f, xs, ys) = + if xs is + Cons(x, xs) and ys is Cons(y, ys) and zipWith(f, xs, ys) is Some(tail) then Some(Cons(f(x, y), tail)) + Nil and ys is Nil then Some(Nil) + else None +//│ fun zipWith: forall 'head 'head0 'A. (('head, 'head0,) -> 'A, Cons['head] | ~Cons[anything], Cons['head0] | ~Cons[anything],) -> (None | Some[Cons['A] | Nil]) + +zipWith(pairup, Cons(0, Nil), Cons("0", Nil)).value.toArray +//│ Array[anything] +//│ res +//│ = [ [ 0, '0' ], [] ] + + +fun zipWith(f, xs, ys) = + if xs is + Cons(x, xs) and ys is Cons(y, ys) and zipWith(f, xs, ys) is Some(tail) then Some(Cons(f(x, y), tail)) + Nil and ys is Nil then Some(Nil) + else None +//│ fun zipWith: forall 'head 'head0 'A. (('head, 'head0,) -> 'A, Cons['head] | ~Cons[anything], Cons['head0] | ~Cons[anything],) -> (None | Some[Cons['A] | Nil]) + +zipWith(pairup, Cons(0, Nil), Cons("0", Nil)).value.toArray +//│ Array[anything] +//│ res +//│ = [ [ 0, '0' ], [] ] + + +fun zipWith(f, xs, ys) = + if xs is Cons(x, xs) and ys is Cons(y, ys) and zipWith(f, xs, ys) is Some(tail) then Some(Cons(f(x, y), tail)) + else if xs is Nil and ys is Nil then Some(Nil) + else None +//│ fun zipWith: forall 'head 'head0 'A. (('head, 'head0,) -> 'A, Cons['head] | ~Cons[anything], Cons['head0] | ~Cons[anything],) -> (None | Some[Cons['A] | Nil]) + +zipWith(pairup, Cons(0, Nil), Cons("0", Nil)).value.toArray +//│ Array[anything] +//│ res +//│ = [ [ 0, '0' ], [] ] + + +fun zipWith(f, xs, ys) = + if xs is Cons(x, xs) and ys is Cons(y, ys) then + if zipWith(f, xs, ys) is Some(tail) then Some(Cons(f(x, y), tail)) + else None + else if xs is Nil and ys is Nil then Some(Nil) + else None +//│ fun zipWith: forall 'head 'head0 'A. (('head, 'head0,) -> 'A, Cons['head] | ~Cons[anything], Cons['head0] | ~Cons[anything],) -> (None | Some[Cons['A] | Nil]) + +zipWith(pairup, Cons(0, Nil), Cons("0", Nil)).value.toArray +//│ Array[anything] +//│ res +//│ = [ [ 0, '0' ], [] ] + + +fun zipWith(f, xs, ys) = + if xs is + Cons(x, xs) then + if ys is + Cons(y, ys) then + if zipWith(f, xs, ys) is + Some(tail) then Some(Cons(f(x, y), tail)) + None then None + Nil then None + Nil then + if ys is Nil then Some(Nil) else None +//│ fun zipWith: forall 'head 'head0 'A. (('head, 'head0,) -> 'A, Cons['head] | Nil, Cons['head0] | Nil,) -> (None | Some[Cons['A] | Nil]) + +zipWith(pairup, Nil, Nil).value.toArray +//│ Array[anything] +//│ res +//│ = [] + +:re +zipWith(pairup, Nil, Cons(0, Nil)).value.toArray +//│ Array[anything] +//│ res +//│ Runtime error: +//│ ReferenceError: nothing is not defined + +zipWith(pairup, Cons(0, Nil), Cons("0", Nil)).value.toArray +//│ Array[anything] +//│ res +//│ = [ [ 0, '0' ], [] ] + +zipWith(pairup, Cons(0, Cons(1, Nil)), Cons("0", Cons("1", Nil))).value.toArray +//│ Array[anything] +//│ res +//│ = [ [ 0, '0' ], [ [ 1, '1' ], [] ] ] + + + +fun zipWith_wrong2(f, xs, ys) = + if xs is Cons(x, xs) and ys is Cons(y, ys) and zipWith_wrong2(f, xs, ys) is Some(tail) then Cons(Some(f(x, y)), tail) + else if xs is Nil and ys is Nil then Some(Nil) + else None +//│ fun zipWith_wrong2: forall 'head 'head0 'A. (('head, 'head0,) -> 'A, Cons['head] | ~Cons[anything], Cons['head0] | ~Cons[anything],) -> (Cons[Some['A]] | None | Some[Nil]) + +// * No type error! The definition and use are well-typed... +zipWith_wrong2(pairup, Cons(0, Cons(1, Nil)), Cons("0", Cons("1", Nil))) +//│ Cons[Some[(0 | 1, "0" | "1",)]] | None | Some[Nil] +//│ res +//│ = None { class: [class None] } + +