From 410efa8317f2a244f45f33b2ae7c17be5472f5d5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 24 Jun 2009 15:37:16 +0000 Subject: fixed variance bug; added smart brace insertion... fixed variance bug; added smart brace insertion to parser. moved interactive compiler interface along. --- src/compiler/scala/tools/nsc/Global.scala | 16 +- .../scala/tools/nsc/ast/parser/BracePair.scala | 12 ++ .../scala/tools/nsc/ast/parser/BracePatch.scala | 11 + .../scala/tools/nsc/ast/parser/Parsers.scala | 64 +++++- .../scala/tools/nsc/ast/parser/Scanners.scala | 218 ++++++++++++++++++- .../tools/nsc/ast/parser/SyntaxAnalyzer.scala | 1 + .../tools/nsc/interactive/CompilerControl.scala | 8 +- .../scala/tools/nsc/interactive/Global.scala | 81 ++++++-- .../scala/tools/nsc/reporters/Reporter.scala | 4 + src/compiler/scala/tools/nsc/symtab/Symbols.scala | 87 ++++---- src/compiler/scala/tools/nsc/symtab/Types.scala | 5 +- .../scala/tools/nsc/typechecker/Implicits.scala | 231 +++++++++++---------- .../scala/tools/nsc/typechecker/RefChecks.scala | 37 +++- .../scala/tools/nsc/typechecker/Typers.scala | 14 +- src/library/scala/collection/generic/Addable.scala | 2 +- 15 files changed, 569 insertions(+), 222 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/ast/parser/BracePair.scala create mode 100644 src/compiler/scala/tools/nsc/ast/parser/BracePatch.scala diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index 6db3bfd626..76507271c0 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -307,12 +307,15 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable final def applyPhase(unit: CompilationUnit) { if (settings.debug.value) inform("[running phase " + name + " on " + unit + "]") val unit0 = currentRun.currentUnit - currentRun.currentUnit = unit - reporter.setSource(unit.source) - if (!cancelled(unit)) apply(unit) - currentRun.advanceUnit - assert(currentRun.currentUnit == unit) - currentRun.currentUnit = unit0 + try { + currentRun.currentUnit = unit + reporter.setSource(unit.source) + if (!cancelled(unit)) apply(unit) + currentRun.advanceUnit + } finally { + assert(currentRun.currentUnit == unit) + currentRun.currentUnit = unit0 + } } } @@ -397,6 +400,7 @@ class Global(var settings: Settings, var reporter: Reporter) extends SymbolTable val runsRightAfter = Some("tailcalls") } with SpecializeTypes + // phaseName = "erasure" object erasure extends { val global: Global.this.type = Global.this val runsAfter = List[String]("explicitouter") diff --git a/src/compiler/scala/tools/nsc/ast/parser/BracePair.scala b/src/compiler/scala/tools/nsc/ast/parser/BracePair.scala new file mode 100644 index 0000000000..44cf5e7f2c --- /dev/null +++ b/src/compiler/scala/tools/nsc/ast/parser/BracePair.scala @@ -0,0 +1,12 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2009 LAMP/EPFL + * @author Martin Odersky + */ +package scala.tools.nsc.ast.parser + +/** A descriptor for a matching pair of braces. + * @param loff The offset of the opening brace (-1 means missing) + * @param roff The offset of the closing brace (-1 means missing) + * @param nested The brace pairs nested in this one + */ +case class BracePair(loff: Int, off: Int, nested: List[BracePair]) diff --git a/src/compiler/scala/tools/nsc/ast/parser/BracePatch.scala b/src/compiler/scala/tools/nsc/ast/parser/BracePatch.scala new file mode 100644 index 0000000000..00d2e1a2ba --- /dev/null +++ b/src/compiler/scala/tools/nsc/ast/parser/BracePatch.scala @@ -0,0 +1,11 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2009 LAMP/EPFL + * @author Martin Odersky + */ +package scala.tools.nsc.ast.parser + +/** A patch that postulates that a brace needs to be inserted or deleted at a given position. + * @param off The offset where the brace needs to be inserted or deleted + * @param inserted If true, brace needs to be inserted, otherwise brace needs to be deleted. + */ +case class BracePatch(off: Int, inserted: Boolean) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index fc85f7a196..6246782e9f 100755 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -59,8 +59,11 @@ self => case class OpInfo(operand: Tree, operator: Name, offset: Offset) - class UnitParser(val unit: global.CompilationUnit) extends Parser { - val in = new UnitScanner(unit) + class UnitParser(val unit: global.CompilationUnit, patches: List[BracePatch]) extends Parser { + + def this(unit: global.CompilationUnit) = this(unit, List()) + + val in = new UnitScanner(unit, patches) in.init() def freshName(pos: Position, prefix: String): Name = @@ -71,15 +74,45 @@ self => def warning(offset: Int, msg: String) { unit.warning(o2p(offset), msg) } + def deprecationWarning(offset: Int, msg: String) { + unit.deprecationWarning(o2p(offset), msg) + } + + var smartParsing = false + + val syntaxErrors = new ListBuffer[(Int, String)] + def incompleteInputError(msg: String) { - unit.incompleteInputError(o2p(unit.source.asInstanceOf[BatchSourceFile].content.length - 1), msg) + val offset = unit.source.asInstanceOf[BatchSourceFile].content.length - 1 + if (smartParsing) syntaxErrors += ((offset, msg)) + else unit.incompleteInputError(o2p(offset), msg) } - def deprecationWarning(offset: Int, msg: String) { - unit.deprecationWarning(o2p(offset), msg) + def syntaxError(offset: Int, msg: String) { + if (smartParsing) syntaxErrors += ((offset, msg)) + else unit.error(o2p(offset), msg) } - def syntaxError(offset: Int, msg: String) { unit.error(o2p(offset), msg) } + /** parse unit. If there are inbalanced braces, + * try to correct them and reparse. + */ + def smartParse(): Tree = try { + smartParsing = true + val firstTry = parse() + if (syntaxErrors.isEmpty) firstTry + else { + val patches = in.healBraces() + if (patches.isEmpty) { + for ((offset, msg) <- syntaxErrors) unit.error(o2p(offset), msg) + firstTry + } else { + println(patches) + new UnitParser(unit, patches).parse() + } + } + } finally { + smartParsing = false + } /** the markup parser */ lazy val xmlp = new MarkupParser(this, true) @@ -200,7 +233,9 @@ self => /* ------------- ERROR HANDLING ------------------------------------------- */ - protected def skip() { + var assumedClosingParens = collection.mutable.Map(RPAREN -> 0, RBRACKET -> 0, RBRACE -> 0) + + protected def skip(targetToken: Int) { var nparens = 0 var nbraces = 0 while (true) { @@ -224,6 +259,7 @@ self => nbraces += 1 case _ => } + if (targetToken == in.token && nparens == 0 && nbraces == 0) return in.nextToken() } } @@ -248,7 +284,7 @@ self => lastErrorOffset = in.offset } if (skipIt) - skip() + skip(UNDEF) } def warning(msg: String) { warning(in.offset, msg) } @@ -268,13 +304,21 @@ self => if (in.token != token) { val msg = token2string(token) + " expected but " +token2string(in.token) + " found." - + syntaxErrorOrIncomplete(msg, true) if (in.token == EOF) incompleteInputError(msg) - else syntaxError(in.offset, msg, true) + else syntaxError(in.offset, msg, false) + if ((token == RPAREN || token == RBRACE || token == RBRACKET)) + if (in.parenBalance(token) + assumedClosingParens(token) < 0) + assumedClosingParens(token) += 1 + else + skip(token) + else + skip(UNDEF) } if (in.token == token) in.nextToken() offset } + def surround[T](open: Int, close: Int)(f: => T, orElse: T): T = { val wasOpened = in.token == open accept(open) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index b15ec75cd7..6c40c1f0a6 100755 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -10,6 +10,7 @@ import scala.tools.nsc.util._ import SourceFile.{LF, FF, CR, SU} import Tokens._ import scala.annotation.switch +import scala.collection.mutable.{ListBuffer, ArrayBuffer} trait Scanners { val global : Global @@ -174,7 +175,7 @@ trait Scanners { * - the current token can start a statement and the one before can end it * insert NEWLINES if we are past a blank line, NEWLINE otherwise */ - if (afterLineEnd() && inLastOfStat(lastToken) && inFirstOfStat(token) && + if (!applyBracePatch() && afterLineEnd() && inLastOfStat(lastToken) && inFirstOfStat(token) && (sepRegions.isEmpty || sepRegions.head == RBRACE)) { next copyFrom this offset = if (lineStartOffset <= offset) lineStartOffset else lastLineStartOffset @@ -237,9 +238,10 @@ trait Scanners { /** read next token, filling TokenData fields of Scanner. */ - private final def fetchToken() { + protected final def fetchToken() { offset = charOffset - 1 (ch: @switch) match { + case ' ' | '\t' | CR | LF | FF => nextChar() fetchToken() @@ -801,6 +803,20 @@ trait Scanners { token2string(token) } + // ------------- brace counting and healing ------------------------------ + + /** overridden in UnitScanners: + * apply brace patch if one exists for this offset + * return true if subsequent end of line handling should be suppressed. + */ + def applyBracePatch(): Boolean = false + + /** overridden in UnitScanners */ + def parenBalance(token: Int) = 0 + + /** overridden in UnitScanners */ + def healBraces(): List[BracePatch] = List() + /** Initialization method: read first char, then first token */ def init() { @@ -958,12 +974,208 @@ trait Scanners { /** A scanner over a given compilation unit */ - class UnitScanner(unit: CompilationUnit) extends Scanner { + class UnitScanner(unit: CompilationUnit, patches: List[BracePatch]) extends Scanner { + def this(unit: CompilationUnit) = this(unit, List()) val buf = unit.source.asInstanceOf[BatchSourceFile].content val decodeUnit = !settings.nouescape.value + def warning(off: Offset, msg: String) = unit.warning(unit.position(off), msg) def error (off: Offset, msg: String) = unit.error(unit.position(off), msg) def incompleteInputError(off: Offset, msg: String) = unit.incompleteInputError(unit.position(off), msg) def deprecationWarning(off: Offset, msg: String) = unit.deprecationWarning(unit.position(off), msg) + + private var bracePatches: List[BracePatch] = patches + + lazy val parensAnalyzer = new ParensAnalyzer(unit, List()) + + override def parenBalance(token: Int) = parensAnalyzer.balance(token) + + override def healBraces(): List[BracePatch] = { + var patches: List[BracePatch] = List() + if (!parensAnalyzer.tabSeen) { + var bal = parensAnalyzer.balance(RBRACE) + while (bal < 0) { + patches = new ParensAnalyzer(unit, patches).insertRBrace() + bal += 1 + } + while (bal > 0) { + patches = new ParensAnalyzer(unit, patches).deleteRBrace() + bal -= 1 + } + } + patches + } + + /** Insert or delete a brace, if a patch exists for this offset */ + override def applyBracePatch(): Boolean = { + if (bracePatches.isEmpty || bracePatches.head.off != offset) false + else { + val patch = bracePatches.head + bracePatches = bracePatches.tail +// println("applying brace patch "+offset)//DEBUG + if (patch.inserted) { + next copyFrom this + error(offset, "Missing closing brace `}' assumed here") + token = RBRACE + true + } else { + error(offset, "Unmatched closing brace '}' ignored here") + fetchToken() + false + } + } + } + } + + class ParensAnalyzer(unit: CompilationUnit, patches: List[BracePatch]) extends UnitScanner(unit, patches) { + var balance = collection.mutable.Map(RPAREN -> 0, RBRACKET -> 0, RBRACE -> 0) + + init() + + /** The offset of the first token on this line, or next following line if blank + */ + val lineStart = new ArrayBuffer[Int] + + /** The list of matching top-level brace pairs (each of which may contain nested brace pairs). + */ + val bracePairs: List[BracePair] = { + + var lineCount = 1 + var lastOffset = 0 + + def scan(bpbuf: ListBuffer[BracePair]): Int = { + if (token != NEWLINE && token != NEWLINES) { + while (lastOffset < offset) { + if (buf(lastOffset) == LF) lineCount += 1 + lastOffset += 1 + } + while (lineCount > lineStart.length) + lineStart += offset + } + token match { + case LPAREN => + balance(RPAREN) -= 1; nextToken(); scan(bpbuf) + case LBRACKET => + balance(RBRACKET) -= 1; nextToken(); scan(bpbuf) + case RPAREN => + balance(RPAREN) += 1; nextToken(); scan(bpbuf) + case RBRACKET => + balance(RBRACKET) += 1; nextToken(); scan(bpbuf) + case LBRACE => + balance(RBRACE) -= 1 + val lc = lineCount + val loff = offset + val bpbuf1 = new ListBuffer[BracePair] + nextToken() + val roff = scan(bpbuf1) + if (lc != lineCount) + bpbuf += BracePair(loff, roff, bpbuf1.toList) + scan(bpbuf) + case RBRACE => + balance(RBRACE) += 1 + val off = offset; nextToken(); off + case EOF => + -1 + case _ => + nextToken(); scan(bpbuf) + } + } + + val bpbuf = new ListBuffer[BracePair] + while (token != EOF) { + val roff = scan(bpbuf) + if (roff != -1) { + val current = BracePair(-1, roff, bpbuf.toList) + bpbuf.clear() + bpbuf += current + } + } +// println("lineStart = "+lineStart)//DEBUG +// println("bracepairs = "+bpbuf.toList)//DEBUG + bpbuf.toList + } + + var tabSeen = false + + def line(offset: Int): Int = { + def findLine(lo: Int, hi: Int): Int = { + val mid = (lo + hi) / 2 + if (offset < lineStart(mid)) findLine(lo, mid - 1) + else if (mid + 1 < lineStart.length && offset >= lineStart(mid + 1)) findLine(mid + 1, hi) + else mid + } + findLine(0, lineStart.length - 1) + } + + def column(offset: Int): Int = { + var col = 0 + var i = offset - 1 + while (i >= 0 && buf(i) != CR && buf(i) != LF) { + if (buf(i) == '\t') tabSeen = true + col += 1 + i -= 1 + } + col + } + + def insertPatch(patches: List[BracePatch], patch: BracePatch): List[BracePatch] = patches match { + case List() => List(patch) + case bp :: bps => if (patch.off < bp.off) patch :: patches + else bp :: insertPatch(bps, patch) + } + + def leftColumn(offset: Int) = + if (offset == -1) -1 else column(lineStart(line(offset))) + + def rightColumn(offset: Int, default: Int) = + if (offset == -1) -1 + else { + val rlin = line(offset) + if (lineStart(rlin) == offset) column(offset) + else if (rlin + 1 < lineStart.length) column(lineStart(rlin + 1)) + else default + } + + def insertRBrace(): List[BracePatch] = { + def insert(bps: List[BracePair]): List[BracePatch] = bps match { + case List() => patches + case (bp @ BracePair(loff, roff, nested)) :: bps1 => + val lcol = leftColumn(loff) + val rcol = rightColumn(roff, lcol) + if (lcol <= rcol) insert(bps1) + else { +// println("patch inside "+bp+"/"+line(loff)+"/"+lineStart(line(loff))+"/"+lcol+"/"+rcol)//DEBUG + val patches1 = insert(nested) + if (patches1 ne patches) patches1 + else { +// println("patch for "+bp)//DEBUG + var lin = line(loff) + 1 + while (lin < lineStart.length && column(lineStart(lin)) > lcol) + lin += 1 + if (lin < lineStart.length) + insertPatch(patches, BracePatch(lineStart(lin), true)) + else patches + } + } + } + insert(bracePairs) + } + + def deleteRBrace(): List[BracePatch] = { + def delete(bps: List[BracePair]): List[BracePatch] = bps match { + case List() => patches + case BracePair(loff, roff, nested) :: bps1 => + val lcol = leftColumn(loff) + val rcol = rightColumn(roff, lcol) + if (lcol >= rcol) delete(bps1) + else { + val patches1 = delete(nested) + if (patches1 ne patches) patches1 + else insertPatch(patches, BracePatch(roff, false)) + } + } + delete(bracePairs) + } + override def error(offset: Int, msg: String) {} } } diff --git a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala index 0c3350aedc..0e29853ade 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/SyntaxAnalyzer.scala @@ -22,6 +22,7 @@ abstract class SyntaxAnalyzer extends SubComponent with Parsers with MarkupParse global.informProgress("parsing " + unit) unit.body = if (unit.source.file.name.endsWith(".java")) new JavaUnitParser(unit).parse() + else if (!global.reporter.incompleteHandled) new UnitParser(unit).smartParse() else new UnitParser(unit).parse() } } diff --git a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala index d7b5c3eedb..c1e6747f8c 100644 --- a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala +++ b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala @@ -16,15 +16,9 @@ trait CompilerControl { self: Global => abstract class WorkItem extends (() => Unit) - /** The status of a member that's returned by completion. - */ - object MemberStatus extends Enumeration { - val Accessible, Inherited, Implicit = Value - } - /** Info given for every member found by completion */ - type Member = (Symbol, Type, MemberStatus.ValueSet) + case class Member(val sym: Symbol, val tpe: Type, val accessible: Boolean, val inherited: Boolean, val viaView: Symbol) /** The scheduler by which client and compiler communicate * Must be initialized before starting compilerRunner diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala index 43c5fecaf6..165c9b6896 100755 --- a/src/compiler/scala/tools/nsc/interactive/Global.scala +++ b/src/compiler/scala/tools/nsc/interactive/Global.scala @@ -17,6 +17,8 @@ class Global(settings: Settings, reporter: Reporter) with RichCompilationUnits { self => + import definitions._ + override def onlyPresentation = true /** A list indicating in which order some units should be typechecked. @@ -45,16 +47,12 @@ self => /** The status value of a unit that has not yet been typechecked */ final val JustParsed = 0 - private var resultTree = EmptyTree - // ----------- Overriding hooks in nsc.Global ----------------------- /** Create a RangePosition */ override def rangePos(source: SourceFile, start: Int, point: Int, end: Int) = new RangePosition(source, start, point, end) - - /** Called from typechecker, which signal hereby that a node has been completely typechecked. * If the node is included in unit.targetPos, abandons run and returns newly attributed tree. * Otherwise, if there's some higher priority work to be done, also abandons run with a FreshRunReq. @@ -66,15 +64,25 @@ self => def integrateNew() { context.unit.body = new TreeReplacer(old, result) transform context.unit.body } - if ((context.unit != null) && !result.pos.isSynthetic && (result.pos includes context.unit.targetPos)) { - integrateNew() - throw new TyperResult(result) - } - val typerRun = currentTyperRun - pollForWork() - if (typerRun != currentTyperRun) { - integrateNew() - throw new FreshRunReq + if (activeLocks == 0) { + if (context.unit != null && + !result.pos.isSynthetic && + !isTransparent(result.pos) && + (result.pos includes context.unit.targetPos)) { + integrateNew() + var located = new Locator(context.unit.targetPos) locateIn result + if (located == EmptyTree) { + println("something's wrong: no "+context.unit+" in "+result+result.pos) + located = result + } + throw new TyperResult(located) + } + val typerRun = currentTyperRun + pollForWork() + if (typerRun != currentTyperRun) { + integrateNew() + throw new FreshRunReq + } } } @@ -193,6 +201,7 @@ self => } for (unit <- units) { inform("type checking: "+unit) + activeLocks = 0 currentTyperRun.typeCheck(unit) unit.status = currentRunId } @@ -249,22 +258,36 @@ self => case _ => tree.tpe } + import analyzer.{SearchResult, ImplicitSearch} + def completion(pos: Position, result: Response[List[Member]]) { - import MemberStatus._ respond(result) { val tree = typedTreeAt(pos) locateContext(pos) match { case Some(context) => val superAccess = tree.isInstanceOf[Super] val pre = stabilizedType(tree) - def withStatus(sym: Symbol, vs: ValueSet) = ( + def member(sym: Symbol, inherited: Boolean) = new Member( sym, - pre.memberType(sym), - if (context.isAccessible(sym, pre, superAccess)) vs + Accessible else vs + pre memberType sym, + context.isAccessible(sym, pre, superAccess), + inherited, + NoSymbol ) - val decls = tree.tpe.decls.toList map (withStatus(_, ValueSet())) - val inherited = tree.tpe.members.toList diff decls map (withStatus(_, ValueSet(Inherited))) - val implicits = List() // not yet done + def implicitMembers(s: SearchResult): List[Member] = { + val vtree = viewApply(s, tree, context) + val vpre = stabilizedType(vtree) + vtree.tpe.members map { sym => new Member( + sym, + vpre memberType sym, + context.isAccessible(sym, vpre, false), + false, + s.tree.symbol + )} + } + val decls = tree.tpe.decls.toList map (member(_, false)) + val inherited = tree.tpe.members.toList diff decls map (member(_, true)) + val implicits = applicableViews(tree, context) flatMap implicitMembers decls ::: inherited ::: implicits case None => throw new FatalError("no context found for "+pos) @@ -272,6 +295,19 @@ self => } } + def applicableViews(tree: Tree, context: Context): List[SearchResult] = + new ImplicitSearch(tree, functionType(List(tree.tpe), AnyClass.tpe), true, context.makeImplicit(false)) + .allImplicits + + def viewApply(view: SearchResult, tree: Tree, context: Context): Tree = { + assert(view.tree != EmptyTree) + try { + analyzer.newTyper(context.makeImplicit(false)).typed(Apply(view.tree, List(tree)) setPos tree.pos) + } catch { + case ex: TypeError => EmptyTree + } + } + // ---------------- Helper classes --------------------------- /** A transformer that replaces tree `from` with tree `to` in a given tree */ @@ -317,7 +353,7 @@ self => def typedTreeAt(pos: Position): Tree = { println("starting typedTreeAt") val tree = locateTree(pos) - println("at pos "+pos+" was found: "+tree) + println("at pos "+pos+" was found: "+tree+tree.pos.show) if (tree.tpe ne null) { println("already attributed") tree @@ -331,8 +367,9 @@ self => throw new FatalError("tree not found") } catch { case ex: TyperResult => - println("result found") ex.tree + } finally { + unit.targetPos = NoPosition } } } diff --git a/src/compiler/scala/tools/nsc/reporters/Reporter.scala b/src/compiler/scala/tools/nsc/reporters/Reporter.scala index c12e32a026..e8fe26f1a0 100644 --- a/src/compiler/scala/tools/nsc/reporters/Reporter.scala +++ b/src/compiler/scala/tools/nsc/reporters/Reporter.scala @@ -53,14 +53,18 @@ abstract class Reporter { * Should be re-factored into a subclass. */ var incompleteInputError: (Position, String) => Unit = error + var incompleteHandled: Boolean = false def withIncompleteHandler[T](handler: (Position, String) => Unit)(thunk: => T) = { val savedHandler = incompleteInputError + val savedHandled = incompleteHandled try { incompleteInputError = handler + incompleteHandled = true thunk } finally { incompleteInputError = savedHandler + incompleteHandled = savedHandled } } diff --git a/src/compiler/scala/tools/nsc/symtab/Symbols.scala b/src/compiler/scala/tools/nsc/symtab/Symbols.scala index 32c02ecbcc..af6f45e095 100644 --- a/src/compiler/scala/tools/nsc/symtab/Symbols.scala +++ b/src/compiler/scala/tools/nsc/symtab/Symbols.scala @@ -29,6 +29,8 @@ trait Symbols { val emptySymbolArray = new Array[Symbol](0) val emptySymbolSet = Set.empty[Symbol] + /** Used for deciding in the IDE whether we can interrupt the compiler */ + protected var activeLocks = 0 /** Used to keep track of the recursion depth on locked symbols */ private var recursionTable = Map.empty[Symbol, Int] @@ -299,49 +301,47 @@ trait Symbols { // Locking and unlocking ------------------------------------------------------ - // True if the symbol is unlocked. - // True if the symbol is locked but still below the allowed recursion depth. - // False otherwise - def lockOK: Boolean = { - ((rawflags & LOCKED) == 0) || - ((settings.Yrecursion.value != 0) && - (recursionTable get this match { - case Some(n) => (n <= settings.Yrecursion.value) - case None => true })) - } - - protected var activeLocks = 0 - - // Lock a symbol, using the handler if the recursion depth becomes too great. - def lock(handler: => Unit) = { - if ((rawflags & LOCKED) != 0) { - if (settings.Yrecursion.value != 0) { - recursionTable get this match { - case Some(n) => - if (n > settings.Yrecursion.value) { - handler - } else { - recursionTable += (this -> (n + 1)) - } - case None => - recursionTable += (this -> 1) - } - } else { handler } - } else { - rawflags |= LOCKED - activeLocks += 1 + // True if the symbol is unlocked. + // True if the symbol is locked but still below the allowed recursion depth. + // False otherwise + def lockOK: Boolean = { + ((rawflags & LOCKED) == 0) || + ((settings.Yrecursion.value != 0) && + (recursionTable get this match { + case Some(n) => (n <= settings.Yrecursion.value) + case None => true })) + } + + // Lock a symbol, using the handler if the recursion depth becomes too great. + def lock(handler: => Unit) = { + if ((rawflags & LOCKED) != 0) { + if (settings.Yrecursion.value != 0) { + recursionTable get this match { + case Some(n) => + if (n > settings.Yrecursion.value) { + handler + } else { + recursionTable += (this -> (n + 1)) + } + case None => + recursionTable += (this -> 1) + } + } else { handler } + } else { + rawflags |= LOCKED + activeLocks += 1 + } } - } - // Unlock a symbol - def unlock() = { - if ((rawflags & LOCKED) != 0) { - activeLocks -= 1 - rawflags = rawflags & ~LOCKED - if (settings.Yrecursion.value != 0) - recursionTable -= this + // Unlock a symbol + def unlock() = { + if ((rawflags & LOCKED) != 0) { + activeLocks -= 1 + rawflags = rawflags & ~LOCKED + if (settings.Yrecursion.value != 0) + recursionTable -= this + } } - } // Tests ---------------------------------------------------------------------- @@ -1423,12 +1423,9 @@ trait Symbols { * Never adds id. */ final def fullNameString(separator: Char): String = { - if (this == NoSymbol) return "" - assert(owner != NoSymbol, this) var str = - if (owner.isRoot || - owner.isEmptyPackageClass || - owner.isInterpreterWrapper) simpleName.toString + if (isRoot || isRootPackage || this == NoSymbol) this.toString + else if (owner.isRoot || owner.isEmptyPackageClass || owner.isInterpreterWrapper) simpleName.toString else owner.enclClass.fullNameString(separator) + separator + simpleName if (str.charAt(str.length - 1) == ' ') str = str.substring(0, str.length - 1) str diff --git a/src/compiler/scala/tools/nsc/symtab/Types.scala b/src/compiler/scala/tools/nsc/symtab/Types.scala index c78eb333af..1c3f9692e2 100644 --- a/src/compiler/scala/tools/nsc/symtab/Types.scala +++ b/src/compiler/scala/tools/nsc/symtab/Types.scala @@ -2332,7 +2332,10 @@ A type's typeSymbol should never be inspected directly. uniqueRunId = currentRunId } uniques.findEntry(tp) match { - case null => uniques.addEntry(tp); tp + case null => + //println("new unique type: "+tp) + uniques.addEntry(tp); + tp case tp1 => tp1.asInstanceOf[T] } } diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index 7d0268c6af..27c94c0883 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -44,21 +44,20 @@ self: Analyzer => * @param tree The tree for which the implicit needs to be inserted. * (the inference might instantiate some of the undetermined * type parameters of that tree. - * @param pt0 The original expected type of the implicit. A method type - * for `pt0` implies we are looking for a view, any other type implies - * we are looking for an implicit parameter. + * @param pt The expected type of the implicit. * @param reportAmbiguous Should ambiguous implicit errors be reported? * False iff we search for a view to find out * whether one type is coercible to another. + * @param isView We are looking for a view * @param context The current context * @return A search result */ - def inferImplicit(tree: Tree, pt0: Type, reportAmbiguous: Boolean, context: Context): SearchResult = { + def inferImplicit(tree: Tree, pt: Type, reportAmbiguous: Boolean, isView: Boolean, context: Context): SearchResult = { if (traceImplicits && !tree.isEmpty && !context.undetparams.isEmpty) println("typing implicit with undetermined type params: "+context.undetparams+"\n"+tree) - val search = new ImplicitSearch(tree, pt0, context.makeImplicit(reportAmbiguous)) - context.undetparams = context.undetparams remove (search.result.subst.from contains _) - search.result + val result = new ImplicitSearch(tree, pt, isView, context.makeImplicit(reportAmbiguous)).bestImplicit + context.undetparams = context.undetparams remove (result.subst.from contains _) + result } final val sizeLimit = 100 @@ -123,12 +122,11 @@ self: Analyzer => /** A class that sets up an implicit search. For more info, see comments for `inferImplicit`. * @param tree The tree for which the implicit needs to be inserted. - * @param pt0 The original expected type of the implicit. A method type - * for `pt0` implies we are looking for a view, any other type implies - * we are looking for an implicit parameter. + * @param pt The original expected type of the implicit. + * @param isView We are looking for a view * @param context0 The context used for the implicit search */ - private class ImplicitSearch(tree: Tree, pt0: Type, context0: Context) + class ImplicitSearch(tree: Tree, pt: Type, isView: Boolean, context0: Context) extends Typer(context0) { import infer._ @@ -211,17 +209,6 @@ self: Analyzer => overlaps(dtor1, dted1) && (dtor1 =:= dted1 || complexity(dtor1) > complexity(dted1)) } - /** The normalized expected type (which is a value type). */ - private val pt = normalize(pt0) - - /** Are we looking for an implicit view? This is signalled by the original expected type - * being a method or a polymorphic type. - */ - private val isView = pt0 match { - case MethodType(_, _) | PolyType(_, _) => true - case _ => false - } - if (util.Statistics.enabled) implcnt += 1 private val startTime = if (util.Statistics.enabled) currentTime else 0l @@ -313,15 +300,15 @@ self: Analyzer => //if (traceImplicits) println("typed impl?? "+info.name+":"+info.tpe+" ==> "+itree+" with "+wildPt) def fail(reason: String): SearchResult = { if (settings.XlogImplicits.value) - inform(itree+" is not a valid implicit value for "+pt0+" because:\n"+reason) + inform(itree+" is not a valid implicit value for "+pt+" because:\n"+reason) SearchFailure } try { val itree1 = if (isView) typed1( - Apply(itree, List(Ident("").setType(approximate(pt0.paramTypes.head)))), - EXPRmode, approximate(pt0.resultType)) + Apply(itree, List(Ident("").setType(approximate(pt.typeArgs.head)))), + EXPRmode, approximate(pt.typeArgs.tail.head)) else typed1(itree, EXPRmode, wildPt) @@ -368,61 +355,56 @@ self: Analyzer => } } - /** Search list of implicit info lists for one matching prototype - * pt. If found return a search result with a tree from found implicit info - * which is typed with expected type pt. - * Otherwise return SearchFailure. - * - * @param implicitInfoss The given list of lists of implicit infos + /** Should implicit definition symbol `sym' be considered for applicability testing? + * This is the case if one of the following holds: + * - the symbol's type is initialized + * - the symbol comes from a classfile + * - the symbol comes from a different sourcefile than the current one + * - the symbol's definition comes before, and does not contain the closest enclosing definition, + * - the symbol's definition is a val, var, or def with an explicit result type + * The aim of this method is to prevent premature cyclic reference errors + * by computing the types of only those implicitis for which one of these + * conditions is true. + */ + def isValid(sym: Symbol) = { + def hasExplicitResultType(sym: Symbol) = { + def hasExplicitRT(tree: Tree) = tree match { + case ValDef(_, _, tpt, _) => !tpt.isEmpty + case DefDef(_, _, _, _, tpt, _) => !tpt.isEmpty + case _ => false + } + sym.rawInfo match { + case tc: TypeCompleter => hasExplicitRT(tc.tree) + case PolyType(_, tc: TypeCompleter) => hasExplicitRT(tc.tree) + case _ => true + } + } + def comesBefore(sym: Symbol, owner: Symbol) = + sym.pos.offset.getOrElse(0) < owner.pos.offset.getOrElse(Integer.MAX_VALUE) && + !(owner.ownerChain contains sym) + + sym.isInitialized || + sym.sourceFile == null || + (sym.sourceFile ne context.unit.source.file) || + hasExplicitResultType(sym) || + comesBefore(sym, context.owner) + } + + /** Computes from a list of lists of implicit infos a map which takes + * infos which are applicable for given expected type `pt` to their attributed trees. + * Computes invalid implicits as a side effect (used for better error message). + * @param iss The given list of lists of implicit infos * @param isLocal Is implicit definition visible without prefix? * If this is the case then symbols in preceding lists shadow * symbols of the same name in succeeding lists. */ - def searchImplicit(implicitInfoss: List[List[ImplicitInfo]], isLocal: Boolean): SearchResult = { + def applicableInfos(iss: List[List[ImplicitInfo]], + isLocal: Boolean, + invalidImplicits: ListBuffer[Symbol]): Map[ImplicitInfo, SearchResult] = { /** A set containing names that are shadowed by implicit infos */ val shadowed = new HashSet[Name](8) - /** Should implicit definition symbol `sym' be considered for applicability testing? - * This is the case if one of the following holds: - * - the symbol's type is initialized - * - the symbol comes from a classfile - * - the symbol comes from a different sourcefile than the current one - * - the symbol's definition comes before, and does not contain the closest enclosing definition, - * - the symbol's definition is a val, var, or def with an explicit result type - * The aim of this method is to prevent premature cyclic reference errors - * by computing the types of only those implicitis for which one of these - * conditions is true. - */ - def isValid(sym: Symbol) = { - def hasExplicitResultType(sym: Symbol) = { - def hasExplicitRT(tree: Tree) = tree match { - case ValDef(_, _, tpt, _) => !tpt.isEmpty - case DefDef(_, _, _, _, tpt, _) => !tpt.isEmpty - case _ => false - } - sym.rawInfo match { - case tc: TypeCompleter => hasExplicitRT(tc.tree) - case PolyType(_, tc: TypeCompleter) => hasExplicitRT(tc.tree) - case _ => true - } - } - def comesBefore(sym: Symbol, owner: Symbol) = - sym.pos.offset.getOrElse(0) < owner.pos.offset.getOrElse(Integer.MAX_VALUE) && - !(owner.ownerChain contains sym) - - sym.isInitialized || - sym.sourceFile == null || - (sym.sourceFile ne context.unit.source.file) || - hasExplicitResultType(sym) || - comesBefore(sym, context.owner) - } - - /** The implicits that are not valid because they come later in the source - * and lack an explicit result type. Used for error diagnostics only. - */ - val invalidImplicits = new ListBuffer[Symbol] - /** Try implicit `info` to see whether it is applicable for expected type `pt`. * This is the case if all of the following holds: * - the info's type is not erroneous, @@ -438,11 +420,7 @@ self: Analyzer => (!isView || info.sym != Predef_identity)) typedImplicit(info) else SearchFailure - /** Computes from a list of implicit infos a map which takes - * infos which are applicable for given expected type `pt` to their attributed trees. - * Computes invalid implicits as a side effect (used for better error message). - */ - def applicableInfos(is: List[ImplicitInfo]): Map[ImplicitInfo, SearchResult] = { + def appInfos(is: List[ImplicitInfo]): Map[ImplicitInfo, SearchResult] = { var applicable = Map[ImplicitInfo, SearchResult]() for (i <- is) if (!isValid(i.sym)) invalidImplicits += i.sym @@ -455,9 +433,28 @@ self: Analyzer => applicable } + (Map[ImplicitInfo, SearchResult]() /: (iss map appInfos))(_ ++ _) + } + + /** Search list of implicit info lists for one matching prototype + * pt. If found return a search result with a tree from found implicit info + * which is typed with expected type pt. + * Otherwise return SearchFailure. + * + * @param implicitInfoss The given list of lists of implicit infos + * @param isLocal Is implicit definition visible without prefix? + * If this is the case then symbols in preceding lists shadow + * symbols of the same name in succeeding lists. + */ + def searchImplicit(implicitInfoss: List[List[ImplicitInfo]], isLocal: Boolean): SearchResult = { + + /** The implicits that are not valid because they come later in the source + * and lack an explicit result type. Used for error diagnostics only. + */ + val invalidImplicits = new ListBuffer[Symbol] + /** A map which takes applicable infos to their attributed trees. */ - val applicable: Map[ImplicitInfo, SearchResult] = - (Map[ImplicitInfo, SearchResult]() /: (implicitInfoss map applicableInfos))(_ ++ _) + val applicable = applicableInfos(implicitInfoss, isLocal, invalidImplicits) if (applicable.isEmpty && !invalidImplicits.isEmpty) { infer.setAddendum(tree.pos, () => @@ -477,6 +474,7 @@ self: Analyzer => // Also check that applicable infos that did not get selected are not // in (a companion object of) a subclass of (a companion object of) the class // containing the winning info. + // (no longer needed; rules have changed) /* for (alt <- applicable.keySet) { if (isProperSubClassOrObject(alt.sym.owner, best.sym.owner)) { @@ -609,7 +607,7 @@ self: Analyzer => /** Re-wraps a type in a manifest before calling inferImplicit on the result */ def findManifest(tp: Type): Tree = - inferImplicit(tree, appliedType(ManifestClass.typeConstructor, List(tp)), true, context).tree + inferImplicit(tree, appliedType(ManifestClass.typeConstructor, List(tp)), true, false, context).tree tp.normalize match { case ThisType(_) | SingleType(_, _) => @@ -647,41 +645,50 @@ self: Analyzer => * If that fails, and `pt` is an instance of Manifest, try to construct a manifest. * If all fails return SearchFailure */ - //val start = System.nanoTime() - var result = searchImplicit(context.implicitss, true) - //val timer1 = System.nanoTime() - //if (result == SearchFailure) inscopeFail += timer1 - start else inscopeSucceed += timer1 - start - if (result == SearchFailure) { - if (pt.isInstanceOf[UniqueType]) - implicitsCache get pt match { - case Some(r) => - hits += 1 - result = r - case None => - misses += 1 - result = searchImplicit(implicitsOfExpectedType, false) -// println("new fact: search implicit of "+pt+" = "+result) -// if (implicitsCache.size >= sizeLimit) -// implicitsCache -= implicitsCache.values.next + def bestImplicit: SearchResult = { + //val start = System.nanoTime() + var result = searchImplicit(context.implicitss, true) + //val timer1 = System.nanoTime() + //if (result == SearchFailure) inscopeFail += timer1 - start else inscopeSucceed += timer1 - start + if (result == SearchFailure) { + if (pt.isInstanceOf[UniqueType]) + implicitsCache get pt match { + case Some(r) => + hits += 1 + result = r + case None => + misses += 1 + result = searchImplicit(implicitsOfExpectedType, false) + // println("new fact: search implicit of "+pt+" = "+result) + // if (implicitsCache.size >= sizeLimit) + // implicitsCache -= implicitsCache.values.next implicitsCache(pt) = result - } - else - result = searchImplicit(implicitsOfExpectedType, false) - } - //val timer2 = System.nanoTime() - //if (result == SearchFailure) oftypeFail += timer2 - timer1 else oftypeSucceed += timer2 - timer1 - if (result == SearchFailure) { - val resultTree = implicitManifest(pt) - if (resultTree != EmptyTree) result = new SearchResult(resultTree, EmptyTreeTypeSubstituter) + } + else + result = searchImplicit(implicitsOfExpectedType, false) + } + //val timer2 = System.nanoTime() + //if (result == SearchFailure) oftypeFail += timer2 - timer1 else oftypeSucceed += timer2 - timer1 + if (result == SearchFailure) { + val resultTree = implicitManifest(pt) + if (resultTree != EmptyTree) result = new SearchResult(resultTree, EmptyTreeTypeSubstituter) + } + //val timer3 = System.nanoTime() + //if (result == SearchFailure) manifFail += timer3 - timer2 else manifSucceed += timer3 - timer2 + if (result == SearchFailure && settings.debug.value) + println("no implicits found for "+pt+" "+pt.typeSymbol.info.baseClasses+" "+parts(pt)+implicitsOfExpectedType) + //implicitTime += System.nanoTime() - start + + if (util.Statistics.enabled) impltime += (currentTime - startTime) + result } - //val timer3 = System.nanoTime() - //if (result == SearchFailure) manifFail += timer3 - timer2 else manifSucceed += timer3 - timer2 - if (result == SearchFailure && settings.debug.value) - println("no implicits found for "+pt+" "+pt.typeSymbol.info.baseClasses+" "+parts(pt)+implicitsOfExpectedType) - //implicitTime += System.nanoTime() - start - if (util.Statistics.enabled) impltime += (currentTime - startTime) - result + def allImplicits: List[SearchResult] = { + val invalidImplicits = new ListBuffer[Symbol] + def search(iss: List[List[ImplicitInfo]], isLocal: Boolean) = + applicableInfos(iss, isLocal, invalidImplicits).values.toList + search(context.implicitss, true) ::: search(implicitsOfExpectedType, false) + } } private val DivergentImplicit = new Exception() diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 2015716aae..f9ad690472 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -415,6 +415,7 @@ abstract class RefChecks extends InfoTransform { val varianceValidator = new Traverser { + /** Validate variance of info of symbol `base` */ private def validateVariance(base: Symbol) { def varianceString(variance: Int): String = @@ -422,25 +423,46 @@ abstract class RefChecks extends InfoTransform { else if (variance == -1) "contravariant" else "invariant"; + /** The variance of a symbol occurrence of `tvar` + * seen at the level of the definition of `base`. + * The search proceeds from `base` to the owner of `tvar`. + * Initially the state is covariant, but it might change along the search. + */ def relativeVariance(tvar: Symbol): Int = { val clazz = tvar.owner var sym = base var state = CoVariance while (sym != clazz && state != AnyVariance) { //Console.println("flip: " + sym + " " + sym.isParameter());//DEBUG + // Flip occurrences of type parameters and parameters, unless + // - it's a constructor, or case class factory or extractor + // - it's a type parameter of tvar's owner. if ((sym hasFlag PARAM) && !sym.owner.isConstructor && !sym.owner.isCaseApplyOrUnapply && !(tvar.isTypeParameterOrSkolem && sym.isTypeParameterOrSkolem && tvar.owner == sym.owner)) state = -state; else if (!sym.owner.isClass || - sym.isTerm && ((sym.isPrivateLocal || sym.isProtectedLocal) && !(escapedPrivateLocals contains sym))) + sym.isTerm && ((sym.isPrivateLocal || sym.isProtectedLocal) && !(escapedPrivateLocals contains sym))) { + // return AnyVariance if `sym` is local to a term + // or is private[this] or protected[this] state = AnyVariance - else if (sym.isAliasType) - state = AnyVariance // was NoVariance, but now we always expand aliases. + } else if (sym.isAliasType) { + // return AnyVariance if `sym` is an alias type + // that does not override anything. This is OK, because we always + // expand aliases for variance checking. + // However, if `sym` does override a type in a base class + // we have to assume NoVariance, as there might then be + // references to the type parameter that are not variance checked. + state = if (sym.allOverriddenSymbols.isEmpty) AnyVariance + else NoVariance + } sym = sym.owner } state } + /** Validate that the type `tp` is variance-correct, assuming + * the type occurs itself at variance position given by `variance` + */ def validateVariance(tp: Type, variance: Int): Unit = tp match { case ErrorType => ; case WildcardType => ; @@ -452,10 +474,10 @@ abstract class RefChecks extends InfoTransform { case SingleType(pre, sym) => validateVariance(pre, variance) case TypeRef(pre, sym, args) => - if (sym.isAliasType) + if (sym.isAliasType && relativeVariance(sym) == AnyVariance) validateVariance(tp.normalize, variance) else if (sym.variance != NoVariance) { - val v = relativeVariance(sym); + val v = relativeVariance(sym) if (v != AnyVariance && sym.variance != v * variance) { //Console.println("relativeVariance(" + base + "," + sym + ") = " + v);//DEBUG def tpString(tp: Type) = tp match { @@ -476,6 +498,8 @@ abstract class RefChecks extends InfoTransform { validateVariances(parents, variance) case RefinedType(parents, decls) => validateVariances(parents, variance) + for (sym <- decls.toList) + validateVariance(sym.info, if (sym.isAliasType) NoVariance else variance) case TypeBounds(lo, hi) => validateVariance(lo, -variance) validateVariance(hi, variance) @@ -507,7 +531,8 @@ abstract class RefChecks extends InfoTransform { override def traverse(tree: Tree) { tree match { - case ClassDef(_, _, _, _) | TypeDef(_, _, _, _) => + case ClassDef(_, _, _, _) | + TypeDef(_, _, _, _) => validateVariance(tree.symbol) super.traverse(tree) // ModuleDefs need not be considered because they have been eliminated already diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 71e0af421f..d82c995e05 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -176,11 +176,8 @@ trait Typers { self: Analyzer => */ def applyImplicitArgs(fun: Tree): Tree = fun.tpe match { case MethodType(params, _) => - def implicitArg(pt: Type): SearchResult = - inferImplicit(fun, pt, true, context) - var positional = true - val argResults = params map (_.tpe) map implicitArg + val argResults = params map (_.tpe) map (inferImplicit(fun, _, true, false, context)) val args = argResults.zip(params) flatMap { case (arg, param) => if (arg != SearchFailure) { @@ -218,15 +215,14 @@ trait Typers { self: Analyzer => case OverloadedType(_, _) => EmptyTree case PolyType(_, _) => EmptyTree case _ => - val dummyMethod = new TermSymbol(NoSymbol, NoPosition, "typer$dummy") - def wrapImplicit(from: Symbol): Tree = { - val result = inferImplicit(tree, MethodType(List(from), to), reportAmbiguous, context) + def wrapImplicit(from: Type): Tree = { + val result = inferImplicit(tree, functionType(List(from), to), reportAmbiguous, true, context) if (result.subst != EmptyTreeTypeSubstituter) result.subst traverse tree result.tree } - val result = wrapImplicit(dummyMethod.newSyntheticValueParam(from)) + val result = wrapImplicit(from) if (result != EmptyTree) result - else wrapImplicit(dummyMethod.newSyntheticValueParam(appliedType(ByNameParamClass.typeConstructor, List(from)))) + else wrapImplicit(appliedType(ByNameParamClass.typeConstructor, List(from))) } } diff --git a/src/library/scala/collection/generic/Addable.scala b/src/library/scala/collection/generic/Addable.scala index f27dc8e257..5a794d4cde 100644 --- a/src/library/scala/collection/generic/Addable.scala +++ b/src/library/scala/collection/generic/Addable.scala @@ -42,7 +42,7 @@ trait Addable[A, +This <: Addable[A, This]] { self => * * @param elems the traversable object. */ - def ++(elems: Traversable[A]): This = (thisCollection /: elems) (_ + _) + def ++ (elems: Traversable[A]): This = (thisCollection /: elems) (_ + _) /** Adds a number of elements provided by an iterator * and returns a new collection with the added elements. -- cgit v1.2.3