diff --git a/.gitignore b/.gitignore index 4a989b4c7d..b61da16b72 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ project/Dependencies.scala project/metals.sbt **.worksheet.sc .DS_Store +.idea/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..8f925e079d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "request": "attach", + "name": "gqd debugger", + // "projectName": "mlscript", + "hostName": "localhost", + "port": "8000" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 90f3019795..57d8e96949 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,5 +14,5 @@ "strings": "off" } }, - "files.autoSave": "off" + "files.autoSave": "afterDelay" } diff --git a/compiler/shared/main/scala/mlscript/compiler/ClassLifter.scala b/compiler/shared/main/scala/mlscript/compiler/ClassLifter.scala index b6aa7bcce3..0fbb40f0ee 100644 --- a/compiler/shared/main/scala/mlscript/compiler/ClassLifter.scala +++ b/compiler/shared/main/scala/mlscript/compiler/ClassLifter.scala @@ -120,7 +120,7 @@ class ClassLifter(logDebugMsg: Boolean = false) { selPath2Term(l.map(x => genParName(x.name)).updated(0, "this").reverse, v) }) } - private def toFldsEle(trm: Term): (Option[Var], Fld) = (None, Fld(FldFlags(false, false, false), trm)) + private def toFldsEle(trm: Term): (Option[Var], Fld) = (None, Fld(FldFlags(false, false, false, false), trm)) def getSupClsInfoByTerm(parentTerm: Term): (List[TypeName], List[(Var, Fld)]) = parentTerm match{ case Var(nm) => List(TypeName(nm)) -> Nil @@ -498,7 +498,7 @@ class ClassLifter(logDebugMsg: Boolean = false) { private def liftTypeField(target: Field)(using ctx: LocalContext, cache: ClassCache, globFuncs: Map[Var, (Var, LocalContext)], outer: Option[ClassInfoCache]): (Field, LocalContext) = { val (inT, iCtx) = target.in.map(liftType).unzip val (outT, oCtx) = liftType(target.out) - Field(inT, outT) -> (iCtx.getOrElse(emptyCtx) ++ oCtx) + Field(inT, outT, false) -> (iCtx.getOrElse(emptyCtx) ++ oCtx) } private def liftType(target: Type)(using ctx: LocalContext, cache: ClassCache, globFuncs: Map[Var, (Var, LocalContext)], outer: Option[ClassInfoCache]): (Type, LocalContext) = target match{ diff --git a/shared/src/main/scala/mlscript/ConstraintSolver.scala b/shared/src/main/scala/mlscript/ConstraintSolver.scala index 5d60ac1ff5..c61613dc10 100644 --- a/shared/src/main/scala/mlscript/ConstraintSolver.scala +++ b/shared/src/main/scala/mlscript/ConstraintSolver.scala @@ -99,7 +99,7 @@ class ConstraintSolver extends NormalForms { self: Typer => Nil) val ty = d.typeSignature S( - if (d.fd.isMut) FieldType(S(ty), ty)(d.prov) + if (d.fd.isMut) FieldType(S(ty), ty, false)(d.prov) else ty.toUpper(d.prov) ) case S(p: NuParam) => @@ -512,6 +512,7 @@ class ConstraintSolver extends NormalForms { self: Typer => case (LhsRefined(S(Without(b, _)), _, _, _), RhsBot) => rec(b, BotType, true) case (LhsTop, _) | (LhsRefined(N, empty(), RecordType(Nil), empty()), _) => // TODO ^ actually get rid of LhsTop and RhsBot...? (might make constraint solving slower) + println(s"REPORT ERROR 515") reportError() case (LhsRefined(_, ts, _, trs), RhsBases(pts, _, _)) if ts.exists(pts.contains) => () @@ -530,6 +531,7 @@ class ConstraintSolver extends NormalForms { self: Typer => case (_, RhsBases(_, _, trs)) if trs.nonEmpty => die case (_, RhsBot) | (_, RhsBases(Nil, N, _)) => + println("REPORT ERRROR LINE 534") reportError() case (LhsRefined(S(f0@FunctionType(l0, r0)), ts, r, _) @@ -554,7 +556,10 @@ class ConstraintSolver extends NormalForms { self: Typer => lit match { case _: IntLit | _: DecLit => rec(fldTy.lb.getOrElse(TopType), DecType, false) case _: StrLit => rec(fldTy.lb.getOrElse(TopType), StrType, false) - case _: UnitLit => reportError() + case _: UnitLit => { + println(s"REPORT ERROR 560") + reportError() + } } // * This deals with the implicit Eql type member for user-defined classes. @@ -604,7 +609,10 @@ class ConstraintSolver extends NormalForms { self: Typer => case S(Without(b, ns)) => if (ns(n)) rec(b, RhsBases(ots, N, trs).toType(), true) else rec(b, done_rs.toType(), true) - case _ => reportError() + case _ => { + println("REPORT ERROR 613") + reportError() + } } } case (LhsRefined(N, ts, r, trs), RhsBases(pts, N, trs2)) => @@ -614,9 +622,15 @@ class ConstraintSolver extends NormalForms { self: Typer => case _ => Nil }.contains(p.id))) println(s"OK $ts <: $pts") - else reportError() + else { + println(s"REPORT ERROR 624") + reportError() + } case (LhsRefined(N, ts, r, _), RhsBases(pts, S(L(_: FunctionType | _: ArrayBase)), _)) => - reportError() + { + println(s"REPORT ERROR 627") + reportError() + } case (LhsRefined(S(b: TupleType), ts, r, _), RhsBases(pts, S(L(ty: TupleType)), _)) if b.fields.size === ty.fields.size => (b.fields.unzip._2 lazyZip ty.fields.unzip._2).foreach { (l, r) => @@ -952,7 +966,7 @@ class ConstraintSolver extends NormalForms { self: Typer => } - case (TupleType(fs0), TupleType(fs1)) if fs0.size === fs1.size => // TODO generalize (coerce compatible tuples) + case (t0 @ TupleType(fs0), t1 @ TupleType(fs1)) if t0.isLengthCompatibleWith(t1) => { fs0.lazyZip(fs1).foreach { case ((ln, l), (rn, r)) => ln.foreach { ln => rn.foreach { rn => if (ln =/= rn) err( @@ -962,6 +976,7 @@ class ConstraintSolver extends NormalForms { self: Typer => recLb(r, l) rec(l.ub, r.ub, false) } + } case (t: ArrayBase, a: ArrayType) => recLb(a.inner, t.inner) rec(t.inner.ub, a.inner.ub, false) @@ -984,7 +999,10 @@ class ConstraintSolver extends NormalForms { self: Typer => case (RecordType(fs0), RecordType(fs1)) => fs1.foreach { case (n1, t1) => fs0.find(_._1 === n1).fold { - reportError() + { + println(s"REPORTERROR 933") + reportError() + } } { case (n0, t0) => recLb(t1, t0) rec(t0.ub, t1.ub, false) @@ -1040,7 +1058,10 @@ class ConstraintSolver extends NormalForms { self: Typer => if (tr1.mayHaveTransitiveSelfType) rec(tr1.expand, tr2.expand, true) else (tr1.mkClsTag, tr2.mkClsTag) match { case (S(tag1), S(tag2)) if !(tag1 <:< tag2) => - reportError() + { + println(s"REPORTERROR 992") + reportError() + } case _ => rec(tr1.expand, tr2.expand, true) } @@ -1139,14 +1160,17 @@ class ConstraintSolver extends NormalForms { self: Typer => goToWork(lhs, rhs) case (_: ClassTag | _: TraitTag, _: TraitTag) => goToWork(lhs, rhs) - case _ => reportError() + case _ => { + println(s"REPORTERROR 1093") + reportError() + } }} }}() def reportError(failureOpt: Opt[Message] = N)(implicit cctx: ConCtx, ctx: Ctx): Unit = { val lhs = cctx._1.head val rhs = cctx._2.head - + println(s"CONSTRAINT FAILURE: $lhs <: $rhs") // println(s"CTX: ${cctx.map(_.map(lr => s"${lr._1} <: ${lr._2} [${lr._1.prov}] [${lr._2.prov}]"))}") @@ -1309,7 +1333,6 @@ class ConstraintSolver extends NormalForms { self: Typer => case _ => doesntMatch(rhs) }) - val mismatchMessage = msg"Type mismatch in ${prov.desc}:" -> show(prov.loco) :: ( msg"${printProv(lhsProv)} `${lhs.expPos}` $failure" @@ -1321,6 +1344,8 @@ class ConstraintSolver extends NormalForms { self: Typer => msg"but it flows into ${l.prov.desc}$expTyMsg" -> show(l.prov.loco) :: Nil }.toList.flatten + println(s"dbg [ConstraintSolver]: ${flowHint}") + val constraintProvenanceHints = mk_constraintProvenanceHints var first = true diff --git a/shared/src/main/scala/mlscript/JSBackend.scala b/shared/src/main/scala/mlscript/JSBackend.scala index 98a22c12fb..9abe4bfbfc 100644 --- a/shared/src/main/scala/mlscript/JSBackend.scala +++ b/shared/src/main/scala/mlscript/JSBackend.scala @@ -1202,10 +1202,10 @@ abstract class JSBackend { def prepare(nme: Str, fs: Ls[Opt[Var] -> Fld], pars: Ls[Term], unit: TypingUnit) = { val params = fs.map { - case (S(nme), Fld(FldFlags(mut, spec, _), trm)) => + case (S(nme), Fld(FldFlags(mut, spec, opt, _), trm)) => val ty = tt(trm) - nme -> Field(if (mut) S(ty) else N, ty) - case (N, Fld(FldFlags(mut, spec, _), nme: Var)) => nme -> Field(if (mut) S(Bot) else N, Top) + nme -> Field(if (mut) S(ty) else N, ty, opt) + case (N, Fld(FldFlags(mut, spec, opt, _), nme: Var)) => nme -> Field(if (mut) S(Bot) else N, Top, opt) case _ => die } val publicCtors = fs.filter { diff --git a/shared/src/main/scala/mlscript/MLParser.scala b/shared/src/main/scala/mlscript/MLParser.scala index d05e7a6cbc..d5683f8316 100644 --- a/shared/src/main/scala/mlscript/MLParser.scala +++ b/shared/src/main/scala/mlscript/MLParser.scala @@ -31,7 +31,7 @@ class MLParser(origin: Origin, indent: Int = 0, recordLocations: Bool = true) { } def toParamsTy(t: Type): Tuple = t match { case t: Tuple => t - case _ => Tuple((N, Field(None, t)) :: Nil) + case _ => Tuple((N, Field(None, t, false)) :: Nil) } def letter[p: P] = P( lowercase | uppercase ) @@ -69,14 +69,14 @@ class MLParser(origin: Origin, indent: Int = 0, recordLocations: Bool = true) { def parens[p: P]: P[Term] = locate(P( "(" ~/ parenCell.rep(0, ",") ~ ",".!.? ~ ")" ).map { case (Seq(Right(t -> false)), N) => Bra(false, t) - case (Seq(Right(t -> true)), N) => Tup(N -> Fld(FldFlags(true, false, false), t) :: Nil) // ? single tuple with mutable + case (Seq(Right(t -> true)), N) => Tup(N -> Fld(FldFlags(true, false, false, false), t) :: Nil) // ? single tuple with mutable case (ts, _) => if (ts.forall(_.isRight)) Tup(ts.iterator.map { - case R(f) => N -> Fld(FldFlags(f._2, false, false), f._1) + case R(f) => N -> Fld(FldFlags(f._2, false, false, false), f._1) case _ => die // left unreachable }.toList) else Splc(ts.map { - case R((v, m)) => R(Fld(FldFlags(m, false, false), v)) + case R((v, m)) => R(Fld(FldFlags(m, false, false, false), v)) case L(spl) => L(spl) }.toList) }) @@ -106,8 +106,8 @@ class MLParser(origin: Origin, indent: Int = 0, recordLocations: Bool = true) { "{" ~/ (kw("mut").!.? ~ fieldName ~ "=" ~ term map L.apply).|(kw("mut").!.? ~ variable map R.apply).rep(sep = ";" | ",") ~ "}" ).map { fs => Rcd(fs.map{ - case L((mut, v, t)) => v -> Fld(FldFlags(mut.isDefined, false, false), t) - case R(mut -> id) => id -> Fld(FldFlags(mut.isDefined, false, false), id) }.toList)}) + case L((mut, v, t)) => v -> Fld(FldFlags(mut.isDefined, false, false, false), t) + case R(mut -> id) => id -> Fld(FldFlags(mut.isDefined, false, false, false), id) }.toList)}) def fun[p: P]: P[Term] = locate(P( kw("fun") ~/ term ~ "->" ~ term ).map(nb => Lam(toParams(nb._1), nb._2))) @@ -274,8 +274,8 @@ class MLParser(origin: Origin, indent: Int = 0, recordLocations: Bool = true) { def rcd[p: P]: P[Record] = locate(P( "{" ~/ ( kw("mut").!.? ~ fieldName ~ ":" ~ ty).rep(sep = ";") ~ "}" ) .map(_.toList.map { - case (None, v, t) => v -> Field(None, t) - case (Some(_), v, t) => v -> Field(Some(t), t) + case (None, v, t) => v -> Field(None, t, false) + case (Some(_), v, t) => v -> Field(Some(t), t, false) } pipe Record)) def parTyCell[p: P]: P[Either[Type, (Type, Boolean)]] = (("..." | kw("mut")).!.? ~ ty). map { @@ -289,14 +289,14 @@ class MLParser(origin: Origin, indent: Int = 0, recordLocations: Bool = true) { case (fs, _) => if (fs.forall(_._2.isRight)) Tuple(fs.map { - case (l, Right(t -> false)) => l -> Field(None, t) - case (l, Right(t -> true)) => l -> Field(Some(t), t) + case (l, Right(t -> false)) => l -> Field(None, t, false) + case (l, Right(t -> true)) => l -> Field(Some(t), t, false) case _ => ??? // unreachable }) else Splice(fs.map{ _._2 match { case L(l) => L(l) - case R(r -> true) => R(Field(Some(r), r)) - case R(r -> false) => R(Field(None, r)) + case R(r -> true) => R(Field(Some(r), r, false)) + case R(r -> false) => R(Field(None, r, false)) } }) }) def litTy[p: P]: P[Type] = P( lit.map(l => Literal(l).withLocOf(l)) ) @@ -327,7 +327,7 @@ class MLParser(origin: Origin, indent: Int = 0, recordLocations: Bool = true) { } def tup = parTy.map { case t: Tuple => t - case t => Tuple(N -> Field(N, t) :: Nil) + case t => Tuple(N -> Field(N, t, false) :: Nil) } P((ctorName ~ tup.?).map { case (id, S(body: Tuple)) => diff --git a/shared/src/main/scala/mlscript/NewLexer.scala b/shared/src/main/scala/mlscript/NewLexer.scala index a42ffb7cad..99522f39c2 100644 --- a/shared/src/main/scala/mlscript/NewLexer.scala +++ b/shared/src/main/scala/mlscript/NewLexer.scala @@ -51,10 +51,11 @@ class NewLexer(origin: Origin, raise: Diagnostic => Unit, dbg: Bool) { ";", // ",", "#", - "`" + "`", // ".", // "<", // ">", + "?:" ) private val isAlphaOp = Set( @@ -396,9 +397,12 @@ class NewLexer(origin: Origin, raise: Diagnostic => Unit, dbg: Bool) { lex(k, ind, next(k, SELECT(name))) } else lex(j, ind, next(j, if (isSymKeyword.contains(n)) KEYWORD(n) else IDENT(n, true))) + } + else { + // println(s"dbg [NewLexer.scala]: n: $n, j: $j, isSymKeyword: ${isSymKeyword.contains(n)}") + // else go(j, i f (isSymKeyword.contains(n)) KEYWORD(n) else IDENT(n, true)) + 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 (lit, j) = num(i) // go(j, LITVAL(IntLit(BigInt(str)))) diff --git a/shared/src/main/scala/mlscript/NewParser.scala b/shared/src/main/scala/mlscript/NewParser.scala index 2b26020097..93dd217c4b 100644 --- a/shared/src/main/scala/mlscript/NewParser.scala +++ b/shared/src/main/scala/mlscript/NewParser.scala @@ -254,7 +254,7 @@ abstract class NewParser(origin: Origin, tokens: Ls[Stroken -> Loc], newDefs: Bo final def toParamsTy(t: Type): Tuple = t match { case t: Tuple => t - case _ => Tuple((N, Field(None, t)) :: Nil) + case _ => Tuple((N, Field(None, t, false)) :: Nil) } final def typ(prec: Int = 0)(implicit fe: FoundErr, l: Line): Type = mkType(expr(prec)) @@ -479,7 +479,7 @@ abstract class NewParser(origin: Origin, tokens: Ls[Stroken -> Loc], newDefs: Bo case (br @ BRACKETS(Angle | Square, toks), loc) :: _ => consume val ts = rec(toks, S(br.innerLoc), br.describe).concludeWith(_.argsMaybeIndented()).map { - case (N, Fld(FldFlags(false, false, _), v @ Var(nme))) => + case (N, Fld(FldFlags(false, false, false, _), v @ Var(nme))) => TypeName(nme).withLocOf(v) case _ => ??? } @@ -490,11 +490,11 @@ abstract class NewParser(origin: Origin, tokens: Ls[Stroken -> Loc], newDefs: Bo case (br @ BRACKETS(Round, Spaces(cons, (KEYWORD("override"), ovLoc) :: rest)), loc) :: rest2 => resetCur(BRACKETS(Round, rest)(br.innerLoc) -> loc :: rest2) funParams match { - case ps @ Tup(N -> Fld(FldFlags(false, false, gen), pat) :: Nil) :: Nil => + case ps @ Tup(N -> Fld(FldFlags(false, false, false, gen), pat) :: Nil) :: Nil => val fv = freshVar - (Tup(N -> Fld(FldFlags(false, false, gen), fv) :: Nil) :: Nil, S( + (Tup(N -> Fld(FldFlags(false, false, false, gen), fv) :: Nil) :: Nil, S( (body: Term) => If(IfOpApp(fv, Var("is"), IfThen(pat, body)), S( - App(Sel(Super().withLoc(S(ovLoc)), v), Tup(N -> Fld(FldFlags(false, false, gen), fv) :: Nil)) + App(Sel(Super().withLoc(S(ovLoc)), v), Tup(N -> Fld(FldFlags(false, false, false, gen), fv) :: Nil)) )) )) case r => @@ -744,7 +744,7 @@ abstract class NewParser(origin: Origin, tokens: Ls[Stroken -> Loc], newDefs: Bo case (br @ BRACKETS(bk @ Round, toks), loc) :: _ => consume val res = rec(toks, S(br.innerLoc), br.describe).concludeWith(_.argsMaybeIndented()) match { - case (N, Fld(FldFlags(false, false, _), elt)) :: Nil => Quoted(Bra(false, elt)).withLoc(S(loc ++ elt.toLoc)) + case (N, Fld(FldFlags(false, false, false, _), elt)) :: Nil => Quoted(Bra(false, elt)).withLoc(S(loc ++ elt.toLoc)) case _ => unsupportedQuote(S(loc)) } exprCont(res, prec, allowNewlines = false) @@ -790,7 +790,7 @@ abstract class NewParser(origin: Origin, tokens: Ls[Stroken -> Loc], newDefs: Bo msg"Record field should have a name" -> fld.value.toLoc :: Nil)) Var("") -> fld })) - case (Round, (N, Fld(FldFlags(false, false, _), elt)) :: Nil) => + case (Round, (N, Fld(FldFlags(false, false, false, _), elt)) :: Nil) => Bra(false, elt) case (Round, _) => yeetSpaces match { @@ -1057,7 +1057,7 @@ abstract class NewParser(origin: Origin, tokens: Ls[Stroken -> Loc], newDefs: Bo .concludeWith(_.expr(0, allowSpace = true)) val newAcc = Subs(acc, idx).withLoc(S(l0 ++ l1 ++ idx.toLoc)) exprCont(newAcc, prec, allowNewlines) - case (IDENT(opStr, true), l0) :: _ if /* isInfix(opStr) && */ opPrec(opStr)._1 > prec => + case (IDENT(opStr, true), l0) :: _ if /* isInfix(opStr) && */ opPrec(opStr)._1 > prec && opStr =/= "?" => consume val v = Var(opStr).withLoc(S(l0)) yeetSpaces match { @@ -1177,7 +1177,7 @@ abstract class NewParser(origin: Origin, tokens: Ls[Stroken -> Loc], newDefs: Bo val as = rec(toks, S(br.innerLoc), br.describe).concludeWith(_.argsMaybeIndented()) // val res = TyApp(acc, as.map(_.mapSecond.to)) val res = TyApp(acc, as.map { - case (N, Fld(FldFlags(false, false, _), trm)) => trm.toType match { + case (N, Fld(FldFlags(false, false, false, _), trm)) => trm.toType match { case L(d) => raise(d); Top // TODO better case R(ty) => ty } @@ -1408,7 +1408,7 @@ abstract class NewParser(origin: Origin, tokens: Ls[Stroken -> Loc], newDefs: Bo case _ => // val blck = block - + val argVal = yeetSpaces match { case (KEYWORD("val"), l0) :: _ => consume @@ -1427,21 +1427,34 @@ abstract class NewParser(origin: Origin, tokens: Ls[Stroken -> Loc], newDefs: Bo S(l0) case _ => N } - val argName = yeetSpaces match { + val (argName, isOpt) = yeetSpaces match { + case (IDENT(idStr, false), l0) :: (KEYWORD("?:"), l1) :: _ => + // println(s"dbg [NewParser.scala]: idStr: ${idStr}, l0: ${l0}, keyword: ?:, l1: ${l1}") + consume + consume + (S(Var(idStr).withLoc(S(l0))), true) case (IDENT(idStr, false), l0) :: (KEYWORD(":"), _) :: _ => // TODO: | ... consume consume - S(Var(idStr).withLoc(S(l0))) + (S(Var(idStr).withLoc(S(l0))), false) case (LITVAL(IntLit(i)), l0) :: (KEYWORD(":"), _) :: _ => // TODO: | ... consume consume - S(Var(i.toString).withLoc(S(l0))) - case _ => N + (S(Var(i.toString).withLoc(S(l0))), false) + case _ => (N, false) + } + val body = exprOrIf(prec, true, anns) + // println(s"dbg [NewParser.scala]: argName: ${argName}, argOpt: ${isOpt}") + val isOpt2 = cur match { + case (IDENT("?", true), l0) :: _ => + // println(s"dbg [NewParser.scala]: cur: ${cur}, isOpt2: true") + consume + true + case _ => + false } - // val e = expr(NoElsePrec) -> argMut.isDefined - val e = exprOrIf(prec, true, anns).map(Fld(FldFlags(argMut.isDefined, argSpec.isDefined, argVal.isDefined), _)) - + val e = body.map(Fld(FldFlags(argMut.isDefined, argSpec.isDefined, isOpt || isOpt2, argVal.isDefined), _)) def mkSeq = if (seqAcc.isEmpty) argName -> e else e match { case L(_) => ??? case R(Fld(flags, res)) => @@ -1465,7 +1478,7 @@ abstract class NewParser(origin: Origin, tokens: Ls[Stroken -> Loc], newDefs: Bo } e match { case L(_) => ??? - case R(Fld(FldFlags(false, false, _), res)) => + case R(Fld(FldFlags(false, false, false, _), res)) => argsOrIf(acc, res :: seqAcc, allowNewlines) case R(_) => ??? } diff --git a/shared/src/main/scala/mlscript/NuTypeDefs.scala b/shared/src/main/scala/mlscript/NuTypeDefs.scala index 45a19986aa..206704053e 100644 --- a/shared/src/main/scala/mlscript/NuTypeDefs.scala +++ b/shared/src/main/scala/mlscript/NuTypeDefs.scala @@ -175,7 +175,7 @@ class NuTypeDefs extends ConstraintSolver { self: Typer => // TODO dedup with the one in TypedNuCls lazy val virtualMembers: Map[Str, NuMember] = members ++ tparams.map { case (nme @ TypeName(name), tv, _) => - td.nme.name+"#"+name -> NuParam(nme, FieldType(S(tv), tv)(provTODO), isPublic = true)(level) + td.nme.name+"#"+name -> NuParam(nme, FieldType(S(tv), tv, false)(provTODO), isPublic = true)(level) } ++ parentTP def freshenAbove(lim: Int, rigidify: Bool) @@ -240,7 +240,7 @@ class NuTypeDefs extends ConstraintSolver { self: Typer => /** Includes class-name-coded type parameter fields. */ lazy val virtualMembers: Map[Str, NuMember] = members ++ tparams.map { case (nme @ TypeName(name), tv, _) => - td.nme.name+"#"+name -> NuParam(nme, FieldType(S(tv), tv)(provTODO), isPublic = true)(level) + td.nme.name+"#"+name -> NuParam(nme, FieldType(S(tv), tv, false)(provTODO), isPublic = true)(level) } ++ parentTP // TODO @@ -357,7 +357,7 @@ class NuTypeDefs extends ConstraintSolver { self: Typer => lazy val virtualMembers: Map[Str, NuMember] = members ++ tparams.map { case (nme @ TypeName(name), tv, _) => - td.nme.name+"#"+name -> NuParam(nme, FieldType(S(tv), tv)(provTODO), isPublic = false)(level) + td.nme.name+"#"+name -> NuParam(nme, FieldType(S(tv), tv, false)(provTODO), isPublic = false)(level) } def freshenAbove(lim: Int, rigidify: Bool) @@ -780,8 +780,8 @@ class NuTypeDefs extends ConstraintSolver { self: Typer => mxn.params.size.toString} parameter(s); got ${parArgs.size.toString}", Loc(v :: parArgs.unzip._2)) val paramMems = mxn.params.lazyZip(parArgs).flatMap { - case (nme -> p, _ -> Fld(FldFlags(mut, spec, get), a)) => // TODO factor this with code for classes: - assert(!mut && !spec && !get, "TODO") // TODO check mut, spec, get + case (nme -> p, _ -> Fld(FldFlags(mut, spec, opt, get), a)) => // TODO factor this with code for classes: + assert(!mut && !spec && !opt && !get, "TODO") // TODO check mut, spec, get implicit val genLambdas: GenLambdas = true val a_ty = typeTerm(a) p.lb.foreach(constrain(_, a_ty)) @@ -789,8 +789,8 @@ class NuTypeDefs extends ConstraintSolver { self: Typer => val isPublic = mxn.members(nme.name).isPublic val fty = if (p.lb.isDefined) // * We don't refine the field type when it's mutable as that could lead to muable updates being rejected - FieldType(p.lb, p.ub)(provTODO) - else FieldType(p.lb, a_ty)(provTODO) + FieldType(p.lb, p.ub, p.opt)(provTODO) + else FieldType(p.lb, a_ty, p.opt)(provTODO) Option.when(isPublic)(NuParam(nme, fty, isPublic = isPublic)(lvl)) } @@ -843,8 +843,8 @@ class NuTypeDefs extends ConstraintSolver { self: Typer => case S(ps) => checkArgsNum(ps.size) ps.lazyZip(parArgs).map { - case (nme -> p_ty, _ -> Fld(FldFlags(mut, spec, get), a)) => - assert(!mut && !spec && !get, "TODO") // TODO check mut, spec, get + case (nme -> p_ty, _ -> Fld(FldFlags(mut, spec, opt, get), a)) => + assert(!mut && !spec && !opt && !get, "TODO") // TODO check mut, spec, get val a_ty = typeTerm(a) constrain(a_ty, p_ty) } @@ -853,16 +853,16 @@ class NuTypeDefs extends ConstraintSolver { self: Typer => case S(ps) => checkArgsNum(ps.size) ps.lazyZip(parArgs).flatMap { - case (nme -> p, _ -> Fld(FldFlags(mut, spec, get), a)) => - assert(!mut && !spec && !get, "TODO") // TODO check mut, spec, get + case (nme -> p, _ -> Fld(FldFlags(mut, spec, opt, get), a)) => + assert(!mut && !spec && !opt && !get, "TODO") // TODO check mut, spec, get val a_ty = typeTerm(a) p.lb.foreach(constrain(_, a_ty)) constrain(a_ty, p.ub) val isPublic = cls.members(nme.name).isPublic val fty = if (p.lb.isDefined) // * We don't refine the field type when it's mutable as that could lead to muable updates being rejected - FieldType(p.lb, p.ub)(provTODO) - else FieldType(p.lb, a_ty)(provTODO) + FieldType(p.lb, p.ub, opt)(provTODO) + else FieldType(p.lb, a_ty, opt)(provTODO) Option.when(isPublic)(NuParam(nme, fty, isPublic = isPublic)(lvl)) } case N => @@ -963,16 +963,16 @@ class NuTypeDefs extends ConstraintSolver { self: Typer => decl match { case td: NuTypeDef => td.params.map(_.fields.flatMap { - case (S(nme), Fld(FldFlags(mut, spec, getter), value)) => + case (S(nme), Fld(FldFlags(mut, spec, opt, getter), value)) => assert(!mut && !spec, "TODO") // TODO val tpe = value.toTypeRaise val ty = typeType(tpe) - nme -> FieldType(N, ty)(provTODO) :: Nil - case (N, Fld(FldFlags(mut, spec, getter), nme: Var)) => + nme -> FieldType(N, ty, opt)(provTODO) :: Nil + case (N, Fld(FldFlags(mut, spec, opt, getter), nme: Var)) => assert(!mut && !spec, "TODO") // TODO // nme -> FieldType(N, freshVar(ttp(nme), N, S(nme.name)))(provTODO) nme -> FieldType(N, err(msg"${td.kind.str.capitalize} parameters currently need type annotations", - nme.toLoc))(provTODO) :: Nil + nme.toLoc), opt)(provTODO) :: Nil case (_, fld) => err(msg"Unsupported field specification", fld.toLoc) Nil @@ -1060,7 +1060,7 @@ class NuTypeDefs extends ConstraintSolver { self: Typer => // -- privateFields -- inheritedFields /* parameters can be overridden by inherited fields/methods */ ) ++ typedSignatures.iterator.map(fd_ty => fd_ty._1.nme -> ( - if (fd_ty._1.isMut) FieldType(S(fd_ty._2), fd_ty._2)( + if (fd_ty._1.isMut) FieldType(S(fd_ty._2), fd_ty._2, false)( fd_ty._2.prov) // FIXME prov else fd_ty._2.toUpper(noProv) )) ++ @@ -1555,7 +1555,7 @@ class NuTypeDefs extends ConstraintSolver { self: Typer => val tparamMems = tparams.map { case (tp, tv, vi) => // TODO use vi val fldNme = td.nme.name + "#" + tp.name val skol = SkolemTag(tv)(tv.prov) - NuParam(TypeName(fldNme).withLocOf(tp), FieldType(S(skol), skol)(tv.prov), isPublic = true)(lvl) + NuParam(TypeName(fldNme).withLocOf(tp), FieldType(S(skol), skol, false)(tv.prov), isPublic = true)(lvl) } val tparamFields = tparamMems.map(p => p.nme.toVar -> p.ty) assert(!typedParams.exists(_.keys.exists(tparamFields.keys.toSet)), ???) @@ -1757,7 +1757,7 @@ class NuTypeDefs extends ConstraintSolver { self: Typer => def getterError(loco: Opt[Loc]) = err(msg"Cannot use `val` in constructor parameters", loco) val res = ps.fields.map { - case (S(nme), Fld(FldFlags(mut, spec, getter), value)) => + case (S(nme), Fld(FldFlags(mut, spec, opt, getter), value)) => assert(!mut && !spec, "TODO") // TODO if (getter) // TODO we could support this to some extent @@ -1768,7 +1768,7 @@ class NuTypeDefs extends ConstraintSolver { self: Typer => nme -> ty case _ => ??? } - case (N, Fld(FldFlags(mut, spec, getter), nme: Var)) => + case (N, Fld(FldFlags(mut, spec, opt, getter), nme: Var)) => assert(!mut && !spec, "TODO") // TODO if (getter) getterError(nme.toLoc) @@ -1933,7 +1933,7 @@ class NuTypeDefs extends ConstraintSolver { self: Typer => tv }) freshened += _tv -> tv - rawName+"#"+tn.name -> NuParam(tn, FieldType(S(tv), tv)(provTODO), isPublic = true)(ctx.lvl) + rawName+"#"+tn.name -> NuParam(tn, FieldType(S(tv), tv, false)(provTODO), isPublic = true)(ctx.lvl) } freshened -> parTP.toMap diff --git a/shared/src/main/scala/mlscript/TypeDefs.scala b/shared/src/main/scala/mlscript/TypeDefs.scala index a33177486b..2eacbbee64 100644 --- a/shared/src/main/scala/mlscript/TypeDefs.scala +++ b/shared/src/main/scala/mlscript/TypeDefs.scala @@ -333,7 +333,7 @@ class TypeDefs extends NuTypeDefs { Typer: Typer => // * This is not actually necessary for soundness // * (if they aren't, the object type just won't be instantiable), // * but will help report inheritance errors earlier (see test BadInherit2). - case (nme, FieldType(S(lb), ub)) => constrain(lb, ub) + case (nme, FieldType(S(lb), ub, _)) => constrain(lb, ub) case _ => () } (decls -- defns) match { @@ -343,7 +343,7 @@ class TypeDefs extends NuTypeDefs { Typer: Typer => case _ => val fields = fieldsOf(td.bodyTy, paramTags = true) val tparamTags = td.tparamsargs.map { case (tp, tv) => - tparamField(td.nme, tp) -> FieldType(Some(tv), tv)(tv.prov) } + tparamField(td.nme, tp) -> FieldType(Some(tv), tv, false)(tv.prov) } val ctor = k match { case Cls => val nomTag = clsNameToNomTag(td)(originProv(td.nme.toLoc, "class", td.nme.name), ctx) @@ -354,7 +354,7 @@ class TypeDefs extends NuTypeDefs { Typer: Typer => S(f._1.name.drop(f._1.name.indexOf('#') + 1)) // strip any "...#" prefix )(1).tap(_.upperBounds ::= f._2.ub) f._1 -> ( - if (f._2.lb.isDefined) FieldType(Some(fv), fv)(f._2.prov) + if (f._2.lb.isDefined) FieldType(Some(fv), fv, f._2.opt)(f._2.prov) else fv.toUpper(f._2.prov) ) }).toList diff --git a/shared/src/main/scala/mlscript/TypeSimplifier.scala b/shared/src/main/scala/mlscript/TypeSimplifier.scala index f5a0bd8136..70090fcb61 100644 --- a/shared/src/main/scala/mlscript/TypeSimplifier.scala +++ b/shared/src/main/scala/mlscript/TypeSimplifier.scala @@ -119,8 +119,8 @@ trait TypeSimplifier { self: Typer => tvv(td.tparamsargs.find(_._1.name === postfix).getOrElse(die)._2) match { case VarianceInfo(true, true) => Nil case VarianceInfo(co, contra) => - if (co) v -> FieldType(S(BotType), process(fty.ub, N))(fty.prov) :: Nil - else if (contra) v -> FieldType(fty.lb.map(process(_, N)), TopType)(fty.prov) :: Nil + if (co) v -> FieldType(S(BotType), process(fty.ub, N), false)(fty.prov) :: Nil + else if (contra) v -> FieldType(fty.lb.map(process(_, N)), TopType, false)(fty.prov) :: Nil else v -> default :: Nil }) case N => @@ -132,16 +132,16 @@ trait TypeSimplifier { self: Typer => cls.varianceOf(cls.tparams.find(_._1.name === postfix).getOrElse(die)._2) match { case VarianceInfo(true, true) => Nil case VarianceInfo(co, contra) => - if (co) v -> FieldType(S(BotType), process(fty.ub, N))(fty.prov) :: Nil - else if (contra) v -> FieldType(fty.lb.map(process(_, N)), TopType)(fty.prov) :: Nil + if (co) v -> FieldType(S(BotType), process(fty.ub, N), false)(fty.prov) :: Nil + else if (contra) v -> FieldType(fty.lb.map(process(_, N)), TopType, false)(fty.prov) :: Nil else v -> default :: Nil } case S(trt: TypedNuTrt) => // TODO factor w/ above & generalize trt.tparams.iterator.find(_._1.name === postfix).flatMap(_._3).getOrElse(VarianceInfo.in) match { case VarianceInfo(true, true) => Nil case VarianceInfo(co, contra) => - if (co) v -> FieldType(S(BotType), process(fty.ub, N))(fty.prov) :: Nil - else if (contra) v -> FieldType(fty.lb.map(process(_, N)), TopType)(fty.prov) :: Nil + if (co) v -> FieldType(S(BotType), process(fty.ub, N), false)(fty.prov) :: Nil + else if (contra) v -> FieldType(fty.lb.map(process(_, N)), TopType, false)(fty.prov) :: Nil else v -> default :: Nil } case S(_) => ??? // TODO: @@ -259,10 +259,10 @@ trait TypeSimplifier { self: Typer => // * Reconstruct a TypeRef from its current structural components val typeRef = TypeRef(td.nme, td.tparamsargs.zipWithIndex.map { case ((tp, tv), tpidx) => val fieldTagNme = tparamField(clsTyNme, tp) - val fromTyRef = trs2.get(clsTyNme).map(_.targs(tpidx) |> { ta => FieldType(S(ta), ta)(noProv) }) + val fromTyRef = trs2.get(clsTyNme).map(_.targs(tpidx) |> { ta => FieldType(S(ta), ta, false)(noProv) }) fromTyRef.++(rcd2.fields.iterator.filter(_._1 === fieldTagNme).map(_._2)) .foldLeft((BotType: ST, TopType: ST)) { - case ((acc_lb, acc_ub), FieldType(lb, ub)) => + case ((acc_lb, acc_ub), FieldType(lb, ub, _)) => (acc_lb | lb.getOrElse(BotType), acc_ub & ub) }.pipe { case (lb, ub) => @@ -369,10 +369,10 @@ trait TypeSimplifier { self: Typer => // * Reconstruct a TypeRef from its current structural components val typeRef = TypeRef(cls.td.nme, cls.tparams.zipWithIndex.map { case ((tp, tv, vi), tpidx) => val fieldTagNme = tparamField(clsTyNme, tp) - val fromTyRef = trs2.get(clsTyNme).map(_.targs(tpidx) |> { ta => FieldType(S(ta), ta)(noProv) }) + val fromTyRef = trs2.get(clsTyNme).map(_.targs(tpidx) |> { ta => FieldType(S(ta), ta, false)(noProv) }) fromTyRef.++(rcd2.fields.iterator.filter(_._1 === fieldTagNme).map(_._2)) .foldLeft((BotType: ST, TopType: ST)) { - case ((acc_lb, acc_ub), FieldType(lb, ub)) => + case ((acc_lb, acc_ub), FieldType(lb, ub, _)) => (acc_lb | lb.getOrElse(BotType), acc_ub & ub) }.pipe { case (lb, ub) => @@ -427,7 +427,7 @@ trait TypeSimplifier { self: Typer => .partitionMap(f => f._1.toIndexOption.filter((0 until arity).contains).map(_ -> f._2).toLeft(f)) val componentFieldsMap = componentFields.toMap val tupleComponents = fs.iterator.zipWithIndex.map { case ((nme, ty), i) => - nme -> (ty && componentFieldsMap.getOrElse(i, TopType.toUpper(noProv))).update(go(_, pol.map(!_)), go(_, pol)) + nme -> (ty && componentFieldsMap.getOrElse(i, TopType.toUpper(noProv, true))).update(go(_, pol.map(!_)), go(_, pol)) }.toList S(TupleType(tupleComponents)(tt.prov)) -> rcdFields.mapValues(_.update(go(_, pol.map(!_)), go(_, pol))) case S(ct: ClassTag) => S(ct) -> nFields @@ -964,9 +964,9 @@ trait TypeSimplifier { self: Typer => def transform(st: SimpleType, pol: PolMap, parents: Set[TV], canDistribForall: Opt[Level] = N): SimpleType = trace(s"transform[${printPol(pol)}] $st (${parents.mkString(", ")}) $pol $canDistribForall") { def transformField(f: FieldType): FieldType = f match { - case FieldType(S(lb), ub) if lb === ub => + case FieldType(S(lb), ub, _) if lb === ub => val b = transform(ub, pol.invar, semp) - FieldType(S(b), b)(f.prov) + FieldType(S(b), b, false)(f.prov) case _ => f.update(transform(_, pol.contravar, semp), transform(_, pol, semp)) } st match { @@ -1237,8 +1237,8 @@ trait TypeSimplifier { self: Typer => def nope: false = { println(s"Nope(${ty1.getClass.getSimpleName}): $ty1 ~ $ty2"); false } def unifyF(f1: FieldType, f2: FieldType): Bool = (f1, f2) match { - case (FieldType(S(l1), u1), FieldType(S(l2), u2)) => unify(l1, l2) && unify(u1, u2) - case (FieldType(N, u1), FieldType(N, u2)) => unify(u1, u2) + case (FieldType(S(l1), u1, _), FieldType(S(l2), u2, _)) => unify(l1, l2) && unify(u1, u2) + case (FieldType(N, u1, _), FieldType(N, u2, _)) => unify(u1, u2) case _ => nope } diff --git a/shared/src/main/scala/mlscript/Typer.scala b/shared/src/main/scala/mlscript/Typer.scala index af88e803bc..ec18cd22ee 100644 --- a/shared/src/main/scala/mlscript/Typer.scala +++ b/shared/src/main/scala/mlscript/Typer.scala @@ -333,7 +333,7 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne { val tv = freshVar(noTyProv, N)(1) val tyDef = TypeDef(Als, TN("Array"), List(TN("A") -> tv), - ArrayType(FieldType(None, tv)(noTyProv))(noTyProv), Nil, Nil, semp, N, Nil) + ArrayType(FieldType(None, tv, false)(noTyProv))(noTyProv), Nil, Nil, semp, N, Nil) // * ^ Note that the `noTyProv` here is kind of a problem // * since we currently expand primitive types eagerly in DNFs. // * For instance, see `inn2 v1` in test `Yicong.mls`. @@ -346,7 +346,7 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne { val tv = freshVar(noTyProv, N)(1) val tyDef = TypeDef(Als, TN("MutArray"), List(TN("A") -> tv), - ArrayType(FieldType(Some(tv), tv)(noTyProv))(noTyProv), Nil, Nil, semp, N, Nil) + ArrayType(FieldType(Some(tv), tv, false)(noTyProv))(noTyProv), Nil, Nil, semp, N, Nil) tyDef.tvarVariances = S(MutMap(tv -> VarianceInfo.in)) tyDef } :: @@ -458,7 +458,7 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne }, "emptyArray" -> { val v = freshVar(noProv, N)(1) - PolymorphicType(0, ArrayType(FieldType(S(v), v)(noProv))(noProv)) + PolymorphicType(0, ArrayType(FieldType(S(v), v, false)(noProv))(noProv)) }, "run" -> { val tv = freshVar(noProv, N)(1) @@ -538,7 +538,7 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne TypeBounds(lb_ty, ub_ty)(prov) case Tuple(fields) => TupleType(fields.mapValues(f => - FieldType(f.in.map(rec), rec(f.out))(tp(f.toLoc, "tuple field")) + FieldType(f.in.map(rec), rec(f.out), f.opt)(tp(f.toLoc, "tuple field")) ))(tyTp(ty.toLoc, "tuple type")) case Splice(fields) => SpliceType(fields.map{ @@ -549,7 +549,7 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne L(t) } case R(f) => { - R(FieldType(f.in.map(rec), rec(f.out))(tp(f.toLoc, "splice field"))) + R(FieldType(f.in.map(rec), rec(f.out), false)(tp(f.toLoc, "splice field"))) } })(tyTp(ty.toLoc, "splice type")) case Inter(lhs, rhs) => (if (simplify) rec(lhs) & (rec(rhs), _: TypeProvenance) @@ -569,14 +569,14 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne RecordType.mk(fs.map { nt => if (nt._1.name.isCapitalized) err(msg"Field identifiers must start with a small letter", nt._1.toLoc)(raise) - nt._1 -> FieldType(nt._2.in.map(rec), rec(nt._2.out))( + nt._1 -> FieldType(nt._2.in.map(rec), rec(nt._2.out), false)( tp(App(nt._1, Var("").withLocOf(nt._2)).toCoveringLoc, (if (nt._2.in.isDefined) "mutable " else "") + "record field")) })(prov) case Function(lhs, rhs) => FunctionType(rec(lhs), rec(rhs))(tyTp(ty.toLoc, "function type")) case WithExtension(b, r) => WithType(rec(b), RecordType( - r.fields.map { case (n, f) => n -> FieldType(f.in.map(rec), rec(f.out))( + r.fields.map { case (n, f) => n -> FieldType(f.in.map(rec), rec(f.out), false)( tyTp(App(n, Var("").withLocOf(f)).toCoveringLoc, "extension field")) } )(tyTp(r.toLoc, "extension record")))(tyTp(ty.toLoc, "extension type")) case Literal(lit) => @@ -1022,7 +1022,7 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne :: fieldNames.map(tp => msg"Declared at" -> tp.toLoc))(raise) case _ => } - RecordType.mk(fs.map { case (n, Fld(FldFlags(mut, _, _), t)) => + RecordType.mk(fs.map { case (n, Fld(FldFlags(mut, _, opt, _), t)) => if (n.name.isCapitalized) err(msg"Field identifiers must start with a small letter", term.toLoc)(raise) val tym = typePolymorphicTerm(t) @@ -1030,31 +1030,58 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne if (mut) { val res = freshVar(fprov, N, S(n.name)) val rs = con(tym, res, res) - (n, FieldType(Some(rs), rs)(fprov)) + (n, FieldType(Some(rs), rs, false)(fprov)) } else (n, tym.toUpper(fprov)) })(prov) case tup: Tup if funkyTuples => typeTerms(tup :: Nil, false, Nil) case Tup(fs) => - TupleType(fs.mapConserve { case e @ (n, Fld(flags, t)) => - n match { - case S(v) if ctx.inPattern => - (n, Fld(flags, - Asc(v, t.toTypeRaise).withLoc(v.toLoc.fold(t.toLoc)(_ ++ t.toLoc |> some)))) - case _ => e + TupleType(fs.mapConserve { case e @ (n, f @ Fld(flags, t)) => + { + val res = n match { + case S(v) if ctx.inPattern => + (n, Fld(flags, + Asc(v, t.toTypeRaise).withLoc(v.toLoc.fold(t.toLoc)(_ ++ t.toLoc |> some)))) + case N if flags.opt => { + val res = t match { + case v: Var if ctx.inPattern => + (n, Fld(flags, + Asc(v, Union(TypeVar(R(""), N), Literal(UnitLit(true)))).withLoc(v.toLoc.fold(t.toLoc)(_ ++ t.toLoc |> some)))) + case _ => + // if ctx.inPattern // TODO[optional-fields] + err("Error here", f.toLoc) // TODO[optional-fields] + e + } + res + } + case _ => e + } + println(s"dbg [Typer.scala]: $res opt: ${flags.opt}") + res } - }.map { case (n, Fld(FldFlags(mut, spec, getter), t)) => + }.map { case (n, Fld(FldFlags(mut, spec, opt, getter), t)) => if (getter) err(msg"Cannot use `val` in this position", Loc(t :: n.toList)) - val tym = typePolymorphicTerm(t) - // val tym = if (n.isDefined) typeType(t.toTypeRaise) - // else typePolymorphicTerm(t) - val fprov = tp(t.toLoc, (if (mut) "mutable " else "") + "tuple field") + // val tym = if (n.isDefined) typeType(t.toTypeRaise) + // else typePolymorphicTerm(t) + val fprov = tp(t.toLoc, (if (mut) "mutable " else "") + "tuple field") + // val tym = if (opt) { + // println(s"opt val => $opt") + // ComposedType(true, + // typePolymorphicTerm(t), + // TypeRef(TypeName("undefined"), Nil)(noProv))(fprov) + // } else typePolymorphicTerm(t) + val tym = typePolymorphicTerm(t) if (mut) { val res = freshVar(fprov, N, n.map(_.name)) val rs = con(tym, res, res) - (n, FieldType(Some(rs), rs)(fprov)) - } else (n, tym.toUpper(fprov)) + (n, FieldType(Some(rs), rs, opt)(fprov)) + } else { + val ty = tym.toUpper(fprov) + val tres = FieldType(ty.lb, ty.ub, opt)(ty.prov) + (n, tres) + // (n, tym.toUpper(fprov)) // TODO[optional-fields]: should send opt in toUpper? + } })(fs match { case Nil | ((N, _) :: Nil) => noProv // TODO rm? case _ => tp(term.toLoc, "tuple literal") @@ -1084,7 +1111,7 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne val obj_ty = // Note: this proxy does not seem to make any difference: mkProxy(o_ty, tp(r.toCoveringLoc, "receiver")) - con(obj_ty, RecordType.mk((f, FieldType(Some(fieldType), TopType)( + con(obj_ty, RecordType.mk((f, FieldType(Some(fieldType), TopType, false)( tp(f.toLoc, "assigned field") )) :: Nil)(sprov), fieldType) val vl = typeMonomorphicTerm(rhs) @@ -1096,7 +1123,7 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne val arr_ty = // Note: this proxy does not seem to make any difference: mkProxy(a_ty, tp(a.toCoveringLoc, "receiver")) - con(arr_ty, ArrayType(FieldType(Some(elemType), elemType)(sprov))(prov), TopType) + con(arr_ty, ArrayType(FieldType(Some(elemType), elemType, false)(sprov))(prov), TopType) val i_ty = typeMonomorphicTerm(i) con(i_ty, IntType, TopType) val vl = typeMonomorphicTerm(rhs) @@ -1132,9 +1159,9 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne val t_a = ArrayType(freshVar(prov, N).toUpper(prov))(prov) con(t_l, t_a, t_l) }) - case R(Fld(FldFlags(mt, sp, _), r)) => { + case R(Fld(FldFlags(mt, sp, opt, _), r)) => { val t = typeMonomorphicTerm(r) - if (mt) { R(FieldType(Some(t), t)(t.prov)) } else {R(t.toUpper(t.prov))} + if (mt) { R(FieldType(Some(t), t, opt)(t.prov)) } else {R(t.toUpper(t.prov))} // TODO[optional-flags]: should send opt to toUpper? } })(prov) case Bra(false, trm: Blk) => typeTerm(trm) @@ -1274,11 +1301,11 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne else ctx.poly { implicit ctx => typePolymorphicTerm(a) } a match { case tup @ Tup(as) => - TupleType(as.map { case (n, Fld(FldFlags(mut, spec, _), a)) => // TODO handle mut? + TupleType(as.map { case (n, Fld(FldFlags(mut, spec, opt, _), a)) => // TODO handle mut? // assert(!mut) val fprov = tp(a.toLoc, "argument") val tym = typeArg(a) - (n, tym.toUpper(fprov)) + (n, tym.toUpper(fprov)) // TODO[optional-fields]: should send opt to toUpper? })(as match { // TODO dedup w/ general Tup case case Nil | ((N, _) :: Nil) => noProv case _ => tp(tup.toLoc, "argument list") @@ -1488,7 +1515,7 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne fs.map(_ => freshVar(noProv, N)) // ) - val fld_ty = tupArgs.map(elem => N -> FieldType(N, elem)(elem.prov)) + val fld_ty = tupArgs.map(elem => N -> FieldType(N, elem, false)(elem.prov)) val caseAdtTyp = TypeProvenance(tup.toLoc, "pattern") val adt_ty = TupleType(fld_ty)(caseAdtTyp) .withProv(TypeProvenance(cond.toLoc, "match `condition`")) @@ -1735,7 +1762,7 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne typeTerms(Tup(S(trm) -> Fld(FldFlags.empty, trm) :: Nil) :: sts, rcd, fields) case Blk(sts0) :: sts1 => typeTerms(sts0 ::: sts1, rcd, fields) case Tup(Nil) :: sts => typeTerms(sts, rcd, fields) - case Tup((no, Fld(FldFlags(tmut, _, _), trm)) :: ofs) :: sts => + case Tup((no, Fld(FldFlags(tmut, _, _, _), trm)) :: ofs) :: sts => val ty = { trm match { case Bra(false, t) if ctx.inPattern => // we use syntax `(x: (p))` to type `p` as a pattern and not a type... @@ -1885,11 +1912,11 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne val seenTscs = mutable.Set.empty[TupleSetConstraints] def field(ft: FieldType)(implicit ectx: ExpCtx): Field = ft match { - case FieldType(S(l: TV), u: TV) if l === u => + case FieldType(S(l: TV), u: TV, opt) if l === u => val res = go(u) - Field(S(res), res) // TODO improve Field + Field(S(res), res, opt) // TODO improve Field case f => - Field(f.lb.map(go), go(f.ub)) + Field(f.lb.map(go), go(f.ub), f.opt) } class ExpCtx(val tps: Map[TV, TN]) { @@ -2008,13 +2035,13 @@ class Typer(var dbg: Boolean, var verbose: Bool, var explainErrors: Bool, val ne case ComposedType(false, l, r) => Inter(go(l), go(r)) case RecordType(fs) => Record(fs.mapValues(field)) case TupleType(fs) => Tuple(fs.mapValues(field)) - case ArrayType(FieldType(None, ub)) => AppliedType(TypeName("Array"), go(ub) :: Nil) + case ArrayType(FieldType(None, ub, _)) => AppliedType(TypeName("Array"), go(ub) :: Nil) case ArrayType(f) => val f2 = field(f) AppliedType(TypeName("MutArray"), Bounds(f2.in.getOrElse(Bot), f2.out) :: Nil) case SpliceType(elems) => Splice(elems.map { case L(l) => L(go(l)) - case R(v) => R(Field(v.lb.map(go(_)), go(v.ub))) }) + case R(v) => R(Field(v.lb.map(go(_)), go(v.ub), false)) }) case NegType(t) => Neg(go(t)) case ExtrType(true) => Bot case ExtrType(false) => Top diff --git a/shared/src/main/scala/mlscript/TyperDatatypes.scala b/shared/src/main/scala/mlscript/TyperDatatypes.scala index 17a95fc8ff..427cda4555 100644 --- a/shared/src/main/scala/mlscript/TyperDatatypes.scala +++ b/shared/src/main/scala/mlscript/TyperDatatypes.scala @@ -208,7 +208,7 @@ abstract class TyperDatatypes extends TyperHelpers { Typer: Typer => def freshenAboveImpl(lim: Int, rigidify: Bool)(implicit ctx: Ctx, freshened: MutMap[TV, ST]): FunctionType = FunctionType(lhs.freshenAbove(lim, rigidify), rhs.freshenAbove(lim, rigidify))(prov) override def toString = s"(${lhs match { - case TupleType((N, FieldType(N, f: TupleType)) :: Nil) => "[" + f.showInner + "]" + case TupleType((N, FieldType(N, f: TupleType, _)) :: Nil) => "[" + f.showInner + "]" case TupleType((N, f) :: Nil) => f.toString case lhs => lhs }} -> $rhs)" @@ -303,6 +303,8 @@ abstract class TyperDatatypes extends TyperHelpers { Typer: Typer => fields.map(f => s"${f._1.fold("")(_.name+": ")}${f._2},").mkString(" ") override def toString = s"($showInner)" // override def toString = s"(${fields.map(f => s"${f._1.fold("")(_+": ")}${f._2},").mkString(" ")})" + def isLengthCompatibleWith(that: TupleType): Boolean = fields.sizeCompare(that.fields) <= 0 && + fields.sizeCompare(that.fields.filter(x => !x._2.opt)) >= 0 } case class SpliceType(elems: Ls[Either[SimpleType, FieldType]])(val prov: TypeProvenance) extends ArrayBase { @@ -503,32 +505,31 @@ abstract class TyperDatatypes extends TyperHelpers { Typer: Typer => } } - case class FieldType(lb: Option[SimpleType], ub: SimpleType)(val prov: TypeProvenance) { + case class FieldType(lb: Option[SimpleType], ub: SimpleType, opt: Boolean)(val prov: TypeProvenance) { def level: Int = lb.map(_.level).getOrElse(ub.level) max ub.level def levelBelow(ubLvl: Level)(implicit cache: MutSet[TV]): Level = lb.fold(MinLevel)(_.levelBelow(ubLvl)) max ub.levelBelow(ubLvl) def <:< (that: FieldType)(implicit ctx: Ctx, cache: MutMap[ST -> ST, Bool] = MutMap.empty): Bool = (that.lb.getOrElse(BotType) <:< this.lb.getOrElse(BotType)) && (this.ub <:< that.ub) def && (that: FieldType, prov: TypeProvenance = noProv): FieldType = - FieldType(lb.fold(that.lb)(l => Some(that.lb.fold(l)(l | _))), ub & that.ub)(prov) + FieldType(lb.fold(that.lb)(l => Some(that.lb.fold(l)(l | _))), ub & that.ub, opt && that.opt)(prov) def || (that: FieldType, prov: TypeProvenance = noProv): FieldType = - FieldType(for {l <- lb; r <- that.lb} yield (l & r), ub | that.ub)(prov) + FieldType(for {l <- lb; r <- that.lb} yield (l & r), ub | that.ub, opt || that.opt)(prov) def update(lb: SimpleType => SimpleType, ub: SimpleType => SimpleType): FieldType = - FieldType(this.lb.map(lb), ub(this.ub))(prov) + FieldType(this.lb.map(lb), ub(this.ub), opt)(prov) def freshenAbove(lim: Int, rigidify: Bool)(implicit ctx: Ctx, freshened: MutMap[TV, ST]): FieldType = update(_.freshenAbove(lim, rigidify), _.freshenAbove(lim, rigidify)) override def toString = - lb.fold(s"$ub")(lb => s"mut ${if (lb === BotType) "" else lb}..$ub") + lb.fold(s"$ub")(lb => s"mut ${if (lb === BotType) "" else lb}..$ub") + (if (opt) "?" else "") } - object FieldType { + object FieldType { // TODO[optional-fields] def mk(vi: VarianceInfo, lb: ST, ub: ST)(prov: TP): FieldType = vi match { - case VarianceInfo(true, true) => FieldType(N, TopType)(prov) - case VarianceInfo(true, false) => FieldType(N, ub)(prov) - case VarianceInfo(false, true) => FieldType(S(lb), TopType)(prov) - case VarianceInfo(false, false) => FieldType(S(lb), ub)(prov) + case VarianceInfo(true, true) => FieldType(N, TopType, false)(prov) + case VarianceInfo(true, false) => FieldType(N, ub, false)(prov) + case VarianceInfo(false, true) => FieldType(S(lb), TopType, false)(prov) + case VarianceInfo(false, false) => FieldType(S(lb), ub, false)(prov) } } - val createdTypeVars: Buffer[TV] = Buffer.empty /** A type variable living at a certain polymorphism level `level`, with mutable bounds. @@ -740,7 +741,7 @@ abstract class TyperDatatypes extends TyperHelpers { Typer: Typer => } else N case (a: TupleType, b: RecordType) if pol => lcg(pol, a.toRecord, b) case (a: RecordType, b: RecordType) => - val default = FieldType(N, if (pol) TopType else BotType)(noProv) + val default = FieldType(N, if (pol) TopType else BotType, false)(noProv) if (b.fields.map(_._1).forall(a.fields.map(_._1).contains)) { val u = a.fields.map { case (v, f) => lcgField(pol, f, b.fields.find(_._1 === v).fold(default)(_._2)) diff --git a/shared/src/main/scala/mlscript/TyperHelpers.scala b/shared/src/main/scala/mlscript/TyperHelpers.scala index c4e9c2299b..95d92fa3a1 100644 --- a/shared/src/main/scala/mlscript/TyperHelpers.scala +++ b/shared/src/main/scala/mlscript/TyperHelpers.scala @@ -133,14 +133,14 @@ abstract class TyperHelpers { Typer: Typer => // }(r => s"=> $r") def tupleIntersection(fs1: Ls[Opt[Var] -> FieldType], fs2: Ls[Opt[Var] -> FieldType]): Ls[Opt[Var] -> FieldType] = { - require(fs1.size === fs2.size) + require(fs1.sizeCompare(fs2) <= 0 && fs1.sizeCompare(fs2.filter(x => !x._2.opt)) >= 0) (fs1 lazyZip fs2).map { case ((S(n1), t1), (S(n2), t2)) if n1 =/= n2 => (N, t1 && t2) case ((no1, t1), (no2, t2)) => (no1 orElse no2, t1 && t2) } } def tupleUnion(fs1: Ls[Opt[Var] -> FieldType], fs2: Ls[Opt[Var] -> FieldType]): Ls[Opt[Var] -> FieldType] = { - require(fs1.size === fs2.size) + require(fs1.sizeCompare(fs2) <= 0 && fs1.sizeCompare(fs2.filter(x => !x._2.opt)) >= 0) (fs1 lazyZip fs2).map { case ((S(n1), t1), (S(n2), t2)) => (Option.when(n1 === n2)(n1), t1 || t2) case ((no1, t1), (no2, t2)) => (N, t1 || t2) @@ -375,9 +375,9 @@ abstract class TyperHelpers { Typer: Typer => case _: TypeVariable | _: TypeTag | _: ExtrType => this } - def toUpper(prov: TypeProvenance): FieldType = FieldType(None, this)(prov) - def toLower(prov: TypeProvenance): FieldType = FieldType(Some(this), TopType)(prov) - def toBoth(prov: TypeProvenance): FieldType = FieldType(S(this), this)(prov) + def toUpper(prov: TypeProvenance, opt: Bool = false): FieldType = FieldType(None, this, opt)(prov) + def toLower(prov: TypeProvenance, opt: Bool = false): FieldType = FieldType(Some(this), TopType, opt)(prov) + def toBoth(prov: TypeProvenance, opt: Bool = false): FieldType = FieldType(S(this), this, opt)(prov) def | (that: SimpleType, prov: TypeProvenance = noProv, swapped: Bool = false): SimpleType = (this, that) match { case (TopType, _) => this @@ -394,15 +394,19 @@ abstract class TyperHelpers { Typer: Typer => case (_: RecordType, _: FunctionType) => TopType case (RecordType(fs1), RecordType(fs2)) => RecordType(recordUnion(fs1, fs2))(prov) - case (t0 @ TupleType(fs0), t1 @ TupleType(fs1)) + case (t0 @ TupleType(fs0), t1 @ TupleType(fs1)) if (fs0.sizeCompare(fs1) === 0) => // TODO[optional-fields]: check size is ok? // If the sizes are different, to merge these we'd have to return // the awkward `t0.toArray & t0.toRecord | t1.toArray & t1.toRecord` - if fs0.sizeCompare(fs1) === 0 => + if (!t0.isLengthCompatibleWith(t1)) BotType + println("Here, Union") TupleType(tupleUnion(fs0, fs1))(t0.prov) case _ if !swapped => that | (this, prov, swapped = true) case (`that`, _) => this case (NegType(`that`), _) => TopType - case _ => ComposedType(true, that, this)(prov) + case _ => { + println("Composed") + ComposedType(true, that, this)(prov) + } } /** This is to intersect two types that occur in negative position, @@ -433,7 +437,7 @@ abstract class TyperHelpers { Typer: Typer => case (RecordType(fs1), RecordType(fs2)) => RecordType(mergeSortedMap(fs1, fs2)(_ && _).toList)(prov) case (t0 @ TupleType(fs0), t1 @ TupleType(fs1)) => - if (fs0.sizeCompare(fs1) =/= 0) BotType + if (!t0.isLengthCompatibleWith(t1)) BotType else TupleType(tupleIntersection(fs0, fs1))(t0.prov) case _ if !swapped => that & (this, prov, swapped = true) case (`that`, _) => this @@ -493,8 +497,8 @@ abstract class TyperHelpers { Typer: Typer => // case (ClassTag(ErrTypeId, _), _) | (_, ClassTag(ErrTypeId, _)) => true case (_: TypeTag, _: TypeTag) | (_: TV, _: TV) if this === that => true case (ab: ArrayBase, at: ArrayType) => ab.inner <:< at.inner - case (TupleType(fs1), TupleType(fs2)) => - fs1.sizeCompare(fs2) === 0 && fs1.lazyZip(fs2).forall { + case (t1 @ TupleType(fs1), t2 @ TupleType(fs2)) => + t1.isLengthCompatibleWith(t2) && fs1.lazyZip(fs2).forall { case ((_, ty1), (_, ty2)) => ty1 <:< ty2 } case (RecordType(Nil), _) => TopType <:< that @@ -1133,7 +1137,7 @@ abstract class TyperHelpers { Typer: Typer => val tvv = td.getVariancesOrDefault tparamField(defn, tp) -> FieldType( Some(if (tvv(tv).isCovariant) BotType else tv), - if (tvv(tv).isContravariant) TopType else tv)(prov) + if (tvv(tv).isContravariant) TopType else tv, false)(prov) })(noProv) else TopType subst(td.kind match { diff --git a/shared/src/main/scala/mlscript/helpers.scala b/shared/src/main/scala/mlscript/helpers.scala index 8131ff246d..905ed099e7 100644 --- a/shared/src/main/scala/mlscript/helpers.scala +++ b/shared/src/main/scala/mlscript/helpers.scala @@ -22,12 +22,23 @@ trait TypeLikeImpl extends Located { self: TypeLike => def show(newDefs: Bool): Str = showIn(0)(ShowCtx.mk(this :: Nil, newDefs)) private def parensIf(str: Str, cnd: Boolean): Str = if (cnd) "(" + str + ")" else str - private def showField(f: Field)(implicit ctx: ShowCtx): Str = f match { - case Field(N, ub) => ub.showIn(0) - case Field(S(lb), ub) if lb === ub => ub.showIn(0) - case Field(S(Bot), ub) => s"out ${ub.showIn(0)}" - case Field(S(lb), Top) => s"in ${lb.showIn(0)}" - case Field(S(lb), ub) => s"in ${lb.showIn(0)} out ${ub.showIn(0)}" + private def showField(f: Field)(implicit ctx: ShowCtx): Str = { + val res = f match { + case Field(N, ub, _) => ub.showIn(0) + case Field(S(lb), ub, _) if lb === ub => ub.showIn(0) + case Field(S(Bot), ub, _) => s"out ${ub.showIn(0)}" + case Field(S(lb), Top, _) => s"in ${lb.showIn(0)}" + case Field(S(lb), ub, _) => s"in ${lb.showIn(0)} out ${ub.showIn(0)}" + } + val opt = f match { + case Field(_, _, true) => true + case Field(_, _, false) => false + } + // println(s"dbg: [helpers.scala] OK $f ### ${res} ${opt}") + if (opt) + s"($res)?" + else + res } private def showFields(fs: Ls[Opt[Var] -> Field])(implicit ctx: ShowCtx): Ls[Str] = fs.map(nt => s"${nt._2.mutStr}${nt._1.fold("")(_.name + ": ")}${showField(nt._2)}") @@ -44,7 +55,7 @@ trait TypeLikeImpl extends Located { self: TypeLike => case Function(Tuple(fs), r) => val innerStr = fs match { case Nil => "()" - case N -> Field(N, f) :: Nil if !f.isInstanceOf[Tuple] => f.showIn(31) + case N -> Field(N, f, false) :: Nil if !f.isInstanceOf[Tuple] => f.showIn(31) case _ => val inner = showFields(fs) if (ctx.newDefs) inner.mkString("(", ", ", ")") else inner.mkString("(", ", ", ",)") @@ -58,11 +69,11 @@ trait TypeLikeImpl extends Located { self: TypeLike => val strs = fs.map { nt => val nme = nt._1.name if (nme.isCapitalized) nt._2 match { - case Field(N | S(Bot), Top) => s"$nme" - case Field(S(lb), ub) if lb === ub => s"$nme = ${ub.showIn(0)}" - case Field(N | S(Bot), ub) => s"$nme <: ${ub.showIn(0)}" - case Field(S(lb), Top) => s"$nme :> ${lb.showIn(0)}" - case Field(S(lb), ub) => s"$nme :> ${lb.showIn(0)} <: ${ub.showIn(0)}" + case Field(N | S(Bot), Top, _) => s"$nme" + case Field(S(lb), ub, _) if lb === ub => s"$nme = ${ub.showIn(0)}" + case Field(N | S(Bot), ub, _) => s"$nme <: ${ub.showIn(0)}" + case Field(S(lb), Top, _) => s"$nme :> ${lb.showIn(0)}" + case Field(S(lb), ub, _) => s"$nme :> ${lb.showIn(0)} <: ${ub.showIn(0)}" } else s"${nt._2.mutStr}${nme}: ${showField(nt._2)}" } @@ -528,7 +539,7 @@ trait TypeNameImpl extends Ordered[TypeName] { self: TypeName => trait FldFlagsImpl extends Located { self: FldFlags => def children: Ls[Located] = Nil override def toString(): String = { - val FldFlags(m, s, g) = this + val FldFlags(m, s, o, g) = this val res = (if (m) "m" else "") + (if (s) "s" else "") + (if (g) "g" else "") if (res.isEmpty) "_" else res } @@ -638,11 +649,12 @@ trait TermImpl extends StatementImpl { self: Term => case tup: Tup => "[" + tup.showElems + "]" case Splc(fields) => fields.map{ case L(l) => s"...$l" - case R(Fld(FldFlags(m, s, g), r)) => ( + case R(Fld(FldFlags(m, s, o, g), r)) => ( (if (m) "mut " else "") + (if (g) "val " else "") + (if (s) "#" else "") + r + + (if (o) "?" else "") ) }.mkString("(", ", ", ")") case Bind(l, r) => s"${l.showDbg} as ${r.showDbg}" |> bra @@ -705,11 +717,12 @@ trait TermImpl extends StatementImpl { self: Term => case tup: Tup => "[" + tup.showIn + "]" case Splc(fields) => fields.map{ case L(l) => s"...${l.showIn(false)}" - case R(Fld(FldFlags(m, s, g), r)) => ( + case R(Fld(FldFlags(m, s, o, g), r)) => ( (if (m) "mut " else "") + (if (g) "val " else "") + (if (s) "#" else "") + r.showIn(false) + + (if (o) "?" else "") ) }.mkString("(", ", ", ")") case Bind(l, r) => s"${l.showIn(false)} as ${r.showIn(false)}" |> bra @@ -756,7 +769,7 @@ trait TermImpl extends StatementImpl { self: Term => // * ^ Note: don't think the plain _: Tup without a Bra can actually occur Function(lhs.toType_!, rhs.toType_!) case App(Var("->"), PlainTup(lhs, rhs)) => - Function(Tuple(N -> Field(N, lhs.toType_!) :: Nil), rhs.toType_!) + Function(Tuple(N -> Field(N, lhs.toType_!, false) :: Nil), rhs.toType_!) case App(Var("|"), PlainTup(lhs, rhs)) => Union(lhs.toType_!, rhs.toType_!) case App(Var("&"), PlainTup(lhs, rhs)) => @@ -771,7 +784,7 @@ trait TermImpl extends StatementImpl { self: Term => case _ => throw new NotAType(this) } case Tup(fields) => Tuple(fields.map(fld => (fld._1, fld._2 match { - case Fld(FldFlags(m, s, _), v) => val ty = v.toType_!; Field(Option.when(m)(ty), ty) + case Fld(FldFlags(m, s, o, _), v) => val ty = v.toType_!; Field(Option.when(m)(ty), ty, o) }))) case Bra(rcd, trm) => trm match { case _: Rcd => if (rcd) trm.toType_! else throw new NotAType(this) @@ -782,7 +795,7 @@ trait TermImpl extends StatementImpl { self: Term => case _ => throw new NotAType(this) } case Rcd(fields) => Record(fields.map(fld => (fld._1, fld._2 match { - case Fld(FldFlags(m, s, _), v) => val ty = v.toType_!; Field(Option.when(m)(ty), ty) + case Fld(FldFlags(m, s, o, _), v) => val ty = v.toType_!; Field(Option.when(m)(ty), ty, o) }))) case Where(body, where) => Constrained(body.toType_!, Nil, where.map { @@ -1051,17 +1064,17 @@ trait StatementImpl extends Located { self: Statement => case R(ty) => ty } val params = fs.map { - case (S(nme), Fld(FldFlags(mut, spec, _), trm)) => + case (S(nme), Fld(FldFlags(mut, spec, opt, _), trm)) => val ty = tt(trm) - nme -> Field(if (mut) S(ty) else N, ty) - case (N, Fld(FldFlags(mut, spec, _), nme: Var)) => nme -> Field(if (mut) S(Bot) else N, Top) + nme -> Field(if (mut) S(ty) else N, ty, opt) + case (N, Fld(FldFlags(mut, spec, opt, _), nme: Var)) => nme -> Field(if (mut) S(Bot) else N, Top, opt) case _ => die } val pos = params.unzip._1 val bod = pars.map(tt).foldRight(Record(params): Type)(Inter) val termName = Var(nme.name).withLocOf(nme) val ctor = Def(false, termName, L(Lam(tup, App(termName, Tup(N -> Fld(FldFlags.empty, Rcd(fs.map { - case (S(nme), fld) => nme -> Fld(FldFlags(false, false, fld.flags.genGetter), nme) + case (S(nme), fld) => nme -> Fld(FldFlags(false, false, false, fld.flags.genGetter), nme) case (N, fld @ Fld(_, nme: Var)) => nme -> fld case _ => die })) :: Nil)))), true) @@ -1109,25 +1122,25 @@ trait StatementImpl extends Located { self: Statement => case Bra(false, t) => getFields(t) case Bra(true, Tup(fs)) => Record(fs.map { - case (S(n) -> Fld(FldFlags(mut, _, _), t)) => + case (S(n) -> Fld(FldFlags(mut, _, opt, _), t)) => val ty = t.toType match { case L(d) => allDiags += d; Top case R(t) => t } fields += n -> ty - n -> Field(None, ty) + n -> Field(None, ty, opt) case _ => ??? }) :: Nil case Bra(true, t) => lastWords(s"$t ${t.getClass}") case Tup(fs) => // TODO factor with case Bra(true, Tup(fs)) above Tuple(fs.map { - case (S(n) -> Fld(FldFlags(tmut, _, _), t)) => + case (S(n) -> Fld(FldFlags(tmut, _, _, _), t)) => val ty = t.toType match { case L(d) => allDiags += d; Top case R(t) => t } fields += n -> ty - S(n) -> Field(None, ty) + S(n) -> Field(None, ty, false) case _ => ??? }) :: Nil case _ => ??? // TODO proper error @@ -1137,7 +1150,7 @@ trait StatementImpl extends Located { self: Statement => val tps = tparams.toList val ctor = Def(false, v, R(PolyType(tps.map(L(_)), params.foldRight(AppliedType(clsNme, tps):Type)(Function(_, _)))), true).withLocOf(stmt) - val td = TypeDef(Cls, clsNme, tps, Record(fields.toList.mapValues(Field(None, _))), Nil, Nil, Nil, N).withLocOf(stmt) + val td = TypeDef(Cls, clsNme, tps, Record(fields.toList.mapValues(Field(None, _, false))), Nil, Nil, Nil, N).withLocOf(stmt) td :: ctor :: cs case _ => ??? // TODO methods in data type defs? nested data type defs? } diff --git a/shared/src/main/scala/mlscript/syntax.scala b/shared/src/main/scala/mlscript/syntax.scala index 44a57eeeee..4c0e5cc516 100644 --- a/shared/src/main/scala/mlscript/syntax.scala +++ b/shared/src/main/scala/mlscript/syntax.scala @@ -108,10 +108,10 @@ final case class IfOpsApp(lhs: Term, opsRhss: Ls[Var -> IfBody]) extends IfBody final case class IfBlock(lines: Ls[IfBody \/ Statement]) extends IfBody // final case class IfApp(fun: Term, opsRhss: Ls[Var -> IfBody]) extends IfBody -final case class FldFlags(mut: Bool, spec: Bool, genGetter: Bool) extends FldFlagsImpl // TODO make it a Located and use in diagnostics +final case class FldFlags(mut: Bool, spec: Bool, opt: Bool, genGetter: Bool) extends FldFlagsImpl // TODO make it a Located and use in diagnostics final case class Fld(flags: FldFlags, value: Term) extends FldImpl -object FldFlags { val empty: FldFlags = FldFlags(false, false, false) } +object FldFlags { val empty: FldFlags = FldFlags(false, false, false, false) } sealed abstract class CaseBranches extends CaseBranchesImpl final case class Case(pat: SimpleTerm, body: Term, rest: CaseBranches)(val refined: Bool) extends CaseBranches @@ -165,7 +165,7 @@ final case class Constrained(base: TypeLike, tvBounds: Ls[TypeVar -> Bounds], wh // final case class FirstClassDefn(defn: NuTypeDef) extends Type // TODO // final case class Refinement(base: Type, decls: TypingUnit) extends Type // TODO -final case class Field(in: Opt[Type], out: Type) extends FieldImpl +final case class Field(in: Opt[Type], out: Type, opt: Boolean) extends FieldImpl sealed abstract class NullaryType extends Type diff --git a/shared/src/test/diff/basics/Data.fun b/shared/src/test/diff/basics/Data.fun index 551c3b1c0c..b7706706b4 100644 --- a/shared/src/test/diff/basics/Data.fun +++ b/shared/src/test/diff/basics/Data.fun @@ -13,7 +13,7 @@ data Person(name: string, age: int) //│ Parsed: data Person(...'(' {[name: string, age: int,]} ')'); //│ Desugared: class Person: {age: int, name: string} //│ Desugared: def Person: (name: string, age: int) -> Person[] -//│ AST: Def(false,Var(Person),Right(PolyType(List(),Function(Tuple(List((Some(Var(name)),Field(None,TypeName(string))), (Some(Var(age)),Field(None,TypeName(int))))),AppliedType(TypeName(Person),List())))),true) +//│ AST: Def(false,Var(Person),Right(PolyType(List(),Function(Tuple(List((Some(Var(name)),Field(None,TypeName(string),false)), (Some(Var(age)),Field(None,TypeName(int),false)))),AppliedType(TypeName(Person),List())))),true) //│ Defined class Person //│ Person: (name: string, age: int,) -> Person diff --git a/shared/src/test/diff/basics/Datatypes.fun b/shared/src/test/diff/basics/Datatypes.fun index a2950ace06..63aad23f4e 100644 --- a/shared/src/test/diff/basics/Datatypes.fun +++ b/shared/src/test/diff/basics/Datatypes.fun @@ -124,7 +124,7 @@ data type List a of //│ Desugared: def Nil: forall a. Nil[a] //│ AST: Def(false,Var(Nil),Right(PolyType(List(Left(TypeName(a))),AppliedType(TypeName(Nil),List(TypeName(a))))),true) //│ Desugared: def Cons: forall a. (head: a) -> (tail: anything) -> Cons[a] -//│ AST: Def(false,Var(Cons),Right(PolyType(List(Left(TypeName(a))),Function(Tuple(List((Some(Var(head)),Field(None,TypeName(a))))),Function(Tuple(List((Some(Var(tail)),Field(None,Top)))),AppliedType(TypeName(Cons),List(TypeName(a))))))),true) +//│ AST: Def(false,Var(Cons),Right(PolyType(List(Left(TypeName(a))),Function(Tuple(List((Some(Var(head)),Field(None,TypeName(a),false)))),Function(Tuple(List((Some(Var(tail)),Field(None,Top,false)))),AppliedType(TypeName(Cons),List(TypeName(a))))))),true) //│ Defined type alias List[+a] //│ Defined class Nil[±a] //│ Defined class Cons[+a] diff --git a/shared/src/test/diff/codegen/SymbolicOps.mls b/shared/src/test/diff/codegen/SymbolicOps.mls index 9dd61cb78a..aefd37c6c2 100644 --- a/shared/src/test/diff/codegen/SymbolicOps.mls +++ b/shared/src/test/diff/codegen/SymbolicOps.mls @@ -158,7 +158,6 @@ abstract class Nested { //│ unresolved symbol ++ - fun (??) oops: (Int, Int) => Int //│ fun (??) oops: (Int, Int) -> Int @@ -214,16 +213,16 @@ fun (??) oops(a, b) = a + b :e fun (>>)(f, g) = x => g(f(x)) //│ ╔══[PARSE ERROR] Expected a function name; found parenthesis section instead -//│ ║ l.215: fun (>>)(f, g) = x => g(f(x)) +//│ ║ l.214: fun (>>)(f, g) = x => g(f(x)) //│ ╙── ^^^^^^ //│ ╔══[ERROR] identifier not found: g -//│ ║ l.215: fun (>>)(f, g) = x => g(f(x)) +//│ ║ l.214: fun (>>)(f, g) = x => g(f(x)) //│ ╙── ^ //│ ╔══[ERROR] Type mismatch in application: -//│ ║ l.215: fun (>>)(f, g) = x => g(f(x)) +//│ ║ l.214: fun (>>)(f, g) = x => g(f(x)) //│ ║ ^^^^ //│ ╟── argument of type `[?a]` does not match type `[?b, ?c]` -//│ ║ l.215: fun (>>)(f, g) = x => g(f(x)) +//│ ║ l.214: fun (>>)(f, g) = x => g(f(x)) //│ ║ ^^^ //│ ╟── Note: constraint arises from tuple literal: //│ ║ l.4: fun (>>) compose(f, g) = x => g(f(x)) @@ -235,7 +234,7 @@ fun (>>)(f, g) = x => g(f(x)) :pe fun compose(>>)(f, g) = x => g(f(x)) //│ ╔══[PARSE ERROR] Unexpected operator here -//│ ║ l.236: fun compose(>>)(f, g) = x => g(f(x)) +//│ ║ l.235: fun compose(>>)(f, g) = x => g(f(x)) //│ ╙── ^^ //│ fun compose: forall 'a 'b 'c. () -> ('a -> 'b, 'b -> 'c) -> 'a -> 'c @@ -243,14 +242,14 @@ fun compose(>>)(f, g) = x => g(f(x)) :pe fun () foo(a, b) = a + b //│ ╔══[PARSE ERROR] Expected a symbolic name between brackets, found nothing -//│ ║ l.244: fun () foo(a, b) = a + b +//│ ║ l.243: fun () foo(a, b) = a + b //│ ╙── ^^ //│ fun foo: (Int, Int) -> Int :pe fun ( ) foo(a, b) = a + b //│ ╔══[PARSE ERROR] Expected a symbolic name, found space instead -//│ ║ l.251: fun ( ) foo(a, b) = a + b +//│ ║ l.250: fun ( ) foo(a, b) = a + b //│ ╙── ^^^ //│ fun foo: (Int, Int) -> Int @@ -258,30 +257,30 @@ fun ( ) foo(a, b) = a + b fun ( ) foo(a, b) = a + b //│ ╔══[PARSE ERROR] Expected a symbolic name, found newline instead -//│ ║ l.258: fun ( +//│ ║ l.257: fun ( //│ ║ ^ -//│ ║ l.259: ) foo(a, b) = a + b +//│ ║ l.258: ) foo(a, b) = a + b //│ ╙── //│ fun foo: (Int, Int) -> Int :pe fun (1) foo(a, b) = a + b //│ ╔══[PARSE ERROR] Expected a symbolic name, found literal instead -//│ ║ l.268: fun (1) foo(a, b) = a + b +//│ ║ l.267: fun (1) foo(a, b) = a + b //│ ╙── ^ //│ fun foo: (Int, Int) -> Int :pe fun (++ 1) foo(a, b) = a + b //│ ╔══[PARSE ERROR] Unexpected literal after symbolic name -//│ ║ l.275: fun (++ 1) foo(a, b) = a + b +//│ ║ l.274: fun (++ 1) foo(a, b) = a + b //│ ╙── ^ //│ fun (++) foo: (Int, Int) -> Int :pe fun (a ++ 1) foo(a, b) = a + b //│ ╔══[PARSE ERROR] Expected a symbolic name, found identifier instead -//│ ║ l.282: fun (a ++ 1) foo(a, b) = a + b +//│ ║ l.281: fun (a ++ 1) foo(a, b) = a + b //│ ╙── ^ //│ fun foo: (Int, Int) -> Int // should be `<<|+_+|>>`, but we got `<<|+` @@ -290,7 +289,7 @@ fun (a ++ 1) foo(a, b) = a + b :pe fun (<<|+_+|>>) robot(a, b) = a + b //│ ╔══[PARSE ERROR] Unexpected identifier after symbolic name -//│ ║ l.291: fun (<<|+_+|>>) robot(a, b) = a + b +//│ ║ l.290: fun (<<|+_+|>>) robot(a, b) = a + b //│ ╙── ^ //│ fun (<<|+) robot: (Int, Int) -> Int @@ -306,7 +305,7 @@ fun (<<|+-+|>>) robot(a, b) = a + b :pe fun (:-D) dd(a, b) = a + b //│ ╔══[PARSE ERROR] Unexpected identifier after symbolic name -//│ ║ l.307: fun (:-D) dd(a, b) = a + b +//│ ║ l.306: fun (:-D) dd(a, b) = a + b //│ ╙── ^ //│ fun (:-) dd: (Int, Int) -> Int // should be `:-D`, but we got `:-` diff --git a/shared/src/test/diff/nu/OptionalArgs.mls b/shared/src/test/diff/nu/OptionalArgs.mls new file mode 100644 index 0000000000..ce9da56620 --- /dev/null +++ b/shared/src/test/diff/nu/OptionalArgs.mls @@ -0,0 +1,172 @@ +:NewDefs + +fun (??) oops(a123, b123) = a123 + b123 +//│ fun (??) oops: (Int, Int) -> Int + +1 ?? 2 +//│ Int +//│ res +//│ = 3 + + +fun f1(aOpt?: Int, bOpt?: Int) = aOpt + bOpt +//│ fun f1: (aOpt: (Int)?, bOpt: (Int)?) -> Int + +fun f1(a: Int, b: Int) = a + b +//│ fun f1: (a: Int, b: Int) -> Int + + +class C1 +class C2 extends C1 +let x = new C2() +let y: C1 = x +//│ class C1 { +//│ constructor() +//│ } +//│ class C2 extends C1 { +//│ constructor() +//│ } +//│ let x: C2 +//│ let y: C1 +//│ x +//│ = C2 {} +//│ y +//│ = C2 {} + + +[x, y]: [C1, C1] +//│ [C1, C1] +//│ res +//│ = [ C2 {}, C2 {} ] + + +[x, y]: [C1, C1, C1?] +//│ [C1, C1, (C1)?] +//│ res +//│ = [ C2 {}, C2 {} ] + + +:e +[x]: [C1, C1, C1?] +//│ ╔══[ERROR] Type mismatch in type ascription: +//│ ║ l.50: [x]: [C1, C1, C1?] +//│ ║ ^^^ +//│ ╟── expression of type `[?a]` does not match type `[C1, C1, (C1)?]` +//│ ╟── Note: constraint arises from tuple type: +//│ ║ l.50: [x]: [C1, C1, C1?] +//│ ╙── ^^^^^^^^^^^^^ +//│ [C1, C1, (C1)?] +//│ res +//│ = [ C2 {} ] + + +[1, 2, 3]: [Int, Int, Int] +//│ [Int, Int, Int] +//│ res +//│ = [ 1, 2, 3 ] + +[1, 2]: [Int, Int?] +//│ [Int, (Int)?] +//│ res +//│ = [ 1, 2 ] + +[1]: [Int, Int?] +//│ [Int, (Int)?] +//│ res +//│ = [ 1 ] + +:e +[]: [Int, Int?] +//│ ╔══[ERROR] Type mismatch in type ascription: +//│ ║ l.79: []: [Int, Int?] +//│ ║ ^^ +//│ ╟── expression of type `[]` does not match type `[Int, (Int)?]` +//│ ╟── Note: constraint arises from tuple type: +//│ ║ l.79: []: [Int, Int?] +//│ ╙── ^^^^^^^^^^^ +//│ [Int, (Int)?] +//│ res +//│ = [] + +:e +[1, 2, 3]: [Int, Int?] +//│ ╔══[ERROR] Type mismatch in type ascription: +//│ ║ l.92: [1, 2, 3]: [Int, Int?] +//│ ║ ^^^^^^^^^ +//│ ╟── tuple literal of type `[1, 2, 3]` does not match type `[Int, (Int)?]` +//│ ╟── Note: constraint arises from tuple type: +//│ ║ l.92: [1, 2, 3]: [Int, Int?] +//│ ╙── ^^^^^^^^^^^ +//│ [Int, (Int)?] +//│ res +//│ = [ 1, 2, 3 ] + +[1, 2]: [Int, Int, Int?] +//│ [Int, Int, (Int)?] +//│ res +//│ = [ 1, 2 ] + + +// * Harder case: +:p +fun foo(xs: [Int] & 'a) = xs : [Int, Int?] +//│ |#fun| |foo|(|xs|#:| |[|Int|]| |&| |'a|)| |#=| |xs| |#:| |[|Int|,| |Int|?|]| +//│ AST: TypingUnit(List(NuFunDef(None,Var(foo),None,List(),Left(Lam(Tup(List((Some(Var(xs)),Fld(_,App(Var(&),Tup(List((None,Fld(_,Tup(List((None,Fld(_,Var(Int))))))), (None,Fld(_,Var('a)))))))))),Asc(Var(xs),Tuple(List((None,Field(None,TypeName(Int),false)), (None,Field(None,TypeName(Int),true)))))))))) +//│ Parsed: fun foo = (xs: &([Int,], 'a,),) => xs : [Int, (Int)?]; +//│ fun foo: (xs: [Int]) -> [Int, (Int)?] + +// fun foo(xs: [Int] & 'a) = [xs : [Int, Int?], xs] + +:p +fun foo0(x?: Int) = if x is undefined then 0 else x + 1 +//│ |#fun| |foo0|(|x|#?:| |Int|)| |#=| |#if| |x| |is| |#undefined| |#then| |0| |#else| |x| |+| |1| +//│ AST: TypingUnit(List(NuFunDef(None,Var(foo0),None,List(),Left(Lam(Tup(List((Some(Var(x)),Fld(_,Var(Int))))),If(IfThen(App(Var(is),Tup(List((None,Fld(_,Var(x))), (None,Fld(_,UnitLit(true)))))),IntLit(0)),Some(App(Var(+),Tup(List((None,Fld(_,Var(x))), (None,Fld(_,IntLit(1))))))))))))) +//│ Parsed: fun foo0 = (x: Int,) => if (is(x, undefined,)) then 0 else +(x, 1,); +//│ fun foo0: (x: (Int)?) -> Int + +:p +fun foo1(x?) = if x is undefined then 0 else x + 1 +//│ |#fun| |foo1|(|x|?|)| |#=| |#if| |x| |is| |#undefined| |#then| |0| |#else| |x| |+| |1| +//│ AST: TypingUnit(List(NuFunDef(None,Var(foo1),None,List(),Left(Lam(Tup(List((None,Fld(_,Var(x))))),If(IfThen(App(Var(is),Tup(List((None,Fld(_,Var(x))), (None,Fld(_,UnitLit(true)))))),IntLit(0)),Some(App(Var(+),Tup(List((None,Fld(_,Var(x))), (None,Fld(_,IntLit(1))))))))))))) +//│ Parsed: fun foo1 = (x,) => if (is(x, undefined,)) then 0 else +(x, 1,); +//│ fun foo1: ((Int | ())?) -> Int + +foo1() +//│ Int +//│ res +//│ = 0 + +foo1(2) +//│ Int +//│ res +//│ = 3 + + + +// Error??? +fun foo0(x?:Int) = x + 1 +//│ fun foo0: (x: (Int)?) -> Int + +foo0(1) +//│ Int +//│ res +//│ = 2 + +foo0() +//│ Int +//│ res +//│ = NaN + +:e +fun foo1(x?) = x + 1 +//│ ╔══[ERROR] Type mismatch in operator application: +//│ ║ l.161: fun foo1(x?) = x + 1 +//│ ║ ^^^^^ +//│ ╟── reference of type `()` is not an instance of type `Int` +//│ ║ l.161: fun foo1(x?) = x + 1 +//│ ╙── ^ +//│ fun foo1: ((Int | ())?) -> (Int | error) + + +fun foo(x?) = if x is undefined then 0 else x + 1 +//│ fun foo: ((Int | ())?) -> Int diff --git a/shared/src/test/scala/mlscript/DiffTests.scala b/shared/src/test/scala/mlscript/DiffTests.scala index 6965d73c28..6ecf9a78f5 100644 --- a/shared/src/test/scala/mlscript/DiffTests.scala +++ b/shared/src/test/scala/mlscript/DiffTests.scala @@ -464,7 +464,10 @@ class DiffTests(state: DiffTests.State) && !mode.expectTypeErrors && diag.isInstanceOf[ErrorReport] && diag.source =:= Diagnostic.Typing) { output("TEST CASE FAILURE: There was an unexpected type error"); failures += globalLineNum } if (!allowParseErrors - && !mode.expectParseErrors && diag.isInstanceOf[ErrorReport] && (diag.source =:= Diagnostic.Lexing || diag.source =:= Diagnostic.Parsing)) + && !mode.expectParseErrors && diag.isInstanceOf[ErrorReport] && diag.source =:= Diagnostic.Lexing) + { output("TEST CASE FAILURE: There was an unexpected lexer error"); failures += globalLineNum } + if (!allowParseErrors + && !mode.expectParseErrors && diag.isInstanceOf[ErrorReport] && diag.source =:= Diagnostic.Parsing) { output("TEST CASE FAILURE: There was an unexpected parse error"); failures += globalLineNum } if (!allowCompileErrors && !mode.expectCompileErrors && diag.isInstanceOf[ErrorReport] && diag.source =:= Diagnostic.Compilation)