Skip to content

Commit 0e64e67

Browse files
authored
Merge branch 'mlscript' into tailrec
2 parents 31bd2f5 + f6be56b commit 0e64e67

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+4437
-3534
lines changed

bin/mlscript-opt.js

Lines changed: 3125 additions & 3025 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

build.sbt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ ThisBuild / scalacOptions ++= Seq(
1010
"-deprecation",
1111
"-feature",
1212
"-unchecked",
13+
"-language:higherKinds",
14+
if (insideCI.value) "-Wconf:any:error"
15+
else "-Wconf:any:warning",
1316
)
1417

1518
lazy val root = project.in(file("."))
@@ -23,14 +26,9 @@ lazy val mlscript = crossProject(JSPlatform, JVMPlatform).in(file("."))
2326
.settings(
2427
name := "mlscript",
2528
scalacOptions ++= Seq(
26-
"-language:higherKinds",
2729
"-Ywarn-value-discard",
2830
"-Ypatmat-exhaust-depth:160",
2931
),
30-
scalacOptions ++= {
31-
if (insideCI.value) Seq("-Wconf:any:error")
32-
else Seq("-Wconf:any:warning")
33-
},
3432
wartremoverWarnings ++= Warts.allBut(
3533
Recursion, Throw, Nothing, Return, While, IsInstanceOf,
3634
Var, MutableDataStructures, NonUnitStatements,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
:NewDefs
2+
3+

js/src/main/scala/Main.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ object Main {
159159
|""".stripMargin
160160

161161
val backend = new JSWebBackend()
162-
val (lines, resNames) = backend(pgrm, true)
162+
val (lines, resNames) = backend(pgrm, newDefs = true)
163163
val code = lines.mkString("\n")
164164

165165
// TODO: add a toggle button to show js code

shared/src/main/scala/mlscript/ConstraintSolver.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ class ConstraintSolver extends NormalForms { self: Typer =>
9393
def handle(virtualMembers: Map[Str, NuMember]): Opt[FieldType] =
9494
virtualMembers.get(fld.name) match {
9595
case S(d: TypedNuFun) =>
96+
if (d.fd.isLetOrLetRec)
97+
err(msg"Let binding '${d.name}' cannot tbe accessed as a field" -> fld.toLoc ::
98+
msg"Use a `val` declaration to make it a field" -> d.fd.toLoc ::
99+
Nil)
96100
val ty = d.typeSignature
97101
S(
98102
if (d.fd.isMut) FieldType(S(ty), ty)(d.prov)

shared/src/main/scala/mlscript/JSBackend.scala

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import scala.collection.mutable.{Set => MutSet}
88
import scala.util.control.NonFatal
99
import scala.util.chaining._
1010

11-
abstract class JSBackend(allowUnresolvedSymbols: Bool) {
11+
abstract class JSBackend {
1212
def oldDefs: Bool
1313

1414
protected implicit class TermOps(term: Term) {
@@ -146,10 +146,7 @@ abstract class JSBackend(allowUnresolvedSymbols: Bool) {
146146
return Left(CodeGenError(s"type alias ${name} is not a valid expression"))
147147
case S(_) => lastWords("register mismatch in scope")
148148
case N =>
149-
if (allowUnresolvedSymbols)
150-
JSIdent(name)
151-
else
152-
return Left(CodeGenError(s"unresolved symbol ${name}"))
149+
return Left(CodeGenError(s"unresolved symbol ${name}"))
153150
}
154151
})
155152

@@ -1317,7 +1314,7 @@ abstract class JSBackend(allowUnresolvedSymbols: Bool) {
13171314

13181315
}
13191316

1320-
class JSWebBackend extends JSBackend(allowUnresolvedSymbols = true) {
1317+
class JSWebBackend extends JSBackend {
13211318
def oldDefs = false
13221319

13231320
// Name of the array that contains execution results
@@ -1448,7 +1445,7 @@ class JSWebBackend extends JSBackend(allowUnresolvedSymbols = true) {
14481445
if (newDefs) generateNewDef(pgrm) else generate(pgrm)
14491446
}
14501447

1451-
abstract class JSTestBackend extends JSBackend(allowUnresolvedSymbols = false) {
1448+
abstract class JSTestBackend extends JSBackend {
14521449

14531450
private val lastResultSymbol = topLevelScope.declareValue("res", Some(false), false, N)
14541451
private val resultIdent = JSIdent(lastResultSymbol.runtimeName)

shared/src/main/scala/mlscript/NuTypeDefs.scala

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ class NuTypeDefs extends ConstraintSolver { self: Typer =>
425425
def symbolicName: Opt[Str] = fd.symbolicNme.map(_.name)
426426
def toLoc: Opt[Loc] = fd.toLoc
427427
lazy val prov = TypeProvenance(toLoc, "member")
428-
def isPublic = true // TODO
428+
def isPublic: Bool = !fd.isLetOrLetRec
429429
lazy val typeSignature: ST =
430430
if (fd.isMut) bodyType
431431
else PolymorphicType.mk(level, bodyType)
@@ -553,6 +553,8 @@ class NuTypeDefs extends ConstraintSolver { self: Typer =>
553553
val funSigs = MutMap.empty[Str, NuFunDef]
554554
val implems = decls.filter {
555555
case fd @ NuFunDef(_, nme, snme, tparams, R(rhs)) =>
556+
if (fd.isLetOrLetRec)
557+
err(s"`let` bindings must have a right-hand side", fd.toLoc)
556558
funSigs.updateWith(nme.name) {
557559
case S(s) =>
558560
err(s"A type signature for '${nme.name}' was already given", fd.toLoc)
@@ -985,7 +987,7 @@ class NuTypeDefs extends ConstraintSolver { self: Typer =>
985987
lazy val (typedSignatures, funImplems) : (Ls[(NuFunDef, ST)], Ls[NuFunDef]) = decl match {
986988
case td: NuTypeDef => ctx.nest.nextLevel { implicit ctx =>
987989
val (signatures, rest) = td.body.entities.partitionMap {
988-
case fd @ NuFunDef(N | S(false), nme, snme, tparams, R(rhs)) => // currently `val`s are encoded with `S(false)`
990+
case fd @ NuFunDef(_, nme, snme, tparams, R(rhs)) if !fd.isLetOrLetRec =>
989991
L((fd, rhs))
990992
// TODO also pick up signature off implems with typed params/results
991993
case s => R(s)
@@ -1045,7 +1047,7 @@ class NuTypeDefs extends ConstraintSolver { self: Typer =>
10451047
lazy val allFields: Set[Var] = decl match {
10461048
case td: NuTypeDef =>
10471049
(td.params.getOrElse(Tup(Nil)).fields.iterator.flatMap(_._1) ++ td.body.entities.iterator.collect {
1048-
case fd: NuFunDef => fd.nme
1050+
case fd: NuFunDef if !fd.isLetOrLetRec => fd.nme
10491051
}).toSet ++ inheritedFields ++ tparams.map {
10501052
case (nme @ TypeName(name), tv, _) =>
10511053
Var(td.nme.name+"#"+name).withLocOf(nme)
@@ -1280,13 +1282,18 @@ class NuTypeDefs extends ConstraintSolver { self: Typer =>
12801282
}
12811283
if (!td.isDecl && td.kind =/= Trt && !td.isAbstract) {
12821284
toImplement.foreach { m =>
1285+
def mkErr(postfix: Ls[Message -> Opt[Loc]]) = err(
1286+
msg"Member `${m.name}` is declared (or its declaration is inherited) but is not implemented in `${
1287+
td.nme.name}`" -> td.nme.toLoc ::
1288+
msg"Declared here:" -> m.toLoc ::
1289+
postfix)
12831290
implemsMap.get(m.name) match {
1284-
case S(_) =>
1285-
case N =>
1286-
err(msg"Member `${m.name}` is declared (or its declaration is inherited) but is not implemented in `${
1287-
td.nme.name}`" -> td.nme.toLoc ::
1288-
msg"Declared here:" -> m.toLoc ::
1291+
case S(impl) =>
1292+
if (impl.isPrivate) mkErr(
1293+
msg"Note: ${impl.kind.str} member `${m.name}` is private and cannot be used as a valid implementation" -> impl.toLoc ::
12891294
Nil)
1295+
case N =>
1296+
mkErr(Nil)
12901297
}
12911298
}
12921299
}
@@ -1309,27 +1316,35 @@ class NuTypeDefs extends ConstraintSolver { self: Typer =>
13091316
println(s"Checking overriding for ${m} against ${sigMap.get(m.name)}...")
13101317
(m, sigMap.get(m.name)) match {
13111318
case (_, N) =>
1312-
case (m: TypedNuTermDef, S(fun: TypedNuTermDef)) => fun match {
1319+
case (m: TypedNuTermDef, S(sig: TypedNuTermDef)) => sig match {
13131320
// * If the implementation and the declaration are in the same class,
13141321
// * it does not require to be virtual.
1315-
case _ if fun.isPrivate => () // * Private members are not actually inherited
1316-
case td: TypedNuFun if (!td.fd.isVirtual && !clsSigns.contains(fun)) =>
1317-
err(msg"${m.kind.str.capitalize} member `${m.name
1318-
}` is not virtual and cannot be overridden" -> m.toLoc ::
1319-
msg"Originally declared here:" -> fun.toLoc ::
1322+
case _ if sig.isPrivate => () // * Private members are not actually inherited
1323+
case td: TypedNuFun if (!td.fd.isVirtual && !clsSigns.contains(sig)) =>
1324+
err(msg"${m.kind.str.capitalize} member '${m.name
1325+
}' is not virtual and cannot be overridden" -> m.toLoc ::
1326+
msg"Originally declared here:" -> sig.toLoc ::
13201327
Nil)
13211328
case p: NuParam if (!p.isVirtual && !clsSigns.contains(p)) =>
1322-
err(msg"Inherited parameter named `${m.name
1323-
}` is not virtual and cannot be overridden" -> m.toLoc ::
1324-
msg"Originally declared here:" -> fun.toLoc ::
1329+
err(msg"Inherited parameter named '${m.name
1330+
}' is not virtual and cannot be overridden" -> m.toLoc ::
1331+
msg"Originally declared here:" -> sig.toLoc ::
13251332
Nil)
13261333
case _ =>
1334+
m match {
1335+
case fun: TypedNuFun if (fun.fd.isLetOrLetRec) =>
1336+
err(msg"Cannot implement ${m.kind.str} member '${m.name
1337+
}' with a let binding" -> m.toLoc ::
1338+
msg"Originally declared here:" -> sig.toLoc ::
1339+
Nil)
1340+
case _ =>
1341+
}
13271342
val mSign = m.typeSignature
13281343
implicit val prov: TP = mSign.prov
1329-
constrain(mSign, fun.typeSignature)
1344+
constrain(mSign, sig.typeSignature)
13301345
}
13311346
case (_, S(that)) =>
1332-
err(msg"${m.kind.str.capitalize} member `${m.name}` cannot override ${
1347+
err(msg"${m.kind.str.capitalize} member '${m.name}' cannot override ${
13331348
that.kind.str} member of the same name declared in parent" -> td.toLoc ::
13341349
msg"Originally declared here:" -> that.toLoc ::
13351350
Nil)
@@ -1714,9 +1729,11 @@ class NuTypeDefs extends ConstraintSolver { self: Typer =>
17141729
overrideCheck(clsSigns, ifaceMembers, clsSigns)
17151730
}()
17161731

1717-
implemCheck(impltdMems,
1718-
(clsSigns.iterator ++ ifaceMembers.iterator)
1719-
.distinctBy(_.name).filterNot(_.isImplemented).toList)
1732+
trace(s"Checking signature implementations...") {
1733+
implemCheck(impltdMems,
1734+
(clsSigns.iterator ++ ifaceMembers.iterator)
1735+
.distinctBy(_.name).filterNot(_.isImplemented).toList)
1736+
}()
17201737

17211738
val allMembers =
17221739
(ifaceMembers ++ impltdMems).map(d => d.name -> d).toMap ++ typedSignatureMembers

shared/src/main/scala/mlscript/codegen/Codegen.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ class JSMember(`object`: JSExpr, property: JSExpr) extends JSExpr {
589589
override def precedence: Int = 20
590590
override def toSourceCode: SourceCode =
591591
`object`.toSourceCode.parenthesized(
592-
`object`.precedence < precedence || `object`.isInstanceOf[JSRecord]
592+
`object`.precedence < precedence || `object`.isInstanceOf[JSRecord] || `object`.isInstanceOf[JSNew]
593593
) ++ SourceCode("[") ++ property.toSourceCode ++ SourceCode("]")
594594

595595
override def isSimple: Bool = `object`.isSimple
@@ -602,7 +602,7 @@ object JSMember {
602602
class JSField(`object`: JSExpr, val property: JSIdent) extends JSMember(`object`, property) {
603603
override def toSourceCode: SourceCode =
604604
`object`.toSourceCode.parenthesized(
605-
`object`.precedence < precedence || `object`.isInstanceOf[JSRecord]
605+
`object`.precedence < precedence || `object`.isInstanceOf[JSRecord] || `object`.isInstanceOf[JSNew]
606606
) ++ SourceCode(
607607
if (JSField.isValidFieldName(property.name)) {
608608
s".${property.name}"

shared/src/main/scala/mlscript/codegen/Scope.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class Scope(val name: Str, enclosing: Opt[Scope]) {
8686
"Wildcard",
8787
"NoCases",
8888
"discard",
89+
"window",
8990
) foreach { name =>
9091
register(BuiltinSymbol(name, name))
9192
}

shared/src/main/scala/mlscript/syntax.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ final case class NuTypeDef(
215215
}
216216

217217
final case class NuFunDef(
218-
isLetRec: Opt[Bool], // None means it's a `fun`, which is always recursive; Some means it's a `let` or `val`
218+
isLetRec: Opt[Bool], // None means it's a `fun`, which is always recursive; Some means it's a `let`/`let rec` or `val`
219219
nme: Var,
220220
symbolicNme: Opt[Var],
221221
tparams: Ls[TypeName],
@@ -232,7 +232,9 @@ final case class NuFunDef(
232232
val body: Located = rhs.fold(identity, identity)
233233
def kind: DeclKind = Val
234234
val abstractLoc: Opt[Loc] = None
235-
235+
236+
def isLetOrLetRec: Bool = isLetRec.isDefined && !genField
237+
236238
// If the member has no implementation, it is virtual automatically
237239
def isVirtual: Bool = virtualLoc.nonEmpty || rhs.isRight
238240

shared/src/test/diff/codegen/MutFields.mls

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -173,15 +173,15 @@ t.x
173173

174174
class Cont
175175
class MySumCont extends Cont {
176-
let init(n) = n + 1
176+
val init(n) = n + 1
177177
mut val pc = init(0)
178178
}
179179
//│ class Cont {
180180
//│ constructor()
181181
//│ }
182182
//│ class MySumCont extends Cont {
183183
//│ constructor()
184-
//│ let init: Int -> Int
184+
//│ val init: Int -> Int
185185
//│ mut val pc: Int
186186
//│ }
187187

@@ -200,22 +200,21 @@ if f(false) is MySumCont then 1 else 2
200200
//│ res
201201
//│ = 1
202202

203-
// * FIXME reject: `let`is not accessible here
204203
new MySumCont().init
205204
//│ Int -> Int
206205
//│ res
207-
//│ = undefined
206+
//│ = [Function (anonymous)]
208207

209208

210209
:pe
211210
:e
212211
:ng
213212
class Test(mut val x: Int)
214213
//│ ╔══[PARSE ERROR] Unexpected 'val' keyword in expression position
215-
//│ ║ l.213: class Test(mut val x: Int)
214+
//│ ║ l.212: class Test(mut val x: Int)
216215
//│ ╙── ^^^
217216
//│ ╔══[ERROR] Unsupported field specification
218-
//│ ║ l.213: class Test(mut val x: Int)
217+
//│ ║ l.212: class Test(mut val x: Int)
219218
//│ ╙── ^
220219
//│ class Test()
221220

@@ -228,13 +227,13 @@ module Foo { val x = 0 }
228227
:e
229228
fun test = set Foo.x = 1
230229
//│ ╔══[ERROR] Type mismatch in assignment:
231-
//│ ║ l.229: fun test = set Foo.x = 1
230+
//│ ║ l.228: fun test = set Foo.x = 1
232231
//│ ║ ^^^^^^^^^^^^^
233232
//│ ╟── member of type `0` is not mutable
234-
//│ ║ l.223: module Foo { val x = 0 }
233+
//│ ║ l.222: module Foo { val x = 0 }
235234
//│ ║ ^^^^^
236235
//│ ╟── but it flows into assigned field with expected type `?x`
237-
//│ ║ l.229: fun test = set Foo.x = 1
236+
//│ ║ l.228: fun test = set Foo.x = 1
238237
//│ ╙── ^^
239238
//│ fun test: ()
240239

shared/src/test/diff/codegen/New.mls

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,82 @@ c()
107107
//│ = C {}
108108

109109

110+
:js
111+
class X(val a: Int)
112+
new X(1).a
113+
//│ class X(a: Int)
114+
//│ Int
115+
//│ // Prelude
116+
//│ class TypingUnit10 {
117+
//│ #X;
118+
//│ constructor() {
119+
//│ }
120+
//│ get X() {
121+
//│ const qualifier = this;
122+
//│ if (this.#X === undefined) {
123+
//│ class X {
124+
//│ #a;
125+
//│ get a() { return this.#a; }
126+
//│ constructor(a) {
127+
//│ this.#a = a;
128+
//│ }
129+
//│ static
130+
//│ unapply(x) {
131+
//│ return [x.#a];
132+
//│ }
133+
//│ };
134+
//│ this.#X = ((a) => Object.freeze(new X(a)));
135+
//│ this.#X.class = X;
136+
//│ this.#X.unapply = X.unapply;
137+
//│ }
138+
//│ return this.#X;
139+
//│ }
140+
//│ }
141+
//│ const typing_unit10 = new TypingUnit10;
142+
//│ globalThis.X = typing_unit10.X;
143+
//│ // Query 1
144+
//│ res = new X.class(1).a;
145+
//│ // End of generated code
146+
//│ res
147+
//│ = 1
148+
149+
:js
150+
class X {
151+
val a = 1
152+
}
153+
(new X).a
154+
//│ class X {
155+
//│ constructor()
156+
//│ val a: 1
157+
//│ }
158+
//│ 1
159+
//│ // Prelude
160+
//│ class TypingUnit11 {
161+
//│ #X;
162+
//│ constructor() {
163+
//│ }
164+
//│ get X() {
165+
//│ const qualifier = this;
166+
//│ if (this.#X === undefined) {
167+
//│ class X {
168+
//│ #a;
169+
//│ get a() { return this.#a; }
170+
//│ constructor() {
171+
//│ this.#a = 1;
172+
//│ const a = this.#a;
173+
//│ }
174+
//│ };
175+
//│ this.#X = X;
176+
//│ }
177+
//│ return this.#X;
178+
//│ }
179+
//│ }
180+
//│ const typing_unit11 = new TypingUnit11;
181+
//│ globalThis.X = typing_unit11.X;
182+
//│ // Query 1
183+
//│ res = (new X).a;
184+
//│ // End of generated code
185+
//│ res
186+
//│ = 1
187+
188+

0 commit comments

Comments
 (0)