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

Optional arguments #223

Open
wants to merge 15 commits into
base: mlscript
Choose a base branch
from
3 changes: 2 additions & 1 deletion shared/src/main/scala/mlscript/NewLexer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ class NewLexer(origin: Origin, raise: Diagnostic => Unit, dbg: Bool) {
";",
// ",",
"#",
"`"
"`",
// ".",
// "<",
// ">",
"?:"
)

private val isAlphaOp = Set(
Expand Down
27 changes: 12 additions & 15 deletions shared/src/main/scala/mlscript/NewParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1427,37 +1427,34 @@ abstract class NewParser(origin: Origin, tokens: Ls[Stroken -> Loc], newDefs: Bo
S(l0)
case _ => N
}
val (argName, argOpt) = yeetSpaces match {
case (IDENT(idStr, false), l0) :: (IDENT("?:", true), l1) :: _ =>
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))), S(l1))
(S(Var(idStr).withLoc(S(l0))), true)
case (IDENT(idStr, false), l0) :: (KEYWORD(":"), _) :: _ => // TODO: | ...
consume
consume
(S(Var(idStr).withLoc(S(l0))), N)
case (LITVAL(IntLit(i)), l0) :: (IDENT("?:", true), l1) :: _ => // TODO: | ...
consume
consume
(S(Var(i.toString).withLoc(S(l0))), S(l1))
(S(Var(idStr).withLoc(S(l0))), false)
case (LITVAL(IntLit(i)), l0) :: (KEYWORD(":"), _) :: _ => // TODO: | ...
consume
consume
(S(Var(i.toString).withLoc(S(l0))), N)
case _ => (N, 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: ${argOpt.isDefined}")

val isOpt = cur match {
// 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 = body.map(Fld(FldFlags(argMut.isDefined, argSpec.isDefined, argOpt.isDefined || isOpt, 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)) =>
Expand Down
59 changes: 56 additions & 3 deletions shared/src/test/diff/nu/OptionalArgs.mls
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
:NewDefs

fun (??) oops(a, b) = a + b
fun (??) oops(a123, b123) = a123 + b123
//│ fun (??) oops: (Int, Int) -> Int

1 ?? 2
Expand All @@ -9,8 +9,8 @@ fun (??) oops(a, b) = a + b
//│ = 3


fun f1(a?: Int, b?: Int) = a + b
//│ fun f1: (a: (Int)?, b: (Int)?) -> Int
fun f1(aOpt?: Int, bOpt?: Int) = aOpt + bOpt
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LPTK how can I handle this case?
should it return an error requiring a and b defined before a + b?

Copy link
Contributor

@LPTK LPTK Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea of this approach is that if your parameter x of type T is optional (annotated with ?), then while the corresponding parameter type remains T when viewed from the outside (but of course, the parameter is still marked as optional), as in (? T) -> Int, the visible type of x inside the body is T | undefined. That's all we need for the feature to work correctly.

//│ fun f1: (aOpt: (Int)?, bOpt: (Int)?) -> Int

fun f1(a: Int, b: Int) = a + b
//│ fun f1: (a: Int, b: Int) -> Int
Expand Down Expand Up @@ -116,3 +116,56 @@ 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(2)
//│ Int
//│ res
//│ = 3

fun foo0(x?:Int) = x + 1
//│ fun foo0: (x: (Int)?) -> Int

:e
fun foo1(x?) = x + 1
//│ ╔══[ERROR] Type mismatch in operator application:
//│ ║ l.143: fun foo1(x?) = x + 1
//│ ║ ^^^^^
//│ ╟── reference of type `()` is not an instance of type `Int`
//│ ║ l.143: 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


fun foo: (x: Int) -> Int
fun foo(x?) = if x is undefined then 0 else x + 1
//│ fun foo: ((Int | ())?) -> Int
//│ fun foo: (x: Int) -> Int

fun foo: (x: Int?) -> Int
fun foo(x) = x + 1
//│ fun foo: Int -> Int
//│ fun foo: (x: (Int)?) -> Int

fun f(x) = (x : [Int, Int?]) : ['a, 'b]
//│ fun f: ([Int, (Int)?]) -> [Int, Int]

fun f(x) = (x : [Int, Int?]) : ['a, 'b]
//│ fun f: ([Int, (Int)?]) -> [Int, Int]
Loading