From 13a5f4e475c8bf3626e3a16b03938a21b2f92a06 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mika=C3=ABl=20Mayer?= <mikael.mayer@epfl.ch>
Date: Thu, 28 Apr 2016 13:14:57 +0200
Subject: [PATCH] Added varargs to methods in Leon + doc.

---
 .../frontends/scalac/CodeExtraction.scala     | 69 +++++++++++++++----
 src/sphinx/faq.rst                            | 49 +++++++++++++
 2 files changed, 104 insertions(+), 14 deletions(-)

diff --git a/src/main/scala/leon/frontends/scalac/CodeExtraction.scala b/src/main/scala/leon/frontends/scalac/CodeExtraction.scala
index b429f0ed5..93236439a 100644
--- a/src/main/scala/leon/frontends/scalac/CodeExtraction.scala
+++ b/src/main/scala/leon/frontends/scalac/CodeExtraction.scala
@@ -469,16 +469,52 @@ trait CodeExtraction extends ASTExtractors {
           }
       }
     }
+    
+    /** For every argument that is lazy, transforms it to a lambda. */
+    def defaultArgConvert(tfd: TypedFunDef, args: Seq[LeonExpr]): Seq[LeonExpr] = {
+      val argsByName = (tfd.fd.params zip args).map(p => if (isLazy(p._1)) Lambda(Seq(), p._2) else p._2)
+      argsByName
+    }
 
-    def getFunDef(sym: Symbol, pos: Position): FunDef = {
+    /** Returns the function associated to the symbol.
+     *  In the case of varargs, if the function is not found
+     *  and there are others with the same name in the same scope,
+     *  finds an equivalent function and converts the argument.*/
+    def getFunDef(sym: Symbol, pos: Position, allowFreeArgs: Boolean = true): (FunDef, (TypedFunDef, Seq[LeonExpr]) => Seq[LeonExpr]) = {
       defsToDefs.get(sym) match {
-        case Some(fd) => fd
+        case Some(fd) => (fd, defaultArgConvert)
         case None =>
-          outOfSubsetError(pos, "Function "+sym.name+" not properly defined?")
+           // Look for other functions accepting lists if they exist.
+          val similarFunction =
+            if(!allowFreeArgs) None
+            else (defsToDefs.find{ case (s, fd) => fd.id.name == sym.nameString &&
+              sym.owner == s.owner &&
+              fd.params.length == 1 && (fd.paramIds(0).getType match {
+                case AbstractClassType(ccd, tps) => ccd.id.name == "List"
+                case _ => false
+              })
+            })
+          similarFunction match {
+            case Some((sym, fd)) =>
+              val convertArgs = (tfd: TypedFunDef, elems: Seq[LeonExpr]) => {
+                val allowedType = fd.paramIds.head.getType match {
+                  case AbstractClassType(_, Seq(tpe)) => tfd.translated(tpe)
+                }
+                val cons = CaseClassType(libraryCaseClass(sym.pos, "leon.collection.Cons"), Seq(allowedType))
+                val nil  = CaseClassType(libraryCaseClass(sym.pos, "leon.collection.Nil"),  Seq(allowedType))
+                List(elems.foldRight(CaseClass(nil, Seq())) {
+                  case (e, ls) => CaseClass(cons, Seq(e, ls))
+                })
+              }
+              (fd, convertArgs)
+            case None =>
+              outOfSubsetError(pos, "Function "+sym.name+" not properly defined?")
+          }
       }
     }
 
     private var isMethod = Set[Symbol]()
+    private var ignoredMethods = Set[Symbol]()
     private var isMutator = Set[Symbol]()
     private var methodToClass = Map[FunDef, LeonClassDef]()
     private var classToInvariants = Map[Symbol, Set[Tree]]()
@@ -642,6 +678,12 @@ trait CodeExtraction extends ASTExtractors {
 
         case t if isIgnored(t.symbol) =>
           // ignore
+          d match {
+            // Special case so that we can find methods with varargs.
+            case ExFunctionDef(fsym, _, _, _, _) =>
+              ignoredMethods += fsym
+            case _ =>
+          }
 
         // Normal methods
         case t @ ExFunctionDef(fsym, _, _, _, _) =>
@@ -1033,7 +1075,7 @@ trait CodeExtraction extends ASTExtractors {
 
       case up@ExUnapplyPattern(s, args) =>
         implicit val p: Position = NoPosition
-        val fd = getFunDef(s, up.pos)
+        val (fd, _) = getFunDef(s, up.pos, allowFreeArgs=false)
         val (sub, ctx) = args.map (extractPattern(_)).unzip
         val unapplyMethod = defsToDefs(s)
         val formalTypes = tupleTypeWrap(
@@ -1618,7 +1660,7 @@ trait CodeExtraction extends ASTExtractors {
           elems.foldRight(CaseClass(nil, Seq())) {
             case (e, ls) => CaseClass(cons, Seq(extractTree(e), ls))
           }
-
+          
         case chr @ ExCharLiteral(c) =>
           CharLiteral(c)
 
@@ -1644,21 +1686,20 @@ trait CodeExtraction extends ASTExtractors {
 
           (rrec, sym.name.decoded, rargs) match {
             case (null, _, args) =>
-              val fd = getFunDef(sym, c.pos)
-
+              val (fd, convertArgs) = getFunDef(sym, c.pos, allowFreeArgs=true)
               val newTps = tps.map(t => extractType(t))
-              val argsByName = (fd.params zip args).map(p => if (isLazy(p._1)) Lambda(Seq(), p._2) else p._2)
-
-              FunctionInvocation(fd.typed(newTps), argsByName)
+              val tfd = fd.typed(newTps)
+ 
+              FunctionInvocation(tfd, convertArgs(tfd, args))
 
-            case (IsTyped(rec, ct: ClassType), _, args) if isMethod(sym) =>
-              val fd = getFunDef(sym, c.pos)
+            case (IsTyped(rec, ct: ClassType), methodName, args) if isMethod(sym) || ignoredMethods(sym) =>
+              val (fd, convertArgs) = getFunDef(sym, c.pos)
               val cd = methodToClass(fd)
 
               val newTps = tps.map(t => extractType(t))
-              val argsByName = (fd.params zip args).map(p => if (isLazy(p._1)) Lambda(Seq(), p._2) else p._2)
+              val tfd = fd.typed(newTps)
 
-              MethodInvocation(rec, cd, fd.typed(newTps), argsByName)
+              MethodInvocation(rec, cd, tfd, convertArgs(tfd, args))
 
             case (IsTyped(rec, ft: FunctionType), _, args) =>
               application(rec, args)
diff --git a/src/sphinx/faq.rst b/src/sphinx/faq.rst
index b3e1ec040..cb45de8b8 100644
--- a/src/sphinx/faq.rst
+++ b/src/sphinx/faq.rst
@@ -36,6 +36,55 @@ large, so, if the input list had the size `Int.MaxValue + 1`
 around and produce `Int.MinValue` (that is, -2^31), so the
 `ensuring` clause would not hold.
 
+Use variable number of arguments in Leon programs
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+I have defined an apply function that should accept a variable number of arguments.
+
+.. code-block:: scala
+
+    import leon.collection._
+    case class Element(children: List[Element]) {
+      def addChildren(c: Element*): Element = {
+        Element(children ++ c.toList)
+      }
+    }
+
+This does not compile in Leon, why?
+
+**Answer:** 
+
+To support variable number of arguments, do the following:
+
+.. code-block:: scala
+
+    import leon.collection._
+    import leon.annotation._
+    case class Element(children: List[Element]) {
+      @ignore
+      def addChildren(c: Element*): Element = {
+        var l: List[WebTree] = Nil[WebTree]()
+        for (e <- elems) {
+          l = Cons(e, l)
+        }
+        addChildren(l.reverse)
+      }
+      def addChildren(c: List[Element]): Element = {
+        Element(children ++ toList)
+      }
+    }
+
+This code is compatible with both Leon and Scala.
+At parsing time, when Leon encounters a call to
+`addChildren(a, b)`
+using the first method, it translates it to
+`addChildren(Cons(a, Cons(b, Nil())))`
+using the second method.
+When Scala encounters the same call,
+it executes the `@ignore`-d function and calls the second method.
+
+The reason is that the `scala.collection.Seq` used in the case of multiple arguments does not have a method `toList` that converts the sequence to a Leon `List`. Hence this workaround.
+
 Compiling Leon programs to bytecode
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-- 
GitLab