Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements and fixes for UCS #157

Merged
merged 58 commits into from
May 22, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
d93e0d6
Add examples from meeting
LPTK Feb 21, 2023
d76b9b0
Add `ne` and `eq` to the JS polyfill
chengluyu Mar 11, 2023
4f0f870
No longer report inexhaustiveness when there is a default branch
chengluyu Mar 11, 2023
f7f0418
Add an incomplete implementation of untyped lambda calculus
chengluyu Mar 11, 2023
03a28b8
Support alias clauses and complete the ULC test cases
chengluyu Mar 12, 2023
d30b9c9
Update test files due to previous commits
chengluyu Mar 12, 2023
2a7a5a7
Add incomplete SLTC and built-in string comparison functions
chengluyu Mar 12, 2023
3d1018b
Fix missing interleaved let bindings in nested branches
chengluyu Mar 12, 2023
67513c1
Fix unhygienically extracting positional class parameters
chengluyu Mar 12, 2023
84b9ba7
Merge remote-tracking branch 'lptk-fork/new-definition-typing' into u…
chengluyu Mar 12, 2023
e4dbbf7
Use `:NewDefs` in lambda calculus test cases
chengluyu Mar 13, 2023
3c9f9a1
Gather lambda calculus test files
chengluyu Mar 13, 2023
a9c80d8
Add incomplete JSON test cases and support escape chars in `Lexer`
chengluyu Mar 14, 2023
e7a9dd5
Merge remote-tracking branch 'lptk-fork/new-definition-typing' into u…
chengluyu Mar 14, 2023
556eb26
Append test file updates
chengluyu Mar 14, 2023
32c70de
No duplication when the else branch is taken at least once
chengluyu Mar 14, 2023
c8ddd6e
Add a more essential test case
chengluyu Mar 14, 2023
aab8d27
Remove syntax highlight overrides for .mls files
chengluyu Mar 15, 2023
fef2c0c
No longer translate literal patterns to `==` applications
chengluyu Mar 15, 2023
8ac73ee
Categorize let bindings so no longer missing alias patterns
chengluyu Mar 15, 2023
c8707c5
Improve warnings of duplicated branches
chengluyu Mar 15, 2023
73dee4c
Improve the debug display of `MutCaseOf`
chengluyu Mar 15, 2023
e0f426a
Fix duplicated interleaved bindings and missing alias bindings
chengluyu Mar 16, 2023
1784ecb
Gather exhaustiveness information on the fly
chengluyu Mar 16, 2023
09a0fb9
Support basic `else if` composition
chengluyu Mar 16, 2023
ae1ca13
Support wildcard followed by more clauses
chengluyu Mar 17, 2023
281bb1d
Ignore .DS_Store :^(
chengluyu Mar 17, 2023
6283351
Add more `_ and t`-like test cases
chengluyu Mar 17, 2023
14344e6
Support wildcard followed by clauses by copying existing wildcard
chengluyu Mar 17, 2023
9997ddf
Fix making module names (e.g. `None`) as positional binding names
chengluyu Mar 17, 2023
f5ba540
Use the new definition typing on `SplitAroundOp.mls`
chengluyu Mar 17, 2023
f036769
Add an interesting case in `LitUCS.mls`.
chengluyu Mar 17, 2023
d972ee3
Add an interleaved let example revealing a code generation problem
chengluyu Mar 17, 2023
a2a5180
Improve the warning message for duplicated branches
chengluyu Mar 17, 2023
15d0c57
Fix all warnings
chengluyu Mar 17, 2023
24c4386
Fix warnings and make CI happy :-P
chengluyu Mar 17, 2023
1bd5ee7
Mark expected errors and warnings
chengluyu Mar 17, 2023
572e618
Fix the parameter error in the previous CI fix
chengluyu Mar 17, 2023
4963caa
Remove two `FIXME` in `MultiwayIf.mls`
chengluyu Mar 18, 2023
8b40fec
Fix temporary name generation in `JSBackend`
chengluyu Mar 18, 2023
9eb14bd
Always check the exhaustiveness of the outer if-then-else first
chengluyu Mar 18, 2023
e227ec3
Add test cases in the poster and gather parse failures
chengluyu Mar 22, 2023
9a8e6aa
Support subclass refinement and preemptive wildcard branches
chengluyu Mar 23, 2023
522f8fb
Consume `MatchAny` and try to merge inexhaustive branches
chengluyu Mar 23, 2023
a35a416
Add new UCS test cases
LPTK Mar 23, 2023
96cdb81
Add zipWith tests
LPTK Mar 26, 2023
73dcda1
Add a couple small test cases
LPTK Apr 4, 2023
b7a7cd5
Support negative integers
chengluyu Apr 26, 2023
d168eb5
Add some examples of unhygienic let bindings
chengluyu Apr 30, 2023
f6021f4
Merge remote-tracking branch 'lptk-fork/new-definition-typing' into u…
chengluyu May 5, 2023
2ff4c7b
Fix `undefined` is a keyword
chengluyu May 5, 2023
ee1ab19
Merge branch 'new-definition-typing' into ucs-paper
chengluyu May 5, 2023
66d41f2
Fix comments
chengluyu May 12, 2023
34b1864
Update CI: run on all PRs
LPTK May 17, 2023
70fd442
Fix minor problems mentioned in PR comments
chengluyu May 18, 2023
b89fa16
Fix the warning in UCS source files
chengluyu May 18, 2023
14a232a
Try to fix more warnings
chengluyu May 18, 2023
17d05de
Describe `FIXME` without actual errors
chengluyu May 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions shared/src/main/scala/mlscript/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1047,9 +1047,7 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool)
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(_))
val caseTree = buildCaseTree(desugarIf(body, fallback))
checkExhaustive(caseTree, N)(summarizePatterns(caseTree), ctx, raise)
val desugared = constructTerm(caseTree)
println(s"Desugared term: ${desugared.print(false)}")
Expand Down
24 changes: 4 additions & 20 deletions shared/src/main/scala/mlscript/ucs/Clause.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ abstract class Clause {
val locations: Ls[Loc]

protected final def bindingsToString: String =
(if (bindings.isEmpty) "" else " with " + Clause.showBindings(bindings))
if (bindings.isEmpty) "" else " with " + (bindings match {
case Nil => ""
case bindings => bindings.map(_.name.name).mkString("(", ", ", ")")
})
}

object Clause {
Expand Down Expand Up @@ -64,23 +67,4 @@ object Clause {
) extends Clause {
override def toString(): String = s"«$name = $term»" + bindingsToString
}

def showBindings(bindings: Ls[LetBinding]): Str =
bindings match {
case Nil => ""
case bindings => bindings.map(_.name.name).mkString("(", ", ", ")")
}

def showClauses(clauses: Iterable[Clause]): Str = clauses.mkString("", " and ", "")

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"
})
}
}
}
71 changes: 58 additions & 13 deletions shared/src/main/scala/mlscript/ucs/Desugarer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,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.
Expand Down Expand Up @@ -363,7 +363,7 @@ class Desugarer extends TypeDefs { self: Typer =>
val (patternPart, extraTestOpt) = separatePattern(patTest)
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 =>
Expand Down Expand Up @@ -517,9 +517,19 @@ class Desugarer extends TypeDefs { self: Typer =>
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(clauses, trailingBindings) -> term =>
printlnUCS("+ " + clauses.mkString("", " and ", "") + {
(if (trailingBindings.isEmpty) "" else " ") +
(trailingBindings match {
case Nil => ""
case bindings => bindings.map(_.name.name).mkString("(", ", ", ")")
}) +
s" => $term"
})
}
branches.toList
}(r => s"[desugarIf] produces ${r.size} branch(es)")
}(r => s"[desugarIf] produces ${r.size} ${"path".pluralize(r.size)}")

import MutCaseOf.{MutCase, IfThenElse, Match, MissingCase, Consequent}

Expand Down Expand Up @@ -718,22 +728,36 @@ class Desugarer extends TypeDefs { self: Typer =>
m match {
case Consequent(term) => term
case Match(scrutinee, branches, wildcard) =>
printlnUCS("• Owned let bindings")
val ownedBindings = m.getBindings.iterator.filterNot {
_.kind === LetBinding.Kind.InterleavedLet
}.toList
if (ownedBindings.isEmpty)
printlnUCS(" * <No bindings>")
else
ownedBindings.foreach { case LetBinding(kind, _, name, value) =>
printlnUCS(s" * ($kind) $name = $value")
}
// 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(" * <No interleaved bindings>")
else
interleavedBindings.foreach { case LetBinding(_, _, name, value) =>
printlnUCS(s" * $name = $value")
}
val cases = traceUCS("• For each case branch"){
rec2(branches.toList)(defs, scrutinee, wildcard)
}(_ => "• End for each")
val resultTerm = scrutinee.local match {
case N => CaseOf(scrutinee.term, cases)
case S(aliasVar) => Let(false, aliasVar, scrutinee.term, CaseOf(aliasVar, cases))
}
// Collect interleaved let bindings from case branches.
val bindings = branches.iterator.flatMap(_.consequent.getBindings).filter {
_.kind === LetBinding.Kind.InterleavedLet
}.toList
printlnUCS("• Collect interleaved let bindings from case branches")
bindings.foreach { case LetBinding(_, _, name, value) =>
printlnUCS(s" - $name = $value")
}
mkBindings(bindings, resultTerm, defs)
mkBindings(ownedBindings, mkBindings(interleavedBindings, resultTerm, defs), defs)
case MissingCase =>
import Message.MessageContext
throw new DesugaringException(msg"missing a default branch", N)
Expand Down Expand Up @@ -790,4 +814,25 @@ class Desugarer extends TypeDefs { self: Typer =>
}
rec(scrutinee.reference, fields)
}

protected def buildCaseTree
(paths: Ls[Conjunction -> Term])
(implicit raise: Diagnostic => Unit)
: 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)
traceUCS("*** Updated tree ***") {
MutCaseOf.show(root).foreach(printlnUCS(_))
}()
}
root
}
}(_ => "[buildCaseTree]")
}
12 changes: 9 additions & 3 deletions shared/src/main/scala/mlscript/ucs/LetBinding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@ object LetBinding {
sealed abstract class Kind

object Kind {
case object ScrutineeAlias extends Kind
case object FieldExtraction extends Kind
case object InterleavedLet extends 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"
}
}
}

Expand Down
95 changes: 54 additions & 41 deletions shared/src/main/scala/mlscript/ucs/MutCaseOf.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,44 +43,50 @@ 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(_.name.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"
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, "")
rec(consequent, indent + 1)
case MutCase.Constructor(Var(className) -> fields, consequent) =>
lines += s"$baseIndent case $className =>"
fields.foreach { case (field, Var(alias)) =>
lines += s"$baseIndent let $alias = .$field"
// Show pattern bindings.
lines += s"$baseIndent [pattern $alias = ${scrutinee.reference}.$field]"
}
rec(consequent, indent + 2, "")
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<missing case>"
bindingLines.foreach { lines += baseIndent + _ }
lines += s"$baseIndent<missing case>"
}
}
rec(t, 0, "")
rec(t, 0)
lines.toList
}

Expand Down Expand Up @@ -217,7 +223,16 @@ object MutCaseOf {
})"
}

def merge(branch: Conjunction -> Term)(implicit raise: Diagnostic => Unit): Unit = {
def merge(originalBranch: Conjunction -> Term)(implicit raise: Diagnostic => Unit): 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
branch._1.separate(scrutinee) match {
// No conditions against the same scrutinee.
case N =>
Expand Down Expand Up @@ -331,53 +346,51 @@ object MutCaseOf {
def mergeDefault(bindings: Ls[LetBinding], default: Term)(implicit raise: Diagnostic => Unit): Int = 0
}

private def buildFirst(conjunction: Conjunction, term: Term): MutCaseOf = {
def buildFirst(conjunction: Conjunction, term: Term): 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 MatchLiteral(scrutinee, literal) =>
val branches = Buffer(
MutCase.Literal(literal, rec(realTail)).withLocation(literal.toLoc)
MutCase.Literal(literal, consequentTree.withBindings(afterHeadBindings)).withLocation(literal.toLoc)
)
Match(scrutinee, branches, N)
case BooleanTest(test) => IfThenElse(test, rec(realTail), MissingCase)
.withBindings(beforeHeadBindings)
case BooleanTest(test) =>
IfThenElse(test, consequentTree, MissingCase)
.withBindings(beforeHeadBindings)
.withBindings(afterHeadBindings)
case MatchClass(scrutinee, className, fields) =>
val branches = Buffer(
MutCase.Constructor(className -> Buffer.from(fields), rec(realTail))
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.Constructor(Var(s"Tuple#$arity") -> Buffer.from(fields), rec(realTail))
MutCase.Constructor(Var(s"Tuple#$arity") -> Buffer.from(fields), consequentTree.withBindings(afterHeadBindings))
.withLocations(head.locations)
)
Match(scrutinee, branches, N)
Match(scrutinee, branches, N).withBindings(beforeHeadBindings)
case Binding(name, term, isField) =>
val kind = if (isField)
LetBinding.Kind.FieldExtraction
else
LetBinding.Kind.ScrutineeAlias
rec(realTail).withBindings(LetBinding(kind, false, name, term) :: Nil)
}).withBindings(head.bindings)
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
}
}
}
Loading