diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala | 3 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/ast/parser/NewScanners.scala | 83 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala | 3 | ||||
-rwxr-xr-x | src/compiler/scala/tools/nsc/javac/JavaScanners.scala | 5 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/matching/TransMatcher.scala | 9 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/symtab/Definitions.scala | 2 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/symtab/Symbols.scala | 13 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/symtab/Types.scala | 2 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala | 32 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/transform/TailCalls.scala | 11 | ||||
-rw-r--r-- | src/library/scala/Iterator.scala | 29 | ||||
-rw-r--r-- | src/library/scala/List.scala | 14 | ||||
-rw-r--r-- | src/library/scala/annotation/switch.scala | 17 | ||||
-rw-r--r-- | src/library/scala/annotation/tailrec.scala | 16 | ||||
-rwxr-xr-x | src/library/scalax/collection/immutable/Stream.scala | 2 |
15 files changed, 162 insertions, 79 deletions
diff --git a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala index 7a725bd82e..55695f725a 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/MarkupParsers.scala @@ -10,6 +10,7 @@ import scala.collection.mutable import scala.tools.nsc.util.{Position,NoPosition,SourceFile} import scala.xml.{Text, TextBuffer} import SourceFile.{SU,LF} +import scala.annotation.switch /** This trait ... * @@ -238,7 +239,7 @@ trait MarkupParsers {self: Parsers => val base = if (hex) 16 else 10 var i = 0 while (ch != ';') { - ch match { + (ch: @switch) match { case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => i = i * base + ch.asDigit case 'a' | 'b' | 'c' | 'd' | 'e' | 'f' diff --git a/src/compiler/scala/tools/nsc/ast/parser/NewScanners.scala b/src/compiler/scala/tools/nsc/ast/parser/NewScanners.scala index f9151a4b8d..db547b81db 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/NewScanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/NewScanners.scala @@ -8,6 +8,7 @@ package scala.tools.nsc.ast.parser import scala.tools.nsc.util.SourceFile._ import scala.tools.nsc.util._ +import scala.annotation.switch trait NewScanners { val global : Global @@ -586,10 +587,25 @@ trait NewScanners { } def isDigit(c : Char) : Boolean = digit2int(c, 10) >= 0 - def isIdentifierStart(c: Char): Boolean = c match { - case 'A' | 'B' | 'C' | 'D' | 'E' | - 'F' | 'G' | 'H' | 'I' | 'J' | - 'K' | 'L' | 'M' | 'N' | 'O' | + def isIdentifierStart(c: Char): Boolean = (c: @switch) match { + case 'A' | 'B' | 'C' | 'D' | 'E' | + 'F' | 'G' | 'H' | 'I' | 'J' | + 'K' | 'L' | 'M' | 'N' | 'O' | + 'P' | 'Q' | 'R' | 'S' | 'T' | + 'U' | 'V' | 'W' | 'X' | 'Y' | + 'Z' | '$' | '_' | + 'a' | 'b' | 'c' | 'd' | 'e' | + 'f' | 'g' | 'h' | 'i' | 'j' | + 'k' | 'l' | 'm' | 'n' | 'o' | + 'p' | 'q' | 'r' | 's' | 't' | + 'u' | 'v' | 'w' | 'x' | 'y' | // scala-mode: need to understand multi-line case patterns + 'z' => true + case _ => false + } + def isIdentifierPart(c: Char) : Boolean = (c: @switch) match { + case ('A' | 'B' | 'C' | 'D' | 'E' | + 'F' | 'G' | 'H' | 'I' | 'J' | + 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T' | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z' | '$' | '_' | @@ -597,35 +613,18 @@ trait NewScanners { 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | - 'u' | 'v' | 'w' | 'x' | 'y' | // scala-mode: need to understand multi-line case patterns - 'z' => true - case _ => false - } - def isIdentifierPart(c: Char) : Boolean = c match { - case ('A' | 'B' | 'C' | 'D' | 'E' | - 'F' | 'G' | 'H' | 'I' | 'J' | - 'K' | 'L' | 'M' | 'N' | 'O' | - 'P' | 'Q' | 'R' | 'S' | 'T' | - 'U' | 'V' | 'W' | 'X' | 'Y' | - 'Z' | '$' | '_' | - 'a' | 'b' | 'c' | 'd' | 'e' | - 'f' | 'g' | 'h' | 'i' | 'j' | - 'k' | 'l' | 'm' | 'n' | 'o' | - 'p' | 'q' | 'r' | 's' | 't' | - 'u' | 'v' | 'w' | 'x' | 'y' | - 'z') => true - case '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9' => true - case c if Character.isUnicodeIdentifierPart(c) => true - case _ => false + 'u' | 'v' | 'w' | 'x' | 'y' | + 'z') => true + case '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9' => true + case c => Character.isUnicodeIdentifierPart(c) } //isIdentifierStart(c) || isDigit(c) || isUnicodeIdentifierPart(c) - def isOperatorPart(c : Char) : Boolean = c match { - case '~' | '!' | '@' | '#' | '%' | - '^' | '*' | '+' | '-' | '<' | - '>' | '?' | ':' | '=' | '&' | - '|' | '/' | '\\' => true - case c if isSpecial(c) => true - case _ => false + def isOperatorPart(c : Char) : Boolean = (c: @switch) match { + case '~' | '!' | '@' | '#' | '%' | + '^' | '*' | '+' | '-' | '<' | + '>' | '?' | ':' | '=' | '&' | + '|' | '/' | '\\' => true + case c => isSpecial(c) } private def getOperatorRest(implicit in : CoreScannerInput) : Unit = { @@ -719,18 +718,18 @@ trait NewScanners { in.error(offset, "Invalid literal number") code } - def inFirstOfStat(token: Int) = token match { - case EOF | /*CASE |*/ CATCH | ELSE | EXTENDS | FINALLY | MATCH | REQUIRES | WITH | YIELD | - COMMA | SEMI | NEWLINE | NEWLINES | DOT | COLON | EQUALS | ARROW | /* | USCORE - bug #756 */ - LARROW | SUBTYPE | VIEWBOUND | SUPERTYPE | HASH | // todo: add LBRACKET - RPAREN | RBRACKET | RBRACE => false - case _ => true + // todo: add LBRACKET + def inFirstOfStat(token: Int) = (token: @switch) match { + case EOF | CATCH | ELSE | EXTENDS | FINALLY | MATCH | REQUIRES | WITH | YIELD | + COMMA | SEMI | NEWLINE | NEWLINES | DOT | COLON | EQUALS | ARROW | LARROW | + SUBTYPE | VIEWBOUND | SUPERTYPE | HASH | RPAREN | RBRACKET | RBRACE => false + case _ => true } - def inLastOfStat(token: Int) = token match { - case CHARLIT | INTLIT | LONGLIT | FLOATLIT | DOUBLELIT | STRINGLIT | SYMBOLLIT | - IDENTIFIER | BACKQUOTED_IDENT | THIS | NULL | TRUE | FALSE | RETURN | USCORE | - TYPE | XMLSTART | RPAREN | RBRACKET | RBRACE => true - case _ => false + def inLastOfStat(token: Int) = (token: @switch) match { + case CHARLIT | INTLIT | LONGLIT | FLOATLIT | DOUBLELIT | STRINGLIT | SYMBOLLIT | + IDENTIFIER | BACKQUOTED_IDENT | THIS | NULL | TRUE | FALSE | RETURN | USCORE | + TYPE | XMLSTART | RPAREN | RBRACKET | RBRACE => true + case _ => false } def digit(c : Char, radix : Int) = c match { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index 5de7e88300..4722210eb6 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -217,10 +217,9 @@ abstract class GenJVM extends SubComponent { addGenericSignature(jclass, c.symbol) addAnnotations(jclass, c.symbol.attributes) - emitClass(jclass, c.symbol) - if (c.symbol.attributes.exists(_.atp.typeSymbol == BeanInfoAttr)) + if (c.symbol hasAttribute BeanInfoAttr) genBeanInfoClass(c) } diff --git a/src/compiler/scala/tools/nsc/javac/JavaScanners.scala b/src/compiler/scala/tools/nsc/javac/JavaScanners.scala index cddbfcfd60..61443ebcdc 100755 --- a/src/compiler/scala/tools/nsc/javac/JavaScanners.scala +++ b/src/compiler/scala/tools/nsc/javac/JavaScanners.scala @@ -8,6 +8,7 @@ package scala.tools.nsc.javac import scala.tools.nsc.util._ import SourceFile.{LF, FF, CR, SU} import JavaTokens._ +import scala.annotation.switch trait JavaScanners { val global : Global @@ -347,7 +348,7 @@ trait JavaScanners { in.next case _ => pos = in.cpos // Position.encode(in.cline, in.ccol) - in.ch match { + (in.ch: @switch) match { case 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | @@ -702,7 +703,7 @@ trait JavaScanners { private def getIdentRest { while (true) { - in.ch match { + (in.ch: @switch) match { case 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | diff --git a/src/compiler/scala/tools/nsc/matching/TransMatcher.scala b/src/compiler/scala/tools/nsc/matching/TransMatcher.scala index ba35cc79c9..967c0938a6 100644 --- a/src/compiler/scala/tools/nsc/matching/TransMatcher.scala +++ b/src/compiler/scala/tools/nsc/matching/TransMatcher.scala @@ -38,7 +38,14 @@ trait TransMatcher { self: transform.ExplicitOuter with PatternNodes with Parall /** handles all translation of pattern matching */ - def handlePattern(selector: Tree, cases: List[CaseDef], doCheckExhaustive: Boolean, owner: Symbol, handleOuter: Tree => Tree)(implicit typer : Typer): Tree = { + def handlePattern( + selector: Tree, + cases: List[CaseDef], + doCheckExhaustive: Boolean, + owner: Symbol, + handleOuter: Tree => Tree) + (implicit typer : Typer): Tree = + { DBG("****") DBG("**** initalize, selector = "+selector+" selector.tpe = "+selector.tpe) DBG("**** doCheckExhaustive == "+doCheckExhaustive) diff --git a/src/compiler/scala/tools/nsc/symtab/Definitions.scala b/src/compiler/scala/tools/nsc/symtab/Definitions.scala index 0c37a9574b..0cecbc03c4 100644 --- a/src/compiler/scala/tools/nsc/symtab/Definitions.scala +++ b/src/compiler/scala/tools/nsc/symtab/Definitions.scala @@ -151,6 +151,8 @@ trait Definitions { var ByNameParamClass: Symbol = _ //var UnsealedClass: Symbol = _ lazy val UncheckedClass: Symbol = getClass("scala.unchecked") + lazy val TailrecClass: Symbol = getClass("scala.annotation.tailrec") + lazy val SwitchClass: Symbol = getClass("scala.annotation.switch") var EqualsPatternClass: Symbol = _ diff --git a/src/compiler/scala/tools/nsc/symtab/Symbols.scala b/src/compiler/scala/tools/nsc/symtab/Symbols.scala index 391dd69059..a0dd86c79f 100644 --- a/src/compiler/scala/tools/nsc/symtab/Symbols.scala +++ b/src/compiler/scala/tools/nsc/symtab/Symbols.scala @@ -91,16 +91,12 @@ trait Symbols { else -1 } - var attributes: List[AnnotationInfo] = List() - + // XXX the attributes logic is essentially duplicated across Symbols and Types + var attributes: List[AnnotationInfo] = Nil def setAttributes(attrs: List[AnnotationInfo]): this.type = { this.attributes = attrs; this } /** Does this symbol have an attribute of the given class? */ - def hasAttribute(cls: Symbol): Boolean = - attributes.exists { - case AnnotationInfo(tp, _, _) if tp.typeSymbol == cls => true - case _ => false - } + def hasAttribute(cls: Symbol) = attributes exists { _.atp.typeSymbol == cls } var privateWithin: Symbol = _ // set when symbol has a modifier of the form private[X], NoSymbol otherwise. @@ -347,8 +343,7 @@ trait Symbols { } } - def isDeprecated = - attributes exists (attr => attr.atp.typeSymbol == DeprecatedAttr) + def isDeprecated = hasAttribute(DeprecatedAttr) /** Does this symbol denote a wrapper object of the interpreter or its class? */ final def isInterpreterWrapper = diff --git a/src/compiler/scala/tools/nsc/symtab/Types.scala b/src/compiler/scala/tools/nsc/symtab/Types.scala index 4909628f4a..828a59f35a 100644 --- a/src/compiler/scala/tools/nsc/symtab/Types.scala +++ b/src/compiler/scala/tools/nsc/symtab/Types.scala @@ -775,6 +775,8 @@ trait Types { /** Return the attributes on this type */ val attributes: List[AnnotationInfo] = Nil + /** Test for the presence of an attribute */ + def hasAttribute(clazz: Symbol) = attributes exists { _.atp.typeSymbol == clazz } /** Add an attribute to this type */ def withAttribute(attrib: AnnotationInfo) = withAttributes(List(attrib)) diff --git a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala index b7f904fbbd..db5845c94a 100644 --- a/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala +++ b/src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala @@ -454,16 +454,19 @@ abstract class ExplicitOuter extends InfoTransform with TransMatcher with Patter } var checkExhaustive = true - def isUncheckedAnnotation(tpe: Type) = tpe match { - case AnnotatedType(List(AnnotationInfo(atp, _, _)), _, _) if atp.typeSymbol == UncheckedClass => - true - case _ => - false - } + var requireSwitch = false + + def isUncheckedAnnotation(tpe: Type) = tpe hasAttribute UncheckedClass + def isSwitchAnnotation(tpe: Type) = tpe hasAttribute SwitchClass + nselector match { - case Typed(nselector1, tpt) if isUncheckedAnnotation(tpt.tpe) => - nselector = nselector1 - checkExhaustive = false + case Typed(nselector1, tpt) => + if (isUncheckedAnnotation(tpt.tpe)) { + nselector = nselector1 + checkExhaustive = false + } + if (isSwitchAnnotation(tpt.tpe)) + requireSwitch = true case _ => } @@ -471,13 +474,22 @@ abstract class ExplicitOuter extends InfoTransform with TransMatcher with Patter val t = atPos(tree.pos) { val t_untyped = handlePattern(nselector, ncases.toList, checkExhaustive, currentOwner, transform)(localTyper) + /* if @switch annotation is present, verify the resulting tree is a Match */ + if (requireSwitch) t_untyped match { + case Block(_, Match(_, _)) => // ok + case _ => + unit.error(tree.pos, "could not emit switch for @switch annotated match") + } + localTyper.typed(t_untyped, resultType) } if (settings.debug.value) Console.println("finished translation of " + tid) - if(nguard.isEmpty) {t} else Block(nguard.toList, t) setType t.tpe + if (nguard.isEmpty) t + else Block(nguard.toList, t) setType t.tpe + case _ => val x = super.transform(tree) diff --git a/src/compiler/scala/tools/nsc/transform/TailCalls.scala b/src/compiler/scala/tools/nsc/transform/TailCalls.scala index 31b8665b74..d2fd0bec42 100644 --- a/src/compiler/scala/tools/nsc/transform/TailCalls.scala +++ b/src/compiler/scala/tools/nsc/transform/TailCalls.scala @@ -39,6 +39,9 @@ abstract class TailCalls extends Transform } } + /** The @tailrec annotation indicates TCO is mandatory */ + private def tailrecRequired(defdef: DefDef) = defdef.symbol hasAttribute TailrecClass + /** * A Tail Call Transformer * @@ -149,8 +152,9 @@ abstract class TailCalls extends Transform override def transform(tree: Tree): Tree = { tree match { - case DefDef(mods, name, tparams, vparams, tpt, rhs) => + case dd @ DefDef(mods, name, tparams, vparams, tpt, rhs) => log("Entering DefDef: " + name) + var isTransformed = false val newCtx = mkContext(ctx) newCtx.currentMethod = tree.symbol newCtx.makeLabel() @@ -173,6 +177,7 @@ abstract class TailCalls extends Transform var newRHS = transform(rhs, newCtx); if (newCtx.accessed) { log("Rewrote def " + newCtx.currentMethod) + isTransformed = true val newThis = newCtx.currentMethod.newValue(tree.pos, nme.THIS) .setInfo(currentClass.tpe) @@ -189,6 +194,10 @@ abstract class TailCalls extends Transform } else { copy.DefDef(tree, mods, name, tparams, vparams, tpt, transform(rhs, newCtx)) } + + if (!isTransformed && tailrecRequired(dd)) + unit.error(dd.pos, "could not optimize @tailrec annotated method") + log("Leaving DefDef: " + name) t1 diff --git a/src/library/scala/Iterator.scala b/src/library/scala/Iterator.scala index d04d32393a..b5092a2430 100644 --- a/src/library/scala/Iterator.scala +++ b/src/library/scala/Iterator.scala @@ -14,6 +14,7 @@ package scala import Predef._ import collection.mutable.{Buffer, ListBuffer, ArrayBuffer} +import annotation.tailrec /** The <code>Iterator</code> object provides various functions for * creating specialized iterators. @@ -248,8 +249,14 @@ trait Iterator[+A] { * @param n the number of elements to drop * @return the new iterator */ - def drop(n: Int): Iterator[A] = - if (n > 0 && hasNext) { next; drop(n - 1) } else this + def drop(n: Int): Iterator[A] = { + @tailrec + def loop(left: Int): Iterator[A] = + if (left > 0 && hasNext) { next; loop(left - 1) } + else this + + loop(n) + } /** A sub-iterator of <code>until - from elements * starting at index <code>from</code> @@ -366,12 +373,18 @@ trait Iterator[+A] { * @param p the predicate used to skip elements. * @return an iterator consisting of the remaining elements */ - def dropWhile(p: A => Boolean): Iterator[A] = - if (hasNext) { - val x = next - if (p(x)) dropWhile(p) - else Iterator.single(x) append this - } else this + def dropWhile(p: A => Boolean): Iterator[A] = { + @tailrec + def loop(): Iterator[A] = + if (hasNext) { + val x = next + if (p(x)) loop() + else Iterator.single(x) append this + } + else this + + loop() + } /** Return an iterator formed from this iterator and the specified iterator * <code>that</code> by associating each element of the former with diff --git a/src/library/scala/List.scala b/src/library/scala/List.scala index eda39f9ec9..5482deabf4 100644 --- a/src/library/scala/List.scala +++ b/src/library/scala/List.scala @@ -12,6 +12,7 @@ package scala import scala.collection.mutable.ListBuffer +import annotation.tailrec import Predef._ /** This object provides methods for creating specialized lists, and for @@ -702,6 +703,7 @@ sealed abstract class List[+A] extends Seq[A] with Product { * @return the suffix of length <code>n</code> of the list */ def takeRight(n: Int): List[A] = { + @tailrec def loop(lead: List[A], lag: List[A]): List[A] = lead match { case Nil => lag case _ :: tail => loop(tail, lag.tail) @@ -765,9 +767,14 @@ sealed abstract class List[+A] extends Seq[A] with Product { * @return the longest suffix of the list whose first element * does not satisfy the predicate <code>p</code>. */ - override def dropWhile(p: A => Boolean): List[A] = - if (isEmpty || !p(head)) this - else tail dropWhile p + override def dropWhile(p: A => Boolean): List[A] = { + @tailrec + def loop(xs: List[A]): List[A] = + if (xs.isEmpty || !p(xs.head)) xs + else loop(xs.tail) + + loop(this) + } /** Returns the longest prefix of the list whose elements all satisfy * the given predicate, and the rest of the list. @@ -823,6 +830,7 @@ sealed abstract class List[+A] extends Seq[A] with Product { * @return the reversed list of results. */ def reverseMap[B](f: A => B): List[B] = { + @tailrec def loop(l: List[A], res: List[B]): List[B] = l match { case Nil => res case head :: tail => loop(tail, f(head) :: res) diff --git a/src/library/scala/annotation/switch.scala b/src/library/scala/annotation/switch.scala new file mode 100644 index 0000000000..5ecbf73905 --- /dev/null +++ b/src/library/scala/annotation/switch.scala @@ -0,0 +1,17 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2009, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ +package scala.annotation + +/** <p> + * An annotation to be applied to a match expression. If present, + * the compiler will verify that the match has been compiled to a + * tableswitch or lookupswitch, and issue an error if it instead + * compiles into a series of conditional expressions. + * </p> + */ +final class switch extends StaticAnnotation
\ No newline at end of file diff --git a/src/library/scala/annotation/tailrec.scala b/src/library/scala/annotation/tailrec.scala new file mode 100644 index 0000000000..6ce23a92fa --- /dev/null +++ b/src/library/scala/annotation/tailrec.scala @@ -0,0 +1,16 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2009, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ +package scala.annotation + +/** <p> + * A method annotation which verifies that the method will be compiled + * with tail call optimization. If it is present, the compiler will + * issue an error if the method cannot be optimized into a loop. + * </p> + */ +final class tailrec extends StaticAnnotation diff --git a/src/library/scalax/collection/immutable/Stream.scala b/src/library/scalax/collection/immutable/Stream.scala index 036fefe70c..4055be40bc 100755 --- a/src/library/scalax/collection/immutable/Stream.scala +++ b/src/library/scalax/collection/immutable/Stream.scala @@ -14,6 +14,7 @@ package scalax.collection.immutable import mutable.ListBuffer import generic.{SequenceTemplate, SequenceFactory, EmptyIterableFactory, Builder, LazyBuilder} import annotation.unchecked.uncheckedVariance +import annotation.tailrec /** * The object <code>Stream</code> provides helper functions @@ -486,6 +487,7 @@ self => * @param sep The separator string printed between consecutive elements. */ def print(sep: String) { + @tailrec def loop(these: Stream[A], start: String) { Console.print(start) if (isEmpty) Console.print("empty") |