From 910a701fcc93e0663f0a6a15ac11499beb1ca6a9 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 12 Mar 2012 17:58:34 +0100 Subject: SI-5189: refined GADT soundness fix extrapolate GADT skolems: only complicate types when needed make sure we only deskolemize GADT skolems after typedCase --- src/compiler/scala/reflect/internal/Flags.scala | 2 +- src/compiler/scala/reflect/internal/Symbols.scala | 22 +++++++--- src/compiler/scala/reflect/internal/Types.scala | 7 ++-- .../scala/tools/nsc/typechecker/Infer.scala | 2 +- .../tools/nsc/typechecker/TypeDiagnostics.scala | 2 +- .../scala/tools/nsc/typechecker/Typers.scala | 47 +++++++++++++--------- 6 files changed, 53 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/reflect/internal/Flags.scala b/src/compiler/scala/reflect/internal/Flags.scala index 8aae80eed4..3110d73461 100644 --- a/src/compiler/scala/reflect/internal/Flags.scala +++ b/src/compiler/scala/reflect/internal/Flags.scala @@ -107,7 +107,7 @@ class ModifierFlags { // pre: PRIVATE or PROTECTED are also set final val JAVA = 0x00100000 // symbol was defined by a Java class final val STATIC = 0x00800000 // static field, method or class - final val CASEACCESSOR = 0x01000000 // symbol is a case parameter (or its accessor) + final val CASEACCESSOR = 0x01000000 // symbol is a case parameter (or its accessor, or a GADT skolem) final val TRAIT = 0x02000000 // symbol is a trait final val DEFAULTPARAM = 0x02000000 // the parameter has a default value final val PARAMACCESSOR = 0x20000000 // for field definitions generated for primary constructor diff --git a/src/compiler/scala/reflect/internal/Symbols.scala b/src/compiler/scala/reflect/internal/Symbols.scala index 9678d2b8cd..7673aec4c7 100644 --- a/src/compiler/scala/reflect/internal/Symbols.scala +++ b/src/compiler/scala/reflect/internal/Symbols.scala @@ -269,11 +269,17 @@ trait Symbols extends api.Symbols { self: SymbolTable => /** Create a new existential type skolem with this symbol its owner, * based on the given symbol and origin. */ - def newExistentialSkolem(basis: Symbol, origin: AnyRef, name: TypeName = null, info: Type = null): TypeSkolem = { - val skolem = newTypeSkolemSymbol(if (name eq null) basis.name.toTypeName else name, origin, basis.pos, (basis.flags | EXISTENTIAL) & ~PARAM) - skolem setInfo (if (info eq null) basis.info cloneInfo skolem else info) + def newExistentialSkolem(basis: Symbol, origin: AnyRef): TypeSkolem = { + val skolem = newTypeSkolemSymbol(basis.name.toTypeName, origin, basis.pos, (basis.flags | EXISTENTIAL) & ~PARAM) + skolem setInfo (basis.info cloneInfo skolem) } + // flags set up to maintain TypeSkolem's invariant: origin.isInstanceOf[Symbol] == !hasFlag(EXISTENTIAL) + // CASEACCESSOR | SYNTHETIC used to single this symbol out in deskolemizeGADT + def newGADTSkolem(name: TypeName, origin: Symbol, info: Type): TypeSkolem = + newTypeSkolemSymbol(name, origin, origin.pos, origin.flags & ~(EXISTENTIAL | PARAM) | CASEACCESSOR | SYNTHETIC) setInfo info + + final def newExistential(name: TypeName, pos: Position = NoPosition, newFlags: Long = 0L): Symbol = newAbstractType(name, pos, EXISTENTIAL | newFlags) @@ -495,6 +501,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => // List[T] forSome { type T } final def isExistentialSkolem = isExistentiallyBound && isSkolem final def isExistentialQuantified = isExistentiallyBound && !isSkolem + final def isGADTSkolem = isSkolem && hasFlag(CASEACCESSOR | SYNTHETIC) // class C extends D( { class E { ... } ... } ). Here, E is a class local to a constructor final def isClassLocalToConstructor = isClass && hasFlag(INCONSTRUCTOR) @@ -2129,12 +2136,17 @@ trait Symbols extends api.Symbols { self: SymbolTable => else if (owner.isRefinementClass) ExplicitFlags & ~OVERRIDE else ExplicitFlags + // make the error message more googlable + def flagsExplanationString = + if (isGADTSkolem) " (this is a GADT skolem)" + else "" + def accessString = hasFlagsToString(PRIVATE | PROTECTED | LOCAL) def defaultFlagString = hasFlagsToString(defaultFlagMask) private def defStringCompose(infoString: String) = compose( defaultFlagString, keyString, - varianceString + nameString + infoString + varianceString + nameString + infoString + flagsExplanationString ) /** String representation of symbol's definition. It uses the * symbol's raw info to avoid forcing types. @@ -2477,7 +2489,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => * where the skolem was introduced (this is important for knowing when to pack it * again into ab Existential). origin is `null` only in skolemizeExistentials called * from <:< or isAsSpecific, because here its value does not matter. - * I elieve the following invariant holds: + * I believe the following invariant holds: * * origin.isInstanceOf[Symbol] == !hasFlag(EXISTENTIAL) */ diff --git a/src/compiler/scala/reflect/internal/Types.scala b/src/compiler/scala/reflect/internal/Types.scala index 2382413a9a..549c9e4607 100644 --- a/src/compiler/scala/reflect/internal/Types.scala +++ b/src/compiler/scala/reflect/internal/Types.scala @@ -1041,8 +1041,8 @@ trait Types extends api.Types { self: SymbolTable => baseClasses.head.newOverloaded(this, members.toList) } } - /** The existential skolems and existentially quantified variables which are free in this type */ - def existentialSkolems: List[Symbol] = { + /** The (existential or otherwise) skolems and existentially quantified variables which are free in this type */ + def skolemsExceptMethodTypeParams: List[Symbol] = { var boundSyms: List[Symbol] = List() var skolems: List[Symbol] = List() for (t <- this) { @@ -1050,7 +1050,8 @@ trait Types extends api.Types { self: SymbolTable => case ExistentialType(quantified, qtpe) => boundSyms = boundSyms ::: quantified case TypeRef(_, sym, _) => - if ((sym hasFlag EXISTENTIAL) && !(boundSyms contains sym) && !(skolems contains sym)) + if ((sym.isExistentialSkolem || sym.isGADTSkolem) && // treat GADT skolems like existential skolems + !((boundSyms contains sym) || (skolems contains sym))) skolems = sym :: skolems case _ => } diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala index 8b3bc253fd..a59622d4df 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala @@ -1099,7 +1099,7 @@ trait Infer { // since instantiateTypeVar wants to modify the skolem that corresponds to the method's type parameter, // and it uses the TypeVar's origin to locate it, deskolemize the existential skolem to the method tparam skolem // (the existential skolem was created by adaptConstrPattern to introduce the type slack necessary to soundly deal with variant type parameters) - case skolem if skolem.isExistentialSkolem => freshVar(skolem.deSkolemize.asInstanceOf[TypeSymbol]) + case skolem if skolem.isGADTSkolem => freshVar(skolem.deSkolemize.asInstanceOf[TypeSymbol]) case p => freshVar(p) } diff --git a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala index 1434002121..e17a271dd0 100644 --- a/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala +++ b/src/compiler/scala/tools/nsc/typechecker/TypeDiagnostics.scala @@ -157,7 +157,7 @@ trait TypeDiagnostics { } // todo: use also for other error messages - def existentialContext(tp: Type) = tp.existentialSkolems match { + def existentialContext(tp: Type) = tp.skolemsExceptMethodTypeParams match { case Nil => "" case xs => " where " + (disambiguate(xs map (_.existentialToString)) mkString ", ") } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 25f3e7af5c..0dd4b37131 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -909,8 +909,9 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { def apply(tp: Type) = mapOver(tp) match { case TypeRef(NoPrefix, tpSym, Nil) if variance != 0 && tpSym.isTypeParameterOrSkolem && tpSym.owner.isTerm => val bounds = if (variance == 1) TypeBounds.upper(tpSym.tpe) else TypeBounds.lower(tpSym.tpe) - val skolem = context.owner.newExistentialSkolem(tpSym, tpSym, unit.freshTypeName("?"+tpSym.name), bounds) - // println("mapping "+ tpSym +" to "+ skolem + " : "+ bounds +" -- pt= "+ pt) + // origin must be the type param so we can deskolemize + val skolem = context.owner.newGADTSkolem(unit.freshTypeName("?"+tpSym.name), tpSym, bounds) + // println("mapping "+ tpSym +" to "+ skolem + " : "+ bounds +" -- pt= "+ pt +" in "+ context.owner +" at "+ context.tree ) skolems += skolem skolem.tpe case tp1 => tp1 @@ -928,9 +929,19 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { freeVars foreach ctorContext.scope.enter newTyper(ctorContext).infer.inferConstructorInstance(tree1, clazz.typeParams, ptSafe) - // tree1's type-slack skolems will be deskolemized (to the method type parameter skolems) - // once the containing CaseDef has been type checked (see typedCase) - tree1 + // simplify types without losing safety, + // so that error messages don't unnecessarily refer to skolems + val extrapolate = new ExistentialExtrapolation(freeVars) extrapolate (_: Type) + val extrapolated = tree1.tpe match { + case MethodType(ctorArgs, res) => // ctorArgs are actually in a covariant position, since this is the type of the subpatterns of the pattern represented by this Apply node + ctorArgs foreach (p => p.info = extrapolate(p.info)) // no need to clone, this is OUR method type + copyMethodType(tree1.tpe, ctorArgs, extrapolate(res)) + case tp => tp + } + + // once the containing CaseDef has been type checked (see typedCase), + // tree1's remaining type-slack skolems will be deskolemized (to the method type parameter skolems) + tree1 setType extrapolated } else { tree } @@ -1095,7 +1106,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { val found = tree.tpe val req = pt if (!found.isErroneous && !req.isErroneous) { - if (!context.reportErrors && isPastTyper && req.existentialSkolems.nonEmpty) { + if (!context.reportErrors && isPastTyper && req.skolemsExceptMethodTypeParams.nonEmpty) { // Ignore type errors raised in later phases that are due to mismatching types with existential skolems // We have lift crashing in 2.9 with an adapt failure in the pattern matcher. // Here's my hypothsis why this happens. The pattern matcher defines a variable of type @@ -1112,7 +1123,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { // // val x = expr context.unit.warning(tree.pos, "recovering from existential Skolem type error in tree \n" + tree + "\nwith type " + tree.tpe + "\n expected type = " + pt + "\n context = " + context.tree) - adapt(tree, mode, deriveTypeWithWildcards(pt.existentialSkolems)(pt)) + adapt(tree, mode, deriveTypeWithWildcards(pt.skolemsExceptMethodTypeParams)(pt)) } else { // create an actual error AdaptTypeError(tree, found, req) @@ -2112,21 +2123,21 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { // body1 = checkNoEscaping.locals(context.scope, pt, body1) val treeWithSkolems = treeCopy.CaseDef(cdef, pat1, guard1, body1) setType body1.tpe - // undo adaptConstrPattern's evil deeds, as they confuse the old pattern matcher - // TODO: Paul, can we do the deskolemization lazily in the old pattern matcher - object deskolemizeOnce extends TypeMap { - def apply(tp: Type): Type = mapOver(tp) match { - case TypeRef(pre, sym, args) if sym.isExistentialSkolem && sym.deSkolemize.isSkolem && sym.deSkolemize.owner.isTerm => - typeRef(NoPrefix, sym.deSkolemize, args) - case tp1 => tp1 - } - } - - new TypeMapTreeSubstituter(deskolemizeOnce).traverse(treeWithSkolems) + new TypeMapTreeSubstituter(deskolemizeGADTSkolems).traverse(treeWithSkolems) treeWithSkolems // now without skolems, actually } + // undo adaptConstrPattern's evil deeds, as they confuse the old pattern matcher + // the flags are used to avoid accidentally deskolemizing unrelated skolems of skolems + object deskolemizeGADTSkolems extends TypeMap { + def apply(tp: Type): Type = mapOver(tp) match { + case TypeRef(pre, sym, args) if sym.isGADTSkolem => + typeRef(NoPrefix, sym.deSkolemize, args) + case tp1 => tp1 + } + } + def typedCases(cases: List[CaseDef], pattp: Type, pt: Type): List[CaseDef] = cases mapConserve { cdef => newTyper(context.makeNewScope(cdef, context.owner)).typedCase(cdef, pattp, pt) -- cgit v1.2.3 From a38fd5c324899eeafe725f5499d53a14c8766ce7 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 19 Mar 2012 17:14:21 +0100 Subject: Separating Scala Actors from the Scala Library. Scala actors are now in scala-actors.jar. Changes that were done are: - Fixed partest to include actors library for various test usages - Created the entry for the new jar in build.xml - Added maven entries for scala actors Review by: @jsuereth --- build.xml | 21 +++++--- src/build/maven/maven-deploy.xml | 23 ++++---- src/build/maven/scala-actors-pom.xml | 62 ++++++++++++++++++++++ src/build/pack.xml | 9 ++-- src/partest/scala/tools/partest/PartestTask.scala | 11 ++++ .../scala/tools/partest/nest/AntRunner.scala | 1 + .../tools/partest/nest/ConsoleFileManager.scala | 9 ++++ .../scala/tools/partest/nest/DirectRunner.scala | 9 ++-- .../scala/tools/partest/nest/FileManager.scala | 1 + .../tools/partest/nest/ReflectiveRunner.scala | 4 +- .../scala/tools/partest/nest/SBTRunner.scala | 4 ++ src/partest/scala/tools/partest/nest/Worker.scala | 1 + 12 files changed, 130 insertions(+), 25 deletions(-) create mode 100644 src/build/maven/scala-actors-pom.xml (limited to 'src') diff --git a/build.xml b/build.xml index b5a9931450..dc70330d7f 100644 --- a/build.xml +++ b/build.xml @@ -614,9 +614,9 @@ QUICK BUILD (QUICK) - + @@ -995,6 +995,7 @@ PACKED QUICK BUILD (PACK) + @@ -1008,6 +1009,11 @@ PACKED QUICK BUILD (PACK) + + + + + @@ -1137,6 +1143,7 @@ PACKED QUICK BUILD (PACK) + @@ -1160,8 +1167,8 @@ BOOTSTRAPPING BUILD (STRAP) - + @@ -1589,8 +1596,8 @@ DOCUMENTATION - + @@ -1608,7 +1615,7 @@ DOCUMENTATION sourcepath="${src.dir}" classpathref="pack.classpath" addparams="${scalac.args.all}" - docRootContent="${src.dir}/library/rootdoc.txt"> + docRootContent="${src.dir}/library/rootdoc.txt"> @@ -1967,7 +1974,6 @@ DISTRIBUTION - @@ -1979,6 +1985,9 @@ DISTRIBUTION + + + @@ -2055,8 +2064,8 @@ STABLE REFERENCE (STARR) - + diff --git a/src/build/maven/maven-deploy.xml b/src/build/maven/maven-deploy.xml index e0f31a5db2..7f8343a84e 100644 --- a/src/build/maven/maven-deploy.xml +++ b/src/build/maven/maven-deploy.xml @@ -111,11 +111,12 @@ + - - - + + + @@ -168,12 +169,13 @@ - + - - + + + @@ -234,13 +236,14 @@ - - + + - - + + + diff --git a/src/build/maven/scala-actors-pom.xml b/src/build/maven/scala-actors-pom.xml new file mode 100644 index 0000000000..12bae2a23d --- /dev/null +++ b/src/build/maven/scala-actors-pom.xml @@ -0,0 +1,62 @@ + + 4.0.0 + org.scala-lang + scala-actors + jar + @VERSION@ + Scala Actors library + Deprecated Actors Library for Scala + http://www.scala-lang.org/ + 2006 + + LAMP/EPFL + http://lamp.epfl.ch/ + + + + BSD-like + http://www.scala-lang.org/downloads/license.html + + repo + + + + scm:svn:http://lampsvn.epfl.ch/svn-repos/scala/scala/trunk + https://lampsvn.epfl.ch/trac/scala/browser/scala/trunk + + + trac + http://lampsvn.epfl.ch/trac/scala + + + + + org.scala-lang + scala-library + @VERSION@ + + + + + scala-tools.org + @RELEASE_REPOSITORY@ + + + scala-tools.org + @SNAPSHOT_REPOSITORY@ + false + + + + + lamp + EPFL LAMP + + + Typesafe + Typesafe, Inc. + + + diff --git a/src/build/pack.xml b/src/build/pack.xml index e79895e3a8..c12cfa44bf 100644 --- a/src/build/pack.xml +++ b/src/build/pack.xml @@ -114,8 +114,8 @@ MAIN DISTRIBUTION SBAZ version="${version.number}" desc="The Scala library. This is the minimal requirement to run any Scala program." link="${sbaz.universe}/scala-library-${version.number}.sbp"> - - + + @@ -228,6 +228,7 @@ MAIN DISTRIBUTION SBAZ + @@ -290,11 +291,13 @@ MAIN DISTRIBUTION SBAZ basedir="${build-docs.dir}/continuations-plugin"> - + + diff --git a/src/partest/scala/tools/partest/PartestTask.scala b/src/partest/scala/tools/partest/PartestTask.scala index 524dc06327..ad2e155182 100644 --- a/src/partest/scala/tools/partest/PartestTask.scala +++ b/src/partest/scala/tools/partest/PartestTask.scala @@ -299,6 +299,16 @@ class PartestTask extends Task with CompilationPathProperty { } } getOrElse sys.error("Provided classpath does not contain a Scala partest.") + val scalaActors = { + (classpath.list map { fs => new File(fs) }) find { f => + f.getName match { + case "scala-actors.jar" => true + case "actors" if (f.getParentFile.getName == "classes") => true + case _ => false + } + } + } getOrElse sys.error("Provided classpath does not contain a Scala actors.") + def scalacArgsFlat: Option[Seq[String]] = scalacArgs map (_ flatMap { a => val parts = a.getParts if(parts eq null) Seq[String]() else parts.toSeq @@ -324,6 +334,7 @@ class PartestTask extends Task with CompilationPathProperty { antFileManager.LATEST_LIB = scalaLibrary.getAbsolutePath antFileManager.LATEST_COMP = scalaCompiler.getAbsolutePath antFileManager.LATEST_PARTEST = scalaPartest.getAbsolutePath + antFileManager.LATEST_ACTORS = scalaActors.getAbsolutePath javacmd foreach (x => antFileManager.JAVACMD = x.getAbsolutePath) javaccmd foreach (x => antFileManager.JAVAC_CMD = x.getAbsolutePath) diff --git a/src/partest/scala/tools/partest/nest/AntRunner.scala b/src/partest/scala/tools/partest/nest/AntRunner.scala index 4795e5551a..e77385d6e9 100644 --- a/src/partest/scala/tools/partest/nest/AntRunner.scala +++ b/src/partest/scala/tools/partest/nest/AntRunner.scala @@ -22,6 +22,7 @@ class AntRunner extends DirectRunner { var LATEST_LIB: String = _ var LATEST_COMP: String = _ var LATEST_PARTEST: String = _ + var LATEST_ACTORS: String = _ val testRootPath: String = "test" val testRootDir: Directory = Directory(testRootPath) } diff --git a/src/partest/scala/tools/partest/nest/ConsoleFileManager.scala b/src/partest/scala/tools/partest/nest/ConsoleFileManager.scala index 3d72227b04..fa533eeb10 100644 --- a/src/partest/scala/tools/partest/nest/ConsoleFileManager.scala +++ b/src/partest/scala/tools/partest/nest/ConsoleFileManager.scala @@ -83,6 +83,7 @@ class ConsoleFileManager extends FileManager { latestFile = testClassesDir.parent / "bin" latestLibFile = testClassesDir / "library" + latestActorsFile = testClassesDir / "library" / "actors" latestCompFile = testClassesDir / "compiler" latestPartestFile = testClassesDir / "partest" latestFjbgFile = testParent / "lib" / "fjbg.jar" @@ -92,6 +93,7 @@ class ConsoleFileManager extends FileManager { NestUI.verbose("Running on "+dir) latestFile = dir / "bin" latestLibFile = dir / "lib/scala-library.jar" + latestActorsFile = dir / "lib/scala-actors.jar" latestCompFile = dir / "lib/scala-compiler.jar" latestPartestFile = dir / "lib/scala-partest.jar" } @@ -100,6 +102,7 @@ class ConsoleFileManager extends FileManager { NestUI.verbose("Running build/quick") latestFile = prefixFile("build/quick/bin") latestLibFile = prefixFile("build/quick/classes/library") + latestActorsFile = prefixFile("build/quick/classes/library/actors") latestCompFile = prefixFile("build/quick/classes/compiler") latestPartestFile = prefixFile("build/quick/classes/partest") } @@ -109,6 +112,7 @@ class ConsoleFileManager extends FileManager { val p = testParent.getParentFile latestFile = prefixFileWith(p, "bin") latestLibFile = prefixFileWith(p, "lib/scala-library.jar") + latestActorsFile = prefixFileWith(p, "lib/scala-actors.jar") latestCompFile = prefixFileWith(p, "lib/scala-compiler.jar") latestPartestFile = prefixFileWith(p, "lib/scala-partest.jar") } @@ -117,6 +121,7 @@ class ConsoleFileManager extends FileManager { NestUI.verbose("Running dists/latest") latestFile = prefixFile("dists/latest/bin") latestLibFile = prefixFile("dists/latest/lib/scala-library.jar") + latestActorsFile = prefixFile("dists/latest/lib/scala-actors.jar") latestCompFile = prefixFile("dists/latest/lib/scala-compiler.jar") latestPartestFile = prefixFile("dists/latest/lib/scala-partest.jar") } @@ -125,6 +130,7 @@ class ConsoleFileManager extends FileManager { NestUI.verbose("Running build/pack") latestFile = prefixFile("build/pack/bin") latestLibFile = prefixFile("build/pack/lib/scala-library.jar") + latestActorsFile = prefixFile("build/pack/lib/scala-actors.jar") latestCompFile = prefixFile("build/pack/lib/scala-compiler.jar") latestPartestFile = prefixFile("build/pack/lib/scala-partest.jar") } @@ -159,14 +165,17 @@ class ConsoleFileManager extends FileManager { LATEST_LIB = latestLibFile.getAbsolutePath LATEST_COMP = latestCompFile.getAbsolutePath LATEST_PARTEST = latestPartestFile.getAbsolutePath + LATEST_ACTORS = latestActorsFile.getAbsolutePath } var LATEST_LIB: String = "" var LATEST_COMP: String = "" var LATEST_PARTEST: String = "" + var LATEST_ACTORS: String = "" var latestFile: File = _ var latestLibFile: File = _ + var latestActorsFile: File = _ var latestCompFile: File = _ var latestPartestFile: File = _ var latestFjbgFile: File = _ diff --git a/src/partest/scala/tools/partest/nest/DirectRunner.scala b/src/partest/scala/tools/partest/nest/DirectRunner.scala index d3d50ca58c..20f435cfbb 100644 --- a/src/partest/scala/tools/partest/nest/DirectRunner.scala +++ b/src/partest/scala/tools/partest/nest/DirectRunner.scala @@ -57,13 +57,14 @@ trait DirectRunner { // for example, see how it's done in ReflectiveRunner //val consFM = new ConsoleFileManager //import consFM.{ latestCompFile, latestLibFile, latestPartestFile } - val latestCompFile = new File(fileManager.LATEST_COMP); - val latestLibFile = new File(fileManager.LATEST_LIB); - val latestPartestFile = new File(fileManager.LATEST_PARTEST); + val latestCompFile = new File(fileManager.LATEST_COMP) + val latestLibFile = new File(fileManager.LATEST_LIB) + val latestPartestFile = new File(fileManager.LATEST_PARTEST) + val latestActorsFile = new File(fileManager.LATEST_ACTORS) val scalacheckURL = PathSettings.scalaCheck.toURL val scalaCheckParentClassLoader = ScalaClassLoader.fromURLs( - List(scalacheckURL, latestCompFile.toURI.toURL, latestLibFile.toURI.toURL, latestPartestFile.toURI.toURL) + scalacheckURL :: (List(latestCompFile, latestLibFile, latestActorsFile, latestPartestFile).map(_.toURI.toURL)) ) Output.init() diff --git a/src/partest/scala/tools/partest/nest/FileManager.scala b/src/partest/scala/tools/partest/nest/FileManager.scala index a4a94fe93e..6d9e64730f 100644 --- a/src/partest/scala/tools/partest/nest/FileManager.scala +++ b/src/partest/scala/tools/partest/nest/FileManager.scala @@ -62,6 +62,7 @@ trait FileManager extends FileUtil { var LATEST_LIB: String var LATEST_COMP: String var LATEST_PARTEST: String + var LATEST_ACTORS: String var showDiff = false var updateCheck = false diff --git a/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala b/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala index 5cde63dc81..a0511774a9 100644 --- a/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala +++ b/src/partest/scala/tools/partest/nest/ReflectiveRunner.scala @@ -48,9 +48,9 @@ class ReflectiveRunner { new ConsoleFileManager import fileManager. - { latestCompFile, latestLibFile, latestPartestFile, latestFjbgFile, latestScalapFile } + { latestCompFile, latestLibFile, latestPartestFile, latestFjbgFile, latestScalapFile, latestActorsFile } val files = - Array(latestCompFile, latestLibFile, latestPartestFile, latestFjbgFile, latestScalapFile) map (x => io.File(x)) + Array(latestCompFile, latestLibFile, latestPartestFile, latestFjbgFile, latestScalapFile, latestActorsFile) map (x => io.File(x)) val sepUrls = files map (_.toURL) var sepLoader = new URLClassLoader(sepUrls, null) diff --git a/src/partest/scala/tools/partest/nest/SBTRunner.scala b/src/partest/scala/tools/partest/nest/SBTRunner.scala index 4c6f417df5..750e270c18 100644 --- a/src/partest/scala/tools/partest/nest/SBTRunner.scala +++ b/src/partest/scala/tools/partest/nest/SBTRunner.scala @@ -15,6 +15,7 @@ object SBTRunner extends DirectRunner { var LATEST_LIB: String = _ var LATEST_COMP: String = _ var LATEST_PARTEST: String = _ + var LATEST_ACTORS: String = _ val testRootPath: String = "test" val testRootDir: Directory = Directory(testRootPath) } @@ -60,6 +61,9 @@ object SBTRunner extends DirectRunner { fileManager.LATEST_COMP = comp getOrElse sys.error("No scala-compiler found! Classpath = " + fileManager.CLASSPATH) val partest: Option[String] = (fileManager.CLASSPATH split File.pathSeparator filter (_ matches ".*scala-partest.*\\.jar")).headOption fileManager.LATEST_PARTEST = partest getOrElse sys.error("No scala-partest found! Classpath = " + fileManager.CLASSPATH) + val actors: Option[String] = (fileManager.CLASSPATH split File.pathSeparator filter (_ matches ".*scala-actors.*\\.jar")).headOption + fileManager.LATEST_ACTORS = actors getOrElse sys.error("No scala-actors found! Classpath = " + fileManager.CLASSPATH) + // TODO - Do something useful here!!! fileManager.JAVAC_CMD = "javac" fileManager.failed = config.justFailedTests diff --git a/src/partest/scala/tools/partest/nest/Worker.scala b/src/partest/scala/tools/partest/nest/Worker.scala index 3f2cb16082..cb6f2a0edc 100644 --- a/src/partest/scala/tools/partest/nest/Worker.scala +++ b/src/partest/scala/tools/partest/nest/Worker.scala @@ -55,6 +55,7 @@ class ScalaCheckFileManager(val origmanager: FileManager) extends FileManager { var LATEST_LIB: String = origmanager.LATEST_LIB var LATEST_COMP: String = origmanager.LATEST_COMP var LATEST_PARTEST: String = origmanager.LATEST_PARTEST + var LATEST_ACTORS: String = origmanager.LATEST_ACTORS } object Output { -- cgit v1.2.3 From 50bf852c9a48312e60b441d052150fdd78b52a1a Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Mon, 19 Mar 2012 15:07:57 -0700 Subject: Tree adjustments. I noticed that if we gave Ident a "qualifier" method which is always EmptyTree, a whole bunch of code could be simplified. A transparent subclass of Apply which preserves the source level "new" a little longer than never. Since productPrefix is tied up in reification - and oh I detest this "String"-based programming, we shouldn't have to wait until everyone learns this independently to change it - I added "printingPrefix" to Tree, which defaults to productPrefix but can be changed without breaking reify. --- .../scala/reflect/internal/Importers.scala | 2 ++ src/compiler/scala/reflect/internal/Symbols.scala | 1 + .../scala/reflect/internal/TreePrinters.scala | 2 +- .../tools/nsc/interactive/RangePositions.scala | 2 +- src/library/scala/reflect/api/TreePrinters.scala | 2 +- src/library/scala/reflect/api/Trees.scala | 25 ++++++++++++++++------ 6 files changed, 24 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/reflect/internal/Importers.scala b/src/compiler/scala/reflect/internal/Importers.scala index 1003fa804f..04381937d1 100644 --- a/src/compiler/scala/reflect/internal/Importers.scala +++ b/src/compiler/scala/reflect/internal/Importers.scala @@ -351,6 +351,8 @@ trait Importers { self: SymbolTable => new ApplyToImplicitArgs(importTree(fun), args map importTree) case _: from.ApplyImplicitView => new ApplyImplicitView(importTree(fun), args map importTree) + case _: from.ApplyConstructor => + new ApplyConstructor(importTree(fun), args map importTree) case _ => new Apply(importTree(fun), args map importTree) } diff --git a/src/compiler/scala/reflect/internal/Symbols.scala b/src/compiler/scala/reflect/internal/Symbols.scala index 9678d2b8cd..d8e7ce68a2 100644 --- a/src/compiler/scala/reflect/internal/Symbols.scala +++ b/src/compiler/scala/reflect/internal/Symbols.scala @@ -1985,6 +1985,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => else if (isTrait) ("trait", "trait", "TRT") else if (isClass) ("class", "class", "CLS") else if (isType) ("type", "type", "TPE") + else if (isClassConstructor && isPrimaryConstructor) ("primary constructor", "constructor", "PCTOR") else if (isClassConstructor) ("constructor", "constructor", "CTOR") else if (isSourceMethod) ("method", "method", "METH") else if (isTerm) ("value", "value", "VAL") diff --git a/src/compiler/scala/reflect/internal/TreePrinters.scala b/src/compiler/scala/reflect/internal/TreePrinters.scala index f823110440..8ed0ee6357 100644 --- a/src/compiler/scala/reflect/internal/TreePrinters.scala +++ b/src/compiler/scala/reflect/internal/TreePrinters.scala @@ -433,7 +433,7 @@ trait TreePrinters extends api.TreePrinters { self: SymbolTable => /** Hook for extensions */ def xprintTree(treePrinter: TreePrinter, tree: Tree) = - treePrinter.print(tree.productPrefix+tree.productIterator.mkString("(", ", ", ")")) + treePrinter.print(tree.printingPrefix+tree.productIterator.mkString("(", ", ", ")")) def newTreePrinter(writer: PrintWriter): TreePrinter = new TreePrinter(writer) def newTreePrinter(stream: OutputStream): TreePrinter = newTreePrinter(new PrintWriter(stream)) diff --git a/src/compiler/scala/tools/nsc/interactive/RangePositions.scala b/src/compiler/scala/tools/nsc/interactive/RangePositions.scala index d08a363a9d..88e3827403 100644 --- a/src/compiler/scala/tools/nsc/interactive/RangePositions.scala +++ b/src/compiler/scala/tools/nsc/interactive/RangePositions.scala @@ -189,7 +189,7 @@ self: scala.tools.nsc.Global => override def validatePositions(tree: Tree) { def reportTree(prefix : String, tree : Tree) { val source = if (tree.pos.isDefined) tree.pos.source else "" - inform("== "+prefix+" tree ["+tree.id+"] of type "+tree.productPrefix+" at "+tree.pos.show+source) + inform("== "+prefix+" tree ["+tree.id+"] of type "+tree.printingPrefix+" at "+tree.pos.show+source) inform("") inform(treeStatus(tree)) inform("") diff --git a/src/library/scala/reflect/api/TreePrinters.scala b/src/library/scala/reflect/api/TreePrinters.scala index 21b55e9c0e..43865915d3 100644 --- a/src/library/scala/reflect/api/TreePrinters.scala +++ b/src/library/scala/reflect/api/TreePrinters.scala @@ -41,7 +41,7 @@ trait TreePrinters { self: Universe => else if (tree.original != null) print(".setOriginal(", tree.original, ")") case tree: Tree => - print(tree.productPrefix+"(") + print(tree.printingPrefix+"(") val it = tree.productIterator while (it.hasNext) { it.next() match { diff --git a/src/library/scala/reflect/api/Trees.scala b/src/library/scala/reflect/api/Trees.scala index a355207ff0..a8276dc853 100644 --- a/src/library/scala/reflect/api/Trees.scala +++ b/src/library/scala/reflect/api/Trees.scala @@ -76,6 +76,12 @@ trait Trees { self: Universe => private[this] var rawpos: Position = NoPosition + /** Prefix under which to print this tree type. Defaults to product + * prefix (e.g. DefTree) but because that is used in reification + * it cannot be altered without breaking reflection. + */ + def printingPrefix = productPrefix + def pos = rawpos def pos_=(pos: Position) = rawpos = pos def setPos(pos: Position): this.type = { rawpos = pos; this } @@ -249,6 +255,7 @@ trait Trees { self: Universe => * are in DefTrees. */ trait RefTree extends SymTree { + def qualifier: Tree // empty for Idents def name: Name } @@ -489,16 +496,14 @@ trait Trees { self: Universe => /** Factory method for object creation `new tpt(args_1)...(args_n)` * A `New(t, as)` is expanded to: `(new t).(as)` */ - def New(tpt: Tree, argss: List[List[Tree]]): Tree = { - // todo. we need to expose names in scala.reflect.api - val superRef: Tree = Select(New(tpt), nme.CONSTRUCTOR) - if (argss.isEmpty) Apply(superRef, Nil) - else (superRef /: argss) (Apply) + def New(tpt: Tree, argss: List[List[Tree]]): Tree = argss match { + case Nil => new ApplyConstructor(tpt, Nil) + case xs :: rest => rest.foldLeft(new ApplyConstructor(tpt, xs): Tree)(Apply) } /** 0-1 argument list new, based on a type. */ def New(tpe: Type, args: Tree*): Tree = - New(TypeTree(tpe), List(args.toList)) + new ApplyConstructor(TypeTree(tpe), args.toList) /** Type annotation, eliminated by explicit outer */ case class Typed(expr: Tree, tpt: Tree) @@ -537,6 +542,10 @@ trait Trees { self: Universe => class ApplyImplicitView(fun: Tree, args: List[Tree]) extends Apply(fun, args) + class ApplyConstructor(tpt: Tree, args: List[Tree]) extends Apply(Select(New(tpt), nme.CONSTRUCTOR), args) { + override def printingPrefix = "ApplyConstructor" + } + /** Dynamic value application. * In a dynamic application q.f(as) * - q is stored in qual @@ -575,7 +584,9 @@ trait Trees { self: Universe => Select(qualifier, sym.name) setSymbol sym /** Identifier */ - case class Ident(name: Name) extends RefTree + case class Ident(name: Name) extends RefTree { + def qualifier: Tree = EmptyTree + } def Ident(name: String): Ident = Ident(newTermName(name)) -- cgit v1.2.3 From 752884f1606320913ffe66f3bbf3db61746e2436 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Mon, 19 Mar 2012 15:08:43 -0700 Subject: Overhaul of NodePrinters. Drawing on the previous, a half-decade overdue overhaul of NodePrinters. Now with colors if you like colors. % scalac -Xprint:all -Yshow-trees -Dscala.colors a.scala Am heading toward general purpose color configuration. --- .../scala/tools/nsc/ast/NodePrinters.scala | 524 +++++++++++---------- .../scala/tools/nsc/ast/TreeBrowsers.scala | 147 +----- 2 files changed, 287 insertions(+), 384 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/ast/NodePrinters.scala b/src/compiler/scala/tools/nsc/ast/NodePrinters.scala index 9466d1c1f2..3d4253f941 100644 --- a/src/compiler/scala/tools/nsc/ast/NodePrinters.scala +++ b/src/compiler/scala/tools/nsc/ast/NodePrinters.scala @@ -9,14 +9,13 @@ package ast import compat.Platform.EOL import symtab.Flags._ -/** The object nodePrinter converts the internal tree - * representation to a string formatted as a Scala expression. +/** The object `nodePrinter` converts the internal tree + * representation to a string. * * @author Stephane Micheloud - * @version 1.0 + * @author Paul Phillips */ abstract class NodePrinters { - val global: Global import global._ @@ -25,262 +24,293 @@ abstract class NodePrinters { } var infolevel = InfoLevel.Quiet - object nodeToString extends Function1[Tree, String] { + def nodeToString: Tree => String = + if (sys.props contains "scala.colors") nodeToColorizedString + else nodeToRegularString + + object nodeToRegularString extends DefaultPrintAST with (Tree => String) { + def apply(tree: Tree) = stringify(tree) + } + + object nodeToColorizedString extends ColorPrintAST with (Tree => String) { + def apply(tree: Tree) = stringify(tree) + } + + trait ColorPrintAST extends DefaultPrintAST { + import scala.tools.util.color._ + + def keywordColor = Cyan + def typeColor = Yellow + def termColor = Blue + def flagColor = Red + def literalColor = Green + + override def showFlags(tree: MemberDef) = "" + ( + super.showFlags(tree) in flagColor.bright + ) + override def showDefTreeName(tree: DefTree) = "" + ( + if (tree.name.isTermName) tree.name.decode in termColor.bright + else tree.name.decode in typeColor.bright + ) + override def showName(name: Name): String = "" + ( + if (name == nme.EMPTY || name == tpnme.EMPTY) "" in keywordColor + else if (name.isTermName) name.decode in termColor + else name.decode in typeColor + ) + override def showLiteral(lit: Literal) = "" + ( + super.showLiteral(lit) in literalColor.bright + ) + } + + trait DefaultPrintAST extends PrintAST { + def showDefTreeName(tree: DefTree) = showName(tree.name) + def showFlags(tree: MemberDef) = flagsToString(tree.symbol.flags | tree.mods.flags) + def showLiteral(lit: Literal) = lit.value.escapedStringValue + def showTypeTree(tt: TypeTree) = "" + showAttributes(tt) + def showName(name: Name) = name match { + case nme.EMPTY | tpnme.EMPTY => "" + case name => "\"" + name + "\"" + } + + def showSymbol(tree: Tree): String = { + val sym = tree.symbol + if (sym == null || sym == NoSymbol) "" + else " sym/owner/tpe=%s %s/%s/%s".format(sym.accurateKindString, sym.name, sym.owner, sym.tpe) + } + def showType(tree: Tree): String = { + val tpe = tree.tpe + if (tpe == null || tpe == NoType) "" + else " tree.tpe=" + tpe + } + + def showAttributes(tree: Tree): String = { + if (infolevel == InfoLevel.Quiet) "" + else { + try { showSymbol(tree) + showType(tree) trim } + catch { case ex: Throwable => "sym= " + ex.getMessage } + } + } + } + + trait PrintAST { private val buf = new StringBuilder + private var level = 0 - def apply(tree: Tree): String = { - def traverse(tree: Tree, level: Int, comma: Boolean) { - def println(s: String) { - for (i <- 0 until level) buf.append(" ") - buf.append(s) - buf.append(EOL) - } - def printcln(s: String) { - for (i <- 0 until level) buf.append(" ") - buf.append(s) - if (comma) buf.append(",") - buf.append(EOL) - } - def annotationInfoToString(annot: AnnotationInfo): String = { - val str = new StringBuilder - str.append(annot.atp.toString()) - if (!annot.args.isEmpty) - str.append(annot.args.mkString("(", ",", ")")) - if (!annot.assocs.isEmpty) - for (((name, value), index) <- annot.assocs.zipWithIndex) { - if (index > 0) - str.append(", ") - str.append(name).append(" = ").append(value) - } - str.toString - } - def symflags(tree: Tree): String = { - val buf = new StringBuffer - val sym = tree.symbol - buf append flagsToString(sym.flags) + def showName(name: Name): String + def showDefTreeName(defTree: DefTree): String + def showFlags(tree: MemberDef): String + def showLiteral(lit: Literal): String + def showTypeTree(tt: TypeTree): String + def showAttributes(tree: Tree): String // symbol and type + + def showRefTreeName(tree: Tree): String = tree match { + case SelectFromTypeTree(qual, name) => showRefTreeName(qual) + "#" + showName(name) + case Select(qual, name) => showRefTreeName(qual) + "." + showName(name) + case Ident(name) => showName(name) + case _ => "" + tree + } + def showRefTree(tree: RefTree): String = { + def prefix0 = showRefTreeName(tree.qualifier) + def prefix = if (prefix0 == "") "" else (tree match { + case SelectFromTypeTree(_, _) => prefix0 + "#" + case Select(_, _) => prefix0 + "." + case _ => "" + }) + def attrs = showAttributes(tree) match { + case "" => "" + case s => " // " + s + } + prefix + showName(tree.name) + attrs + } + + def stringify(tree: Tree): String = { + buf.clear() + level = 0 + traverse(tree) + buf.toString + } + def traverseAny(x: Any) { + x match { + case t: Tree => traverse(t) + case xs: List[_] => printMultiline("List", "")(xs foreach traverseAny) + case _ => println("" + x) + } + } + def println(s: String) = printLine(s, "") + + def printLine(value: String, comment: String) { + buf append " " * level + buf append value + if (comment != "") { + buf append " // " + buf append comment + } + buf append EOL + } - val annots = ", annots=" + ( - if (!sym.annotations.isEmpty) - sym.annotations.map(annotationInfoToString).mkString("[", ",", "]") - else - tree.asInstanceOf[MemberDef].mods.annotations) - (if (buf.length() > 2) buf.substring(3) - else "0") + ", // flags=" + flagsToString(sym.flags) + annots + def annotationInfoToString(annot: AnnotationInfo): String = { + val str = new StringBuilder + str.append(annot.atp.toString()) + if (!annot.args.isEmpty) + str.append(annot.args.mkString("(", ",", ")")) + if (!annot.assocs.isEmpty) + for (((name, value), index) <- annot.assocs.zipWithIndex) { + if (index > 0) + str.append(", ") + str.append(name).append(" = ").append(value) } + str.toString + } + def printModifiers(tree: MemberDef) { + val annots0 = tree.symbol.annotations match { + case Nil => tree.mods.annotations + case xs => xs map annotationInfoToString + } + val annots = annots0 match { + case Nil => "" + case xs => " " + xs.mkString("@{ ", ", ", " }") + } + val flagString = showFlags(tree) match { + case "" => "0" + case s => s + } + println(flagString + annots) + } - def nodeinfo(tree: Tree): String = - if (infolevel == InfoLevel.Quiet) "" - else { - try { - val buf = new StringBuilder(" // sym=" + tree.symbol) - if (tree.hasSymbol) { - if (tree.symbol.isPrimaryConstructor) - buf.append(", isPrimaryConstructor") - else if (tree.symbol.isConstructor) - buf.append(", isConstructor") - if (tree.symbol != NoSymbol) - buf.append(", sym.owner=" + tree.symbol.owner) - buf.append(", sym.tpe=" + tree.symbol.tpe) - } - buf.append(", tpe=" + tree.tpe) - if (tree.tpe != null) { - var sym = tree.tpe.termSymbol - if (sym == NoSymbol) sym = tree.tpe.typeSymbol - buf.append(", tpe.sym=" + sym) - if (sym != NoSymbol) { - buf.append(", tpe.sym.owner=" + sym.owner) - if ((infolevel > InfoLevel.Normal) && - !(sym.owner eq definitions.ScalaPackageClass) && - !sym.isModuleClass && !sym.isPackageClass && - !sym.isJavaDefined) { - val members = for (m <- tree.tpe.decls) - yield m.toString() + ": " + m.tpe + ", " - buf.append(", tpe.decls=" + members) - } + def applyCommon(tree: Tree, fun: Tree, args: List[Tree]) { + printMultiline(tree) { + traverse(fun) + traverseList("Nil", _ + " arguments(s)")(args) + } + } + + def printMultiline(tree: Tree)(body: => Unit) { + printMultiline(tree.printingPrefix, showAttributes(tree))(body) + } + def printMultiline(prefix: String, comment: String)(body: => Unit) { + printLine(prefix + "(", comment) + indent(body) + println(")") + } + @inline private def indent[T](body: => T): T = { + level += 1 + try body + finally level -= 1 + } + + def traverseList(ifEmpty: String, comment: Int => String)(trees: List[Tree]) { + if (trees.isEmpty) + println(ifEmpty) + else + printMultiline("List", comment(trees.length))(trees foreach traverse) + } + + def traverse(tree: Tree) { + tree match { + case AppliedTypeTree(tpt, args) => applyCommon(tree, tpt, args) + case ApplyDynamic(fun, args) => applyCommon(tree, fun, args) + case Apply(fun, args) => applyCommon(tree, fun, args) + + case Block(stats, expr) => + printMultiline(tree) { + traverseList("{}", _ + " statement(s)")(stats) + traverse(expr) + } + case cd @ ClassDef(mods, name, tparams, impl) => + printMultiline(tree) { + printModifiers(cd) + println(showDefTreeName(cd)) + traverseList("[]", _ + " type parameter(s)")(tparams) + traverse(impl) + } + case md @ ModuleDef(mods, name, impl) => + printMultiline(tree) { + printModifiers(md) + println(showDefTreeName(md)) + traverse(impl) + } + case dd @ DefDef(mods, name, tparams, vparamss, tpt, rhs) => + printMultiline(tree) { + printModifiers(dd) + println(showDefTreeName(dd)) + traverseList("[]", _ + " type parameter(s)")(tparams) + vparamss match { + case Nil => println("Nil") + case Nil :: Nil => println("List(Nil)") + case xss => + printMultiline("List", xss.length + " parameter list(s)") { + xss foreach (xs => traverseList("()", _ + " parameter(s)")(xs)) } + } + traverse(tpt) + traverse(rhs) + } + case EmptyTree => + println(showName(nme.EMPTY)) + case lit @ Literal(value) => + println(showLiteral(lit)) + case New(tpt) => + printMultiline(tree)(traverse(tpt)) + case Super(This(qual), mix) => + println("Super(This(" + showName(qual) + "), " + showName(mix) + ")") + case Super(qual, mix) => + printMultiline(tree) { + traverse(qual) + showName(mix) + } + case Template(parents, self, body) => + printMultiline(tree) { + val ps0 = parents map { p => + if (p.tpe eq null) p match { + case x: RefTree => showRefTree(x) + case x => "" + x } - buf.toString - } catch { - case ex: Throwable => - return " // sym= " + ex.getMessage + else showName(newTypeName(p.tpe.typeSymbol.fullName)) } + printLine(ps0 mkString ", ", "parents") + traverse(self) + traverseList("{}", _ + " statements in body")(body) } - def nodeinfo2(tree: Tree): String = - (if (comma) "," else "") + nodeinfo(tree) - - def applyCommon(name: String, tree: Tree, fun: Tree, args: List[Tree]) { - println(name + "(" + nodeinfo(tree)) - traverse(fun, level + 1, true) - if (args.isEmpty) - println(" Nil // no argument") - else { - val n = args.length - println(" List( // " + n + " arguments(s)") - for (i <- 0 until n) - traverse(args(i), level + 2, i < n-1) - println(" )") + case This(qual) => + println("This(\"" + showName(qual) + "\")" + showAttributes(tree)) + case TypeApply(fun, args) => + printMultiline(tree) { + traverse(fun) + traverseList("[]", _ + " type argument(s)")(args) } - printcln(")") - } + case tt @ TypeTree() => + println(showTypeTree(tt)) - tree match { - case AppliedTypeTree(tpt, args) => applyCommon("AppliedTypeTree", tree, tpt, args) - case Apply(fun, args) => applyCommon("Apply", tree, fun, args) - case ApplyDynamic(fun, args) => applyCommon("ApplyDynamic", tree, fun, args) + case Typed(expr, tpt) => + printMultiline(tree) { + traverse(expr) + traverse(tpt) + } + case vd @ ValDef(mods, name, tpt, rhs) => + printMultiline(tree) { + printModifiers(vd) + println(showDefTreeName(vd)) + traverse(tpt) + traverse(rhs) + } + case td @ TypeDef(mods, name, tparams, rhs) => + printMultiline(tree) { + printModifiers(td) + println(showDefTreeName(td)) + traverseList("[]", _ + " type parameter(s)")(tparams) + traverse(rhs) + } + + case PackageDef(pid, stats) => + printMultiline("PackageDef", "")(pid :: stats foreach traverse) - case Block(stats, expr) => - println("Block(" + nodeinfo(tree)) - if (stats.isEmpty) - println(" List(), // no statement") - else { - val n = stats.length - println(" List( // " + n + " statement(s)") - for (i <- 0 until n) - traverse(stats(i), level + 2, i < n-1) - println(" ),") - } - traverse(expr, level + 1, false) - printcln(")") - case ClassDef(mods, name, tparams, impl) => - println("ClassDef(" + nodeinfo(tree)) - println(" " + symflags(tree)) - println(" \"" + name + "\",") - if (tparams.isEmpty) - println(" List(), // no type parameter") - else { - val n = tparams.length - println(" List( // " + n + " type parameter(s)") - for (i <- 0 until n) - traverse(tparams(i), level + 2, i < n-1) - println(" ),") - } - traverse(impl, level + 1, false) - printcln(")") - case DefDef(mods, name, tparams, vparamss, tpt, rhs) => - println("DefDef(" + nodeinfo(tree)) - println(" " + symflags(tree)) - println(" \"" + name + "\",") - if (tparams.isEmpty) - println(" List(), // no type parameter") - else { - val n = tparams.length - println(" List( // " + n + " type parameter(s)") - for (i <- 0 until n) - traverse(tparams(i), level + 2, i < n-1) - println(" ),") - } - val n = vparamss.length - if (n == 1 && vparamss(0).isEmpty) - println(" List(List()), // no parameter") - else { - println(" List(") - for (i <- 0 until n) { - val m = vparamss(i).length - println(" List( // " + m + " parameter(s)") - for (j <- 0 until m) - traverse(vparamss(i)(j), level + 3, j < m-1) - println(" )") - } - println(" ),") - } - println(" " + tpt + ",") - traverse(rhs, level + 1, false) - printcln(")") - case EmptyTree => - printcln("EmptyTree") - case Ident(name) => - printcln("Ident(\"" + name + "\")" + nodeinfo2(tree)) - case Literal(value) => - printcln("Literal(" + value + ")") - case New(tpt) => - println("New(" + nodeinfo(tree)) - traverse(tpt, level + 1, false) - printcln(")") - case Select(qualifier, selector) => - println("Select(" + nodeinfo(tree)) - traverse(qualifier, level + 1, true) - printcln(" \"" + selector + "\")") - case Super(qual, mix) => - println("Super(\"" + mix + "\")" + nodeinfo(tree)) - traverse(qual, level + 1, true) - case Template(parents, self, body) => - println("Template(" + nodeinfo(tree)) - println(" " + parents.map(p => - if (p.tpe ne null) p.tpe.typeSymbol else "null-" + p - ) + ", // parents") - traverse(self, level + 1, true) - if (body.isEmpty) - println(" List() // no body") - else { - val n = body.length - println(" List( // body") - for (i <- 0 until n) - traverse(body(i), level + 2, i < n-1) - println(" )") - } - printcln(")") - case This(qual) => - println("This(\"" + qual + "\")" + nodeinfo2(tree)) - case TypeApply(fun, args) => - println("TypeApply(" + nodeinfo(tree)) - traverse(fun, level + 1, true) - if (args.isEmpty) - println(" List() // no argument") - else { - val n = args.length - println(" List(") - for (i <- 0 until n) - traverse(args(i), level + 1, i < n-1) - println(" )") - } - printcln(")") - case TypeTree() => - printcln("TypeTree()" + nodeinfo2(tree)) - case Typed(expr, tpt) => - println("Typed(" + nodeinfo(tree)) - traverse(expr, level + 1, true) - traverse(tpt, level + 1, false) - printcln(")") - case ValDef(mods, name, tpt, rhs) => - println("ValDef(" + nodeinfo(tree)) - println(" " + symflags(tree)) - println(" \"" + name + "\",") - traverse(tpt, level + 1, true) - traverse(rhs, level + 1, false) - printcln(")") - case PackageDef(pid, stats) => - println("PackageDef(") - traverse(pid, level + 1, false) - println(",\n") - for (stat <- stats) - traverse(stat, level + 1, false) - printcln(")") - case _ => - tree match { - case p: Product => - if (p.productArity != 0) { - println(p.productPrefix+"(") - for (elem <- (0 until p.productArity) map p.productElement) { - def printElem(elem: Any, level: Int): Unit = elem match { - case t: Tree => - traverse(t, level, false) - case xs: List[_] => - print("List(") - for (x <- xs) printElem(x, level+1) - printcln(")") - case _ => - println(elem.toString) - } - printElem(elem, level+1) - } - printcln(")") - } else printcln(p.productPrefix) - } - } + case _ => + tree match { + case t: RefTree => println(showRefTree(t)) + case t if t.productArity == 0 => println(tree.printingPrefix) + case t => printMultiline(tree)(tree.productIterator foreach traverseAny) + } } - buf setLength 0 - traverse(tree, 0, false) - buf.toString } } diff --git a/src/compiler/scala/tools/nsc/ast/TreeBrowsers.scala b/src/compiler/scala/tools/nsc/ast/TreeBrowsers.scala index c1d6c1a4d4..3302c11127 100644 --- a/src/compiler/scala/tools/nsc/ast/TreeBrowsers.scala +++ b/src/compiler/scala/tools/nsc/ast/TreeBrowsers.scala @@ -352,144 +352,17 @@ abstract class TreeBrowsers { * Tree. */ object TreeInfo { - /** Return the case class name and the Name, if the node defines one */ - def treeName(t: Tree): (String, Name) = t match { - case ProgramTree(units) => - ("Program", EMPTY) - - case UnitTree(unit) => - ("CompilationUnit", newTermName("" + unit)) - - case DocDef(comment, definition) => - ("DocDef", EMPTY) - - case ClassDef(mods, name, tparams, impl) => - ("ClassDef", name) - - case PackageDef(packaged, impl) => - ("PackageDef", EMPTY) - - case ModuleDef(mods, name, impl) => - ("ModuleDef", name) - - case ValDef(mods, name, tpe, rhs) => - ("ValDef", name) - - case DefDef(mods, name, tparams, vparams, tpe, rhs) => - ("DefDef", name) - - case TypeDef(mods, name, tparams, rhs) => - ("TypeDef", name) - - case Import(expr, selectors) => - ("Import", EMPTY) - - case CaseDef(pat, guard, body) => - ("CaseDef", EMPTY) - - case Template(parents, self, body) => - ("Template", EMPTY) - - case LabelDef(name, params, rhs) => - ("LabelDef", name) - - case Block(stats, expr) => - ("Block", EMPTY) - - case Alternative(trees) => - ("Alternative", EMPTY) - - case Bind(name, rhs) => - ("Bind", name) - - case UnApply(fun, args) => - ("UnApply", EMPTY) - - case Match(selector, cases) => - ("Visitor", EMPTY) - - case Function(vparams, body) => - ("Function", EMPTY) - - case Assign(lhs, rhs) => - ("Assign", EMPTY) - - case If(cond, thenp, elsep) => - ("If", EMPTY) - - case Return(expr) => - ("Return", EMPTY) - - case Throw(expr) => - ("Throw", EMPTY) - - case New(init) => - ("New", EMPTY) - - case Typed(expr, tpe) => - ("Typed", EMPTY) - - case TypeApply(fun, args) => - ("TypeApply", EMPTY) - - case Apply(fun, args) => - ("Apply", EMPTY) - - case ApplyDynamic(qual, args) => - ("Apply", EMPTY) - - case Super(qualif, mix) => - ("Super", newTermName("mix: " + mix)) - - case This(qualifier) => - ("This", qualifier) - - case Select(qualifier, selector) => - ("Select", selector) - - case Ident(name) => - ("Ident", name) - - case Literal(value) => - ("Literal", EMPTY) - - case TypeTree() => - ("TypeTree", EMPTY) - - case Annotated(annot, arg) => - ("Annotated", EMPTY) - - case SingletonTypeTree(ref) => - ("SingletonType", EMPTY) - - case SelectFromTypeTree(qualifier, selector) => - ("SelectFromType", selector) - - case CompoundTypeTree(template) => - ("CompoundType", EMPTY) - - case AppliedTypeTree(tpe, args) => - ("AppliedType", EMPTY) - - case TypeBoundsTree(lo, hi) => - ("TypeBoundsTree", EMPTY) - - case ExistentialTypeTree(tpt, whereClauses) => - ("ExistentialTypeTree", EMPTY) - - case Try(block, catcher, finalizer) => - ("Try", EMPTY) - - case EmptyTree => - ("Empty", EMPTY) - - case ArrayValue(elemtpt, trees) => - ("ArrayValue", EMPTY) - - case Star(t) => - ("Star", EMPTY) - } + def treeName(t: Tree): (String, Name) = ((t.printingPrefix, t match { + case UnitTree(unit) => newTermName("" + unit) + case Super(_, mix) => newTermName("mix: " + mix) + case This(qual) => qual + case Select(_, selector) => selector + case Ident(name) => name + case SelectFromTypeTree(_, selector) => selector + case x: DefTree => x.name + case _ => EMPTY + })) /** Return a list of children for the given tree node */ def children(t: Tree): List[Tree] = t match { -- cgit v1.2.3 From aafcc104b6ebe2fa163fc7a9cbfb742bf5f26d08 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Mon, 19 Mar 2012 16:14:58 -0700 Subject: Chill out repl ctrl-char filter. So colors can come through unscathed. --- .../scala/tools/nsc/interpreter/Naming.scala | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/interpreter/Naming.scala b/src/compiler/scala/tools/nsc/interpreter/Naming.scala index 8e215cf63b..19266442cb 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Naming.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Naming.scala @@ -11,16 +11,18 @@ package interpreter */ trait Naming { def unmangle(str: String): String = { + val ESC = '\u001b' val cleaned = removeIWPackages(removeLineWrapper(str)) - var ctrlChars = 0 - cleaned map { ch => - if (ch.isControl && !ch.isWhitespace) { - ctrlChars += 1 - if (ctrlChars > 5) return "[line elided for control chars: possibly a scala signature]" - else '?' - } - else ch - } + // Looking to exclude binary data which hoses the terminal, but + // let through the subset of it we need, like whitespace and also + // for ansi codes. + val binaryChars = cleaned count (ch => ch < 32 && !ch.isWhitespace && ch != ESC) + // Lots of binary chars - translate all supposed whitespace into spaces + if (binaryChars > 5) + cleaned map (ch => if (ch.isWhitespace) ' ' else if (ch < 32) '?' else ch) + // Not lots - preserve whitespace and ESC + else + cleaned map (ch => if (ch.isWhitespace || ch == ESC) ch else if (ch < 32) '?' else ch) } // The two name forms this is catching are the two sides of this assignment: -- cgit v1.2.3 From c7d852558302c5c4abc2eadacf42d51d5050c7f2 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Tue, 20 Mar 2012 01:20:52 +0100 Subject: Adapted indentation in scaladoc code blocks and fixed a pesky crash in the syntax highlighting caused by invalid chars (0x0E) in MarkupParser.scala. --- .../scala/tools/nsc/doc/html/SyntaxHigh.scala | 12 ++--- .../nsc/doc/model/comment/CommentFactory.scala | 60 +++++++++++++++++++++- src/library/scala/xml/parsing/MarkupParser.scala | 22 ++++---- test/scaladoc/resources/code-indent.scala | 37 +++++++++++++ test/scaladoc/scala/html/HtmlFactoryTest.scala | 19 ++++++- 5 files changed, 131 insertions(+), 19 deletions(-) create mode 100644 test/scaladoc/resources/code-indent.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/doc/html/SyntaxHigh.scala b/src/compiler/scala/tools/nsc/doc/html/SyntaxHigh.scala index f19f449d2c..f67abc58da 100644 --- a/src/compiler/scala/tools/nsc/doc/html/SyntaxHigh.scala +++ b/src/compiler/scala/tools/nsc/doc/html/SyntaxHigh.scala @@ -219,24 +219,24 @@ private[html] object SyntaxHigh { parse(" ", i+1) case '&' => parse("&", i+1) - case '<' => + case '<' if i+1 < buf.length => val ch = buf(i+1).toChar if (ch == '-' || ch == ':' || ch == '%') parse("<"+ch+"", i+2) else parse("<", i+1) case '>' => - if (buf(i+1) == ':') + if (i+1 < buf.length && buf(i+1) == ':') parse(">:", i+2) else parse(">", i+1) case '=' => - if (buf(i+1) == '>') + if (i+1 < buf.length && buf(i+1) == '>') parse("=>", i+2) else parse(buf(i).toChar.toString, i+1) case '/' => - if (buf(i+1) == '/' || buf(i+1) == '*') { + if (i+1 < buf.length && (buf(i+1) == '/' || buf(i+1) == '*')) { val c = comment(i+1) parse(""+c+"", i+c.length) } else @@ -257,9 +257,9 @@ private[html] object SyntaxHigh { else parse(buf(i).toChar.toString, i+1) case _ => - if (i == 0 || !Character.isJavaIdentifierPart(buf(i-1).toChar)) { + if (i == 0 || (i >= 1 && !Character.isJavaIdentifierPart(buf(i-1).toChar))) { if (Character.isDigit(buf(i)) || - (buf(i) == '.' && Character.isDigit(buf(i+1)))) { + (buf(i) == '.' && i + 1 < buf.length && Character.isDigit(buf(i+1)))) { val s = numlit(i) parse(""+s+"", i+s.length) } else { diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala index efa524503c..dbbd6ab432 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala @@ -491,7 +491,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => else jump("}}}") blockEnded("code block") - Code(getRead) + Code(normalizeIndentation(getRead)) } /** {{{ title ::= ('=' inline '=' | "==" inline "==" | ...) '\n' }}} */ @@ -732,6 +732,64 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => nextChar() } + /** + * Eliminates the (common) leading spaces in all lines, based on the first line + * For indented pieces of code, it reduces the indent to the least whitespace prefix: + * {{{ + * indented example + * another indented line + * if (condition) + * then do something; + * ^ this is the least whitespace prefix + * }}} + */ + def normalizeIndentation(_code: String): String = { + + var code = _code.trim + var maxSkip = Integer.MAX_VALUE + var crtSkip = 0 + var wsArea = true + var index = 0 + var firstLine = true + var emptyLine = true + + while (index < code.length) { + code(index) match { + case ' ' => + if (wsArea) + crtSkip += 1 + case c => + wsArea = (c == '\n') + maxSkip = if (firstLine || emptyLine) maxSkip else if (maxSkip <= crtSkip) maxSkip else crtSkip + crtSkip = if (c == '\n') 0 else crtSkip + firstLine = if (c == '\n') false else firstLine + emptyLine = if (c == '\n') true else false + } + index += 1 + } + + if (maxSkip == 0) + code + else { + index = 0 + val builder = new StringBuilder + while (index < code.length) { + builder.append(code(index)) + if (code(index) == '\n') { + // we want to skip as many spaces are available, if there are less spaces (like on empty lines, do not + // over-consume them) + index += 1 + val limit = index + maxSkip + while ((index < code.length) && (code(index) == ' ') && index < limit) + index += 1 + } + else + index += 1 + } + builder.toString + } + } + def checkParaEnded(): Boolean = { (char == endOfText) || ((char == endOfLine) && { diff --git a/src/library/scala/xml/parsing/MarkupParser.scala b/src/library/scala/xml/parsing/MarkupParser.scala index 1de08b3025..74781914e3 100644 --- a/src/library/scala/xml/parsing/MarkupParser.scala +++ b/src/library/scala/xml/parsing/MarkupParser.scala @@ -134,7 +134,7 @@ trait MarkupParser extends MarkupParserCommon with TokenTests // /** {{{ - * <? prolog ::= xml S ... ?> + * * }}} */ def xmlProcInstr(): MetaData = { xToken("xml") @@ -195,7 +195,7 @@ trait MarkupParser extends MarkupParserCommon with TokenTests } /** {{{ - * <? prolog ::= xml S? + * "{char} ) ']]>' * * see [15] * }}} */ @@ -369,7 +369,7 @@ trait MarkupParser extends MarkupParserCommon with TokenTests } /** {{{ - * Comment ::= '<!--' ((Char - '-') | ('-' (Char - '-')))* '-->' + * Comment ::= '' * * see [15] * }}} */ @@ -399,7 +399,7 @@ trait MarkupParser extends MarkupParserCommon with TokenTests } /** {{{ - * '<' content1 ::= ... + * '<' content1 ::= ... * }}} */ def content1(pscope: NamespaceBinding, ts: NodeBuffer) { ch match { @@ -420,7 +420,7 @@ trait MarkupParser extends MarkupParserCommon with TokenTests } /** {{{ - * content1 ::= '<' content1 | '&' charref ... + * content1 ::= '<' content1 | '&' charref ... * }}} */ def content(pscope: NamespaceBinding): NodeSeq = { var ts = new NodeBuffer @@ -490,7 +490,7 @@ trait MarkupParser extends MarkupParserCommon with TokenTests /** parses document type declaration and assigns it to instance variable * dtd. * {{{ - * <! parseDTD ::= DOCTYPE name ... > + * * }}} */ def parseDTD() { // dirty but fast var extID: ExternalID = null @@ -545,8 +545,8 @@ trait MarkupParser extends MarkupParserCommon with TokenTests } /** {{{ - * '<' element ::= xmlTag1 '>' { xmlExpr | '{' simpleExpr '}' } ETag - * | xmlTag1 '/' '>' + * '<' element ::= xmlTag1 '>' { xmlExpr | '{' simpleExpr '}' } ETag + * | xmlTag1 '/' '>' * }}} */ def element1(pscope: NamespaceBinding): NodeSeq = { val pos = this.pos @@ -778,7 +778,7 @@ trait MarkupParser extends MarkupParserCommon with TokenTests } /** {{{ - * <! attlist := ATTLIST + * { + val s = node.toString + s.contains("
a typicial indented\ncomment on multiple\ncomment lines
") && + s.contains("
one liner
") && + s.contains("
two lines, one useful
") && + s.contains("
line1\nline2\nline3\nline4
") && + s.contains("
a ragged example\na (condition)\n  the t h e n branch\nan alternative\n  the e l s e branch
") && + s.contains("
l1\n\nl2\n\nl3\n\nl4\n\nl5
") + } + case _ => false + } + } + { val files = createTemplates("basic.scala") //println(files) -- cgit v1.2.3 From 75ef45161d0599e43c33cbba8d253088bdfa9a79 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Mon, 19 Mar 2012 22:48:08 +0100 Subject: Adapted usecases to full signature display --- src/library/scala/collection/GenIterableLike.scala | 64 +++++---- src/library/scala/collection/GenMapLike.scala | 2 + src/library/scala/collection/GenSeqLike.scala | 157 +++++++++++++-------- .../scala/collection/GenTraversableLike.scala | 112 ++++++++------- .../scala/collection/GenTraversableOnce.scala | 53 ++++--- src/library/scala/collection/IterableLike.scala | 1 + src/library/scala/collection/Iterator.scala | 14 +- src/library/scala/collection/MapLike.scala | 14 +- src/library/scala/collection/SeqLike.scala | 49 ++++--- src/library/scala/collection/TraversableLike.scala | 76 +++++----- .../generic/GenericTraversableTemplate.scala | 25 ++-- src/library/scala/collection/immutable/List.scala | 16 ++- .../collection/parallel/ParIterableLike.scala | 3 +- .../scala/collection/parallel/ParSeqLike.scala | 16 ++- 14 files changed, 359 insertions(+), 243 deletions(-) (limited to 'src') diff --git a/src/library/scala/collection/GenIterableLike.scala b/src/library/scala/collection/GenIterableLike.scala index 7e68733afd..8fa5981969 100644 --- a/src/library/scala/collection/GenIterableLike.scala +++ b/src/library/scala/collection/GenIterableLike.scala @@ -39,18 +39,19 @@ trait GenIterableLike[+A, +Repr] extends Any with GenTraversableLike[A, Repr] { def iterator: Iterator[A] /** Checks if the other iterable collection contains the same elements in the same order as this $coll. - * - * $orderDependent - * $willNotTerminateInf * * @param that the collection to compare with. * @tparam B the type of the elements of collection `that`. * @return `true`, if both collections contain the same elements in the same order, `false` otherwise. * * @usecase def sameElements(that: GenIterable[A]): Boolean + * @inheritdoc * - * @param that the collection to compare with. - * @return `true`, if both collections contain the same elements in the same order, `false` otherwise. + * $orderDependent + * $willNotTerminateInf + * + * @param that the collection to compare with. + * @return `true`, if both collections contain the same elements in the same order, `false` otherwise. */ def sameElements[A1 >: A](that: GenIterable[A1]): Boolean @@ -58,8 +59,6 @@ trait GenIterableLike[+A, +Repr] extends Any with GenTraversableLike[A, Repr] { * by combining corresponding elements in pairs. * If one of the two collections is longer than the other, its remaining elements are ignored. * - * $orderDependent - * * @param that The iterable providing the second half of each result pair * @tparam A1 the type of the first half of the returned pairs (this is always a supertype * of the collection's element type `A`). @@ -71,18 +70,19 @@ trait GenIterableLike[+A, +Repr] extends Any with GenTraversableLike[A, Repr] { * of the returned collection is the minimum of the lengths of this $coll and `that`. * * @usecase def zip[B](that: GenIterable[B]): $Coll[(A, B)] + * @inheritdoc * - * @param that The iterable providing the second half of each result pair - * @tparam B the type of the second half of the returned pairs - * @return a new $coll containing pairs consisting of - * corresponding elements of this $coll and `that`. The length - * of the returned collection is the minimum of the lengths of this $coll and `that`. + * $orderDependent + * + * @param that The iterable providing the second half of each result pair + * @tparam B the type of the second half of the returned pairs + * @return a new $coll containing pairs consisting of + * corresponding elements of this $coll and `that`. The length + * of the returned collection is the minimum of the lengths of this $coll and `that`. */ def zip[A1 >: A, B, That](that: GenIterable[B])(implicit bf: CBF[Repr, (A1, B), That]): That /** Zips this $coll with its indices. - * - * $orderDependent * * @tparam A1 the type of the first half of the returned pairs (this is always a supertype * of the collection's element type `A`). @@ -98,11 +98,14 @@ trait GenIterableLike[+A, +Repr] extends Any with GenTraversableLike[A, Repr] { * $coll paired with their index. Indices start at `0`. * * @usecase def zipWithIndex: $Coll[(A, Int)] + * @inheritdoc * - * @return A new $coll containing pairs consisting of all elements of this - * $coll paired with their index. Indices start at `0`. - * @example - * `List("a", "b", "c").zipWithIndex = List(("a", 0), ("b", 1), ("c", 2))` + * $orderDependent + * + * @return A new $coll containing pairs consisting of all elements of this + * $coll paired with their index. Indices start at `0`. + * @example + * `List("a", "b", "c").zipWithIndex = List(("a", 0), ("b", 1), ("c", 2))` * */ def zipWithIndex[A1 >: A, That](implicit bf: CBF[Repr, (A1, Int), That]): That @@ -112,8 +115,6 @@ trait GenIterableLike[+A, +Repr] extends Any with GenTraversableLike[A, Repr] { * If one of the two collections is shorter than the other, * placeholder elements are used to extend the shorter collection to the length of the longer. * - * $orderDependent - * * @param that the iterable providing the second half of each result pair * @param thisElem the element to be used to fill up the result if this $coll is shorter than `that`. * @param thatElem the element to be used to fill up the result if `that` is shorter than this $coll. @@ -124,16 +125,19 @@ trait GenIterableLike[+A, +Repr] extends Any with GenTraversableLike[A, Repr] { * If `that` is shorter than this $coll, `thatElem` values are used to pad the result. * * @usecase def zipAll[B](that: Iterable[B], thisElem: A, thatElem: B): $Coll[(A, B)] - * - * @param that The iterable providing the second half of each result pair - * @param thisElem the element to be used to fill up the result if this $coll is shorter than `that`. - * @param thatElem the element to be used to fill up the result if `that` is shorter than this $coll. - * @tparam B the type of the second half of the returned pairs - * @return a new $coll containing pairs consisting of - * corresponding elements of this $coll and `that`. The length - * of the returned collection is the maximum of the lengths of this $coll and `that`. - * If this $coll is shorter than `that`, `thisElem` values are used to pad the result. - * If `that` is shorter than this $coll, `thatElem` values are used to pad the result. + * @inheritdoc + * + * $orderDependent + * + * @param that The iterable providing the second half of each result pair + * @param thisElem the element to be used to fill up the result if this $coll is shorter than `that`. + * @param thatElem the element to be used to fill up the result if `that` is shorter than this $coll. + * @tparam B the type of the second half of the returned pairs + * @return a new $coll containing pairs consisting of + * corresponding elements of this $coll and `that`. The length + * of the returned collection is the maximum of the lengths of this $coll and `that`. + * If this $coll is shorter than `that`, `thisElem` values are used to pad the result. + * If `that` is shorter than this $coll, `thatElem` values are used to pad the result. */ def zipAll[B, A1 >: A, That](that: GenIterable[B], thisElem: A1, thatElem: B)(implicit bf: CBF[Repr, (A1, B), That]): That diff --git a/src/library/scala/collection/GenMapLike.scala b/src/library/scala/collection/GenMapLike.scala index 12ecbcf140..114169c849 100644 --- a/src/library/scala/collection/GenMapLike.scala +++ b/src/library/scala/collection/GenMapLike.scala @@ -41,6 +41,8 @@ trait GenMapLike[A, +B, +Repr] extends GenIterableLike[(A, B), Repr] with Equals * @return the value associated with `key` if it exists, * otherwise the result of the `default` computation. * @usecase def getOrElse(key: A, default: => B): B + * @inheritdoc + * @tparam B the result type of the default computation. */ def getOrElse[B1 >: B](key: A, default: => B1): B1 diff --git a/src/library/scala/collection/GenSeqLike.scala b/src/library/scala/collection/GenSeqLike.scala index cb0e96fcbb..755abcd2bf 100644 --- a/src/library/scala/collection/GenSeqLike.scala +++ b/src/library/scala/collection/GenSeqLike.scala @@ -114,8 +114,6 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal def indexWhere(p: A => Boolean): Int = indexWhere(p, 0) /** Finds index of first occurrence of some value in this $coll. - * - * $mayNotTerminateInf * * @param elem the element value to search for. * @tparam B the type of the element `elem`. @@ -123,12 +121,14 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * to `elem`, or `-1`, if none exists. * * @usecase def indexOf(elem: A): Int + * @inheritdoc + * + * $mayNotTerminateInf + * */ def indexOf[B >: A](elem: B): Int = indexOf(elem, 0) /** Finds index of first occurrence of some value in this $coll after or at some start index. - * - * $mayNotTerminateInf * * @param elem the element value to search for. * @tparam B the type of the element `elem`. @@ -137,12 +137,14 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * to `elem`, or `-1`, if none exists. * * @usecase def indexOf(elem: A, from: Int): Int + * @inheritdoc + * + * $mayNotTerminateInf + * */ def indexOf[B >: A](elem: B, from: Int): Int = indexWhere(elem ==, from) /** Finds index of last occurrence of some value in this $coll. - * - * $willNotTerminateInf * * @param elem the element value to search for. * @tparam B the type of the element `elem`. @@ -150,6 +152,10 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * to `elem`, or `-1`, if none exists. * * @usecase def lastIndexOf(elem: A): Int + * @inheritdoc + * + * $willNotTerminateInf + * */ def lastIndexOf[B >: A](elem: B): Int = lastIndexWhere(elem ==) @@ -162,6 +168,7 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * to `elem`, or `-1`, if none exists. * * @usecase def lastIndexOf(elem: A, end: Int): Int + * @inheritdoc */ def lastIndexOf[B >: A](elem: B, end: Int): Int = lastIndexWhere(elem ==, end) @@ -195,10 +202,6 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * Builds a new collection by applying a function to all elements of this $coll and * collecting the results in reversed order. * - * $willNotTerminateInf - * - * Note: `xs.reverseMap(f)` is the same as `xs.reverse.map(f)` but might be more efficient. - * * @param f the function to apply to each element. * @tparam B the element type of the returned collection. * @tparam That $thatinfo @@ -207,10 +210,14 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * `f` to each element of this $coll and collecting the results in reversed order. * * @usecase def reverseMap[B](f: A => B): $Coll[B] + * @inheritdoc * - * Note: `xs.reverseMap(f)` is the same as `xs.reverse.map(f)` but might be more efficient. - * @return a new $coll resulting from applying the given function - * `f` to each element of this $coll and collecting the results in reversed order. + * $willNotTerminateInf + * + * Note: `xs.reverseMap(f)` is the same as `xs.reverse.map(f)` but might be more efficient. + * + * @return a new $coll resulting from applying the given function + * `f` to each element of this $coll and collecting the results in reversed order. */ def reverseMap[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That @@ -254,10 +261,13 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * @return a new $coll consisting of all elements of this $coll * except that `replaced` elements starting from `from` are replaced * by `patch`. + * * @usecase def patch(from: Int, that: GenSeq[A], replaced: Int): $Coll[A] - * @return a new $coll consisting of all elements of this $coll - * except that `replaced` elements starting from `from` are replaced - * by `patch`. + * @inheritdoc + * + * @return a new $coll consisting of all elements of this $coll + * except that `replaced` elements starting from `from` are replaced + * by `patch`. */ def patch[B >: A, That](from: Int, patch: GenSeq[B], replaced: Int)(implicit bf: CanBuildFrom[Repr, B, That]): That @@ -268,20 +278,33 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * @tparam That $thatinfo * @param bf $bfinfo * @return a new $coll` which is a copy of this $coll with the element at position `index` replaced by `elem`. + * * @usecase def updated(index: Int, elem: A): $Coll[A] - * @return a copy of this $coll with the element at position `index` replaced by `elem`. + * @inheritdoc + * + * @return a copy of this $coll with the element at position `index` replaced by `elem`. */ def updated[B >: A, That](index: Int, elem: B)(implicit bf: CanBuildFrom[Repr, B, That]): That /** A copy of the $coll with an element prepended. * - * Note that :-ending operators are right associative (see example). - * A mnemonic for `+:` vs. `:+` is: the COLon goes on the COLlection side. + * @param elem the prepended element + * @tparam B the element type of the returned $coll. + * @tparam That $thatinfo + * @param bf $bfinfo + * @return a new collection of type `That` consisting of `elem` followed + * by all elements of this $coll. + * + * @usecase def +:(elem: A): $Coll[A] + * @inheritdoc + * + * Note that :-ending operators are right associative (see example). + * A mnemonic for `+:` vs. `:+` is: the COLon goes on the COLlection side. * - * Also, the original $coll is not modified, so you will want to capture the result. + * Also, the original $coll is not modified, so you will want to capture the result. * - * Example: - * {{{ + * Example: + * {{{ * scala> val x = LinkedList(1) * x: scala.collection.mutable.LinkedList[Int] = LinkedList(1) * @@ -290,17 +313,10 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * * scala> println(x) * LinkedList(1) - * }}} + * }}} * - * @param elem the prepended element - * @tparam B the element type of the returned $coll. - * @tparam That $thatinfo - * @param bf $bfinfo - * @return a new collection of type `That` consisting of `elem` followed - * by all elements of this $coll. - * @usecase def +:(elem: A): $Coll[A] - * @return a new $coll consisting of `elem` followed - * by all elements of this $coll. + * @return a new $coll consisting of `elem` followed + * by all elements of this $coll. */ def +:[B >: A, That](elem: B)(implicit bf: CanBuildFrom[Repr, B, That]): That @@ -308,18 +324,20 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * * A mnemonic for `+:` vs. `:+` is: the COLon goes on the COLlection side. * - * $willNotTerminateInf * @param elem the appended element * @tparam B the element type of the returned $coll. * @tparam That $thatinfo * @param bf $bfinfo * @return a new collection of type `That` consisting of * all elements of this $coll followed by `elem`. + * * @usecase def :+(elem: A): $Coll[A] - * @return a new $coll consisting of - * all elements of this $coll followed by `elem`. - * @example - * {{{ + * @inheritdoc + * + * $willNotTerminateInf + * + * Example: + * {{{ * scala> import scala.collection.mutable.LinkedList * import scala.collection.mutable.LinkedList * @@ -331,7 +349,10 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * * scala> println(a) * LinkedList(1) - * }}} + * }}} + * + * @return a new $coll consisting of + * all elements of this $coll followed by `elem`. */ def :+[B >: A, That](elem: B)(implicit bf: CanBuildFrom[Repr, B, That]): That @@ -346,9 +367,11 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * all elements of this $coll followed by the minimal number of occurrences of `elem` so * that the resulting collection has a length of at least `len`. * @usecase def padTo(len: Int, elem: A): $Coll[A] - * @return a new $coll consisting of - * all elements of this $coll followed by the minimal number of occurrences of `elem` so - * that the resulting $coll has a length of at least `len`. + * @inheritdoc + * + * @return a new $coll consisting of + * all elements of this $coll followed by the minimal number of occurrences of `elem` so + * that the resulting $coll has a length of at least `len`. */ def padTo[B >: A, That](len: Int, elem: B)(implicit bf: CanBuildFrom[Repr, B, That]): That @@ -368,13 +391,6 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal /** Produces a new sequence which contains all elements of this $coll and also all elements of * a given sequence. `xs union ys` is equivalent to `xs ++ ys`. - * $willNotTerminateInf - * - * Another way to express this - * is that `xs union ys` computes the order-presevring multi-set union of `xs` and `ys`. - * `union` is hence a counter-part of `diff` and `intersect` which also work on multi-sets. - * - * $willNotTerminateInf * * @param that the sequence to add. * @tparam B the element type of the returned $coll. @@ -382,9 +398,18 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * @param bf $bfinfo * @return a new collection of type `That` which contains all elements of this $coll * followed by all elements of `that`. + * * @usecase def union(that: GenSeq[A]): $Coll[A] - * @return a new $coll which contains all elements of this $coll - * followed by all elements of `that`. + * @inheritdoc + * + * Another way to express this + * is that `xs union ys` computes the order-presevring multi-set union of `xs` and `ys`. + * `union` is hence a counter-part of `diff` and `intersect` which also work on multi-sets. + * + * $willNotTerminateInf + * + * @return a new $coll which contains all elements of this $coll + * followed by all elements of `that`. */ def union[B >: A, That](that: GenSeq[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = this ++ that @@ -393,7 +418,6 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal union(that: GenSeq[B])(bf) /** Computes the multiset difference between this $coll and another sequence. - * $willNotTerminateInf * * @param that the sequence of elements to remove * @tparam B the element type of the returned $coll. @@ -404,17 +428,21 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * If an element value `x` appears * ''n'' times in `that`, then the first ''n'' occurrences of `x` will not form * part of the result, but any following occurrences will. + * * @usecase def diff(that: GenSeq[A]): $Coll[A] - * @return a new $coll which contains all elements of this $coll - * except some of occurrences of elements that also appear in `that`. - * If an element value `x` appears - * ''n'' times in `that`, then the first ''n'' occurrences of `x` will not form - * part of the result, but any following occurrences will. + * @inheritdoc + * + * $willNotTerminateInf + * + * @return a new $coll which contains all elements of this $coll + * except some of occurrences of elements that also appear in `that`. + * If an element value `x` appears + * ''n'' times in `that`, then the first ''n'' occurrences of `x` will not form + * part of the result, but any following occurrences will. */ def diff[B >: A](that: GenSeq[B]): Repr /** Computes the multiset intersection between this $coll and another sequence. - * $mayNotTerminateInf * * @param that the sequence of elements to intersect with. * @tparam B the element type of the returned $coll. @@ -425,12 +453,17 @@ trait GenSeqLike[+A, +Repr] extends Any with GenIterableLike[A, Repr] with Equal * If an element value `x` appears * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained * in the result, but any following occurrences will be omitted. + * * @usecase def intersect(that: GenSeq[A]): $Coll[A] - * @return a new $coll which contains all elements of this $coll - * which also appear in `that`. - * If an element value `x` appears - * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained - * in the result, but any following occurrences will be omitted. + * @inheritdoc + * + * $mayNotTerminateInf + * + * @return a new $coll which contains all elements of this $coll + * which also appear in `that`. + * If an element value `x` appears + * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained + * in the result, but any following occurrences will be omitted. */ def intersect[B >: A](that: GenSeq[B]): Repr diff --git a/src/library/scala/collection/GenTraversableLike.scala b/src/library/scala/collection/GenTraversableLike.scala index dd5f602c41..fd03e0f446 100644 --- a/src/library/scala/collection/GenTraversableLike.scala +++ b/src/library/scala/collection/GenTraversableLike.scala @@ -136,6 +136,7 @@ trait GenTraversableLike[+A, +Repr] extends Any with GenTraversableOnce[A] with * but this is not necessary. * * @usecase def foreach(f: A => Unit): Unit + * @inheritdoc */ def foreach[U](f: A => U): Unit @@ -149,17 +150,15 @@ trait GenTraversableLike[+A, +Repr] extends Any with GenTraversableOnce[A] with * `f` to each element of this $coll and collecting the results. * * @usecase def map[B](f: A => B): $Coll[B] - * - * @return a new $coll resulting from applying the given function - * `f` to each element of this $coll and collecting the results. + * @inheritdoc + * @return a new $coll resulting from applying the given function + * `f` to each element of this $coll and collecting the results. */ def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That /** Builds a new collection by applying a partial function to all elements of this $coll * on which the function is defined. * - * $collectExample - * * @param pf the partial function which filters and maps the $coll. * @tparam B the element type of the returned collection. * @tparam That $thatinfo @@ -169,36 +168,18 @@ trait GenTraversableLike[+A, +Repr] extends Any with GenTraversableOnce[A] with * The order of the elements is preserved. * * @usecase def collect[B](pf: PartialFunction[A, B]): $Coll[B] + * @inheritdoc * - * @return a new $coll resulting from applying the given partial function - * `pf` to each element on which it is defined and collecting the results. - * The order of the elements is preserved. + * $collectExample + * + * @return a new $coll resulting from applying the given partial function + * `pf` to each element on which it is defined and collecting the results. + * The order of the elements is preserved. */ def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That /** Builds a new collection by applying a function to all elements of this $coll - * and using the elements of the resulting collections. For example: - * - * {{{ - * def getWords(lines: Seq[String]): Seq[String] = lines flatMap (line => line split "\\W+") - * }}} - * - * The type of the resulting collection is guided by the static type of $coll. This might - * cause unexpected results sometimes. For example: - * - * {{{ - * // lettersOf will return a Seq[Char] of likely repeated letters, instead of a Set - * def lettersOf(words: Seq[String]) = words flatMap (word => word.toSet) - * - * // lettersOf will return a Set[Char], not a Seq - * def lettersOf(words: Seq[String]) = words.toSet flatMap (word => word.toSeq) - * - * // xs will be a an Iterable[Int] - * val xs = Map("a" -> List(11,111), "b" -> List(22,222)).flatMap(_._2) - * - * // ys will be a Map[Int, Int] - * val ys = Map("a" -> List(1 -> 11,1 -> 111), "b" -> List(2 -> 22,2 -> 222)).flatMap(_._2) - * }}} + * and using the elements of the resulting collections. * * @param f the function to apply to each element. * @tparam B the element type of the returned collection. @@ -208,33 +189,39 @@ trait GenTraversableLike[+A, +Repr] extends Any with GenTraversableOnce[A] with * `f` to each element of this $coll and concatenating the results. * * @usecase def flatMap[B](f: A => GenTraversableOnce[B]): $Coll[B] + * @inheritdoc * - * @return a new $coll resulting from applying the given collection-valued function - * `f` to each element of this $coll and concatenating the results. - */ - def flatMap[B, That](f: A => GenTraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That - - /** Returns a new $coll containing the elements from the left hand operand followed by the elements from the - * right hand operand. The element type of the $coll is the most specific superclass encompassing - * the element types of the two operands (see example). + * For example: * - * Example: - * {{{ - * scala> val a = LinkedList(1) - * a: scala.collection.mutable.LinkedList[Int] = LinkedList(1) + * {{{ + * def getWords(lines: Seq[String]): Seq[String] = lines flatMap (line => line split "\\W+") + * }}} * - * scala> val b = LinkedList(2) - * b: scala.collection.mutable.LinkedList[Int] = LinkedList(2) + * The type of the resulting collection is guided by the static type of $coll. This might + * cause unexpected results sometimes. For example: * - * scala> val c = a ++ b - * c: scala.collection.mutable.LinkedList[Int] = LinkedList(1, 2) + * {{{ + * // lettersOf will return a Seq[Char] of likely repeated letters, instead of a Set + * def lettersOf(words: Seq[String]) = words flatMap (word => word.toSet) * - * scala> val d = LinkedList('a') - * d: scala.collection.mutable.LinkedList[Char] = LinkedList(a) + * // lettersOf will return a Set[Char], not a Seq + * def lettersOf(words: Seq[String]) = words.toSet flatMap (word => word.toSeq) * - * scala> val e = c ++ d - * e: scala.collection.mutable.LinkedList[AnyVal] = LinkedList(1, 2, a) - * }}} + * // xs will be a an Iterable[Int] + * val xs = Map("a" -> List(11,111), "b" -> List(22,222)).flatMap(_._2) + * + * // ys will be a Map[Int, Int] + * val ys = Map("a" -> List(1 -> 11,1 -> 111), "b" -> List(2 -> 22,2 -> 222)).flatMap(_._2) + * }}} + * + * @return a new $coll resulting from applying the given collection-valued function + * `f` to each element of this $coll and concatenating the results. + */ + def flatMap[B, That](f: A => GenTraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That + + /** Returns a new $coll containing the elements from the left hand operand followed by the elements from the + * right hand operand. The element type of the $coll is the most specific superclass encompassing + * the element types of the two operands. * * @param that the traversable to append. * @tparam B the element type of the returned collection. @@ -244,9 +231,28 @@ trait GenTraversableLike[+A, +Repr] extends Any with GenTraversableOnce[A] with * of this $coll followed by all elements of `that`. * * @usecase def ++[B](that: GenTraversableOnce[B]): $Coll[B] + * @inheritdoc + * + * Example: + * {{{ + * scala> val a = LinkedList(1) + * a: scala.collection.mutable.LinkedList[Int] = LinkedList(1) + * + * scala> val b = LinkedList(2) + * b: scala.collection.mutable.LinkedList[Int] = LinkedList(2) + * + * scala> val c = a ++ b + * c: scala.collection.mutable.LinkedList[Int] = LinkedList(1, 2) + * + * scala> val d = LinkedList('a') + * d: scala.collection.mutable.LinkedList[Char] = LinkedList(a) + * + * scala> val e = c ++ d + * e: scala.collection.mutable.LinkedList[AnyVal] = LinkedList(1, 2, a) + * }}} * - * @return a new $coll which contains all elements of this $coll - * followed by all elements of `that`. + * @return a new $coll which contains all elements of this $coll + * followed by all elements of `that`. */ def ++[B >: A, That](that: GenTraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That diff --git a/src/library/scala/collection/GenTraversableOnce.scala b/src/library/scala/collection/GenTraversableOnce.scala index 67ea4cdb00..f18e2ab6bb 100644 --- a/src/library/scala/collection/GenTraversableOnce.scala +++ b/src/library/scala/collection/GenTraversableOnce.scala @@ -316,11 +316,12 @@ trait GenTraversableOnce[+A] extends Any { * @return the sum of all elements of this $coll with respect to the `+` operator in `num`. * * @usecase def sum: A + * @inheritdoc * - * @return the sum of all elements in this $coll of numbers of type `Int`. - * Instead of `Int`, any other type `T` with an implicit `Numeric[T]` implementation - * can be used as element type of the $coll and as result type of `sum`. - * Examples of such types are: `Long`, `Float`, `Double`, `BigInt`. + * @return the sum of all elements in this $coll of numbers of type `Int`. + * Instead of `Int`, any other type `T` with an implicit `Numeric[T]` implementation + * can be used as element type of the $coll and as result type of `sum`. + * Examples of such types are: `Long`, `Float`, `Double`, `BigInt`. * */ def sum[A1 >: A](implicit num: Numeric[A1]): A1 @@ -333,11 +334,12 @@ trait GenTraversableOnce[+A] extends Any { * @return the product of all elements of this $coll with respect to the `*` operator in `num`. * * @usecase def product: A + * @inheritdoc * - * @return the product of all elements in this $coll of numbers of type `Int`. - * Instead of `Int`, any other type `T` with an implicit `Numeric[T]` implementation - * can be used as element type of the $coll and as result type of `product`. - * Examples of such types are: `Long`, `Float`, `Double`, `BigInt`. + * @return the product of all elements in this $coll of numbers of type `Int`. + * Instead of `Int`, any other type `T` with an implicit `Numeric[T]` implementation + * can be used as element type of the $coll and as result type of `product`. + * Examples of such types are: `Long`, `Float`, `Double`, `BigInt`. */ def product[A1 >: A](implicit num: Numeric[A1]): A1 @@ -348,7 +350,9 @@ trait GenTraversableOnce[+A] extends Any { * @return the smallest element of this $coll with respect to the ordering `cmp`. * * @usecase def min: A - * @return the smallest element of this $coll + * @inheritdoc + * + * @return the smallest element of this $coll */ def min[A1 >: A](implicit ord: Ordering[A1]): A @@ -359,7 +363,9 @@ trait GenTraversableOnce[+A] extends Any { * @return the largest element of this $coll with respect to the ordering `cmp`. * * @usecase def max: A - * @return the largest element of this $coll. + * @inheritdoc + * + * @return the largest element of this $coll. */ def max[A1 >: A](implicit ord: Ordering[A1]): A @@ -387,12 +393,13 @@ trait GenTraversableOnce[+A] extends Any { * Copying will stop once either the end of the current $coll is reached, * or the end of the array is reached. * - * $willNotTerminateInf - * * @param xs the array to fill. * @tparam B the type of the elements of the array. * * @usecase def copyToArray(xs: Array[A]): Unit + * @inheritdoc + * + * $willNotTerminateInf */ def copyToArray[B >: A](xs: Array[B]): Unit @@ -401,13 +408,14 @@ trait GenTraversableOnce[+A] extends Any { * Copying will stop once either the end of the current $coll is reached, * or the end of the array is reached. * - * $willNotTerminateInf - * * @param xs the array to fill. * @param start the starting index. * @tparam B the type of the elements of the array. * * @usecase def copyToArray(xs: Array[A], start: Int): Unit + * @inheritdoc + * + * $willNotTerminateInf */ def copyToArray[B >: A](xs: Array[B], start: Int): Unit @@ -450,15 +458,18 @@ trait GenTraversableOnce[+A] extends Any { def mkString: String /** Converts this $coll to an array. - * $willNotTerminateInf * * @tparam B the type of the elements of the array. A `ClassManifest` for * this type must be available. * @return an array containing all elements of this $coll. * * @usecase def toArray: Array[A] - * @return an array containing all elements of this $coll. - * A `ClassManifest` must be available for the element type of this $coll. + * @inheritdoc + * + * $willNotTerminateInf + * + * @return an array containing all elements of this $coll. + * A `ClassManifest` must be available for the element type of this $coll. */ def toArray[A1 >: A: ClassManifest]: Array[A1] @@ -530,11 +541,13 @@ trait GenTraversableOnce[+A] extends Any { * pair in the map. Duplicate keys will be overwritten by later keys: * if this is an unordered collection, which key is in the resulting map * is undefined. - * $willNotTerminateInf * @return a map containing all elements of this $coll. + * * @usecase def toMap[T, U]: Map[T, U] - * @return a map of type `immutable.Map[T, U]` - * containing all key/value pairs of type `(T, U)` of this $coll. + * @inheritdoc + * $willNotTerminateInf + * @return a map of type `immutable.Map[T, U]` + * containing all key/value pairs of type `(T, U)` of this $coll. */ def toMap[K, V](implicit ev: A <:< (K, V)): GenMap[K, V] } diff --git a/src/library/scala/collection/IterableLike.scala b/src/library/scala/collection/IterableLike.scala index 73d4efe125..fb6d154952 100644 --- a/src/library/scala/collection/IterableLike.scala +++ b/src/library/scala/collection/IterableLike.scala @@ -67,6 +67,7 @@ self => * Subclasses should re-implement this method if a more efficient implementation exists. * * @usecase def foreach(f: A => Unit): Unit + * @inheritdoc */ def foreach[U](f: A => U): Unit = iterator.foreach(f) diff --git a/src/library/scala/collection/Iterator.scala b/src/library/scala/collection/Iterator.scala index d46d215e0c..7b57a91c41 100644 --- a/src/library/scala/collection/Iterator.scala +++ b/src/library/scala/collection/Iterator.scala @@ -322,7 +322,9 @@ trait Iterator[+A] extends TraversableOnce[A] { * @return a new iterator that first yields the values produced by this * iterator followed by the values produced by iterator `that`. * @note Reuse: $consumesTwoAndProducesOneIterator + * * @usecase def ++(that: => Iterator[A]): Iterator[A] + * @inheritdoc */ def ++[B >: A](that: => GenTraversableOnce[B]): Iterator[B] = new AbstractIterator[B] { // optimize a little bit to prevent n log n behavior. @@ -609,7 +611,9 @@ trait Iterator[+A] extends TraversableOnce[A] { * followed by the minimal number of occurrences of `elem` so * that the number of produced values is at least `len`. * @note Reuse: $consumesAndProducesIterator + * * @usecase def padTo(len: Int, elem: A): Iterator[A] + * @inheritdoc */ def padTo[A1 >: A](len: Int, elem: A1): Iterator[A1] = new AbstractIterator[A1] { private var count = 0 @@ -658,7 +662,9 @@ trait Iterator[+A] extends TraversableOnce[A] { * If this iterator is shorter than `that`, `thisElem` values are used to pad the result. * If `that` is shorter than this iterator, `thatElem` values are used to pad the result. * @note Reuse: $consumesTwoAndProducesOneIterator + * * @usecase def zipAll[B](that: Iterator[B], thisElem: A, thatElem: B): Iterator[(A, B)] + * @inheritdoc */ def zipAll[B, A1 >: A, B1 >: B](that: Iterator[B], thisElem: A1, thatElem: B1): Iterator[(A1, B1)] = new AbstractIterator[(A1, B1)] { def hasNext = self.hasNext || that.hasNext @@ -682,7 +688,9 @@ trait Iterator[+A] extends TraversableOnce[A] { * but this is not necessary. * * @note Reuse: $consumesIterator + * * @usecase def foreach(f: A => Unit): Unit + * @inheritdoc */ def foreach[U](f: A => U) { while (hasNext) f(next()) } @@ -1057,15 +1065,17 @@ trait Iterator[+A] extends TraversableOnce[A] { * Copying will stop once either the end of the current iterator is reached, * or the end of the array is reached, or `len` elements have been copied. * - * $willNotTerminateInf - * * @param xs the array to fill. * @param start the starting index. * @param len the maximal number of elements to copy. * @tparam B the type of the elements of the array. * * @note Reuse: $consumesIterator + * * @usecase def copyToArray(xs: Array[A], start: Int, len: Int): Unit + * @inheritdoc + * + * $willNotTerminateInf */ def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): Unit = { var i = start diff --git a/src/library/scala/collection/MapLike.scala b/src/library/scala/collection/MapLike.scala index 07116e99dd..8f88e62791 100644 --- a/src/library/scala/collection/MapLike.scala +++ b/src/library/scala/collection/MapLike.scala @@ -91,14 +91,18 @@ self => * @param kv the key/value pair * @tparam B1 the type of the value in the key/value pair. * @return a new map with the new binding added to this map + * * @usecase def + (kv: (A, B)): Map[A, B] + * @inheritdoc */ def + [B1 >: B] (kv: (A, B1)): Map[A, B1] /** Removes a key from this map, returning a new map. * @param key the key to be removed * @return a new map without a binding for `key` + * * @usecase def - (key: A): Map[A, B] + * @inheritdoc */ def - (key: A): This @@ -115,7 +119,9 @@ self => * @tparam B1 the result type of the default computation. * @return the value associated with `key` if it exists, * otherwise the result of the `default` computation. + * * @usecase def getOrElse(key: A, default: => B): B + * @inheritdoc */ def getOrElse[B1 >: B](key: A, default: => B1): B1 = get(key) match { case Some(v) => v @@ -255,7 +261,9 @@ self => * @param value the value * @tparam B1 the type of the added value * @return A new map with the new key/value mapping added to this map. + * * @usecase def updated(key: A, value: B): Map[A, B] + * @inheritdoc */ def updated [B1 >: B](key: A, value: B1): Map[A, B1] = this + ((key, value)) @@ -269,8 +277,10 @@ self => * @param kvs the remaining key/value pairs * @tparam B1 the type of the added values * @return a new map with the given bindings added to this map + * * @usecase def + (kvs: (A, B)*): Map[A, B] - * @param the key/value pairs + * @inheritdoc + * @param the key/value pairs */ def + [B1 >: B] (kv1: (A, B1), kv2: (A, B1), kvs: (A, B1) *): Map[A, B1] = this + kv1 + kv2 ++ kvs @@ -280,7 +290,9 @@ self => * @param kvs the collection containing the added key/value pairs * @tparam B1 the type of the added values * @return a new map with the given bindings added to this map + * * @usecase def ++ (xs: Traversable[(A, B)]): Map[A, B] + * @inheritdoc */ def ++[B1 >: B](xs: GenTraversableOnce[(A, B1)]): Map[A, B1] = ((repr: Map[A, B1]) /: xs.seq) (_ + _) diff --git a/src/library/scala/collection/SeqLike.scala b/src/library/scala/collection/SeqLike.scala index 526ea7e240..fd1d42d7e9 100644 --- a/src/library/scala/collection/SeqLike.scala +++ b/src/library/scala/collection/SeqLike.scala @@ -410,13 +410,6 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ /** Produces a new sequence which contains all elements of this $coll and also all elements of * a given sequence. `xs union ys` is equivalent to `xs ++ ys`. - * $willNotTerminateInf - * - * Another way to express this - * is that `xs union ys` computes the order-presevring multi-set union of `xs` and `ys`. - * `union` is hence a counter-part of `diff` and `intersect` which also work on multi-sets. - * - * $willNotTerminateInf * * @param that the sequence to add. * @tparam B the element type of the returned $coll. @@ -425,14 +418,21 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ * @return a new collection of type `That` which contains all elements of this $coll * followed by all elements of `that`. * @usecase def union(that: Seq[A]): $Coll[A] - * @return a new $coll which contains all elements of this $coll - * followed by all elements of `that`. + * @inheritdoc + * + * Another way to express this + * is that `xs union ys` computes the order-presevring multi-set union of `xs` and `ys`. + * `union` is hence a counter-part of `diff` and `intersect` which also work on multi-sets. + * + * $willNotTerminateInf + * + * @return a new $coll which contains all elements of this $coll + * followed by all elements of `that`. */ override def union[B >: A, That](that: GenSeq[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = this ++ that /** Computes the multiset difference between this $coll and another sequence. - * $willNotTerminateInf * * @param that the sequence of elements to remove * @tparam B the element type of the returned $coll. @@ -444,11 +444,15 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ * ''n'' times in `that`, then the first ''n'' occurrences of `x` will not form * part of the result, but any following occurrences will. * @usecase def diff(that: Seq[A]): $Coll[A] - * @return a new $coll which contains all elements of this $coll - * except some of occurrences of elements that also appear in `that`. - * If an element value `x` appears - * ''n'' times in `that`, then the first ''n'' occurrences of `x` will not form - * part of the result, but any following occurrences will. + * @inheritdoc + * + * $willNotTerminateInf + * + * @return a new $coll which contains all elements of this $coll + * except some of occurrences of elements that also appear in `that`. + * If an element value `x` appears + * ''n'' times in `that`, then the first ''n'' occurrences of `x` will not form + * part of the result, but any following occurrences will. */ def diff[B >: A](that: GenSeq[B]): Repr = { val occ = occCounts(that.seq) @@ -463,7 +467,6 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ def diff[B >: A](that: Seq[B]): Repr = diff(that: GenSeq[B]) /** Computes the multiset intersection between this $coll and another sequence. - * $mayNotTerminateInf * * @param that the sequence of elements to intersect with. * @tparam B the element type of the returned $coll. @@ -475,11 +478,15 @@ trait SeqLike[+A, +Repr] extends Any with IterableLike[A, Repr] with GenSeqLike[ * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained * in the result, but any following occurrences will be omitted. * @usecase def intersect(that: Seq[A]): $Coll[A] - * @return a new $coll which contains all elements of this $coll - * which also appear in `that`. - * If an element value `x` appears - * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained - * in the result, but any following occurrences will be omitted. + * @inheritdoc + * + * $mayNotTerminateInf + * + * @return a new $coll which contains all elements of this $coll + * which also appear in `that`. + * If an element value `x` appears + * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained + * in the result, but any following occurrences will be omitted. */ def intersect[B >: A](that: GenSeq[B]): Repr = { val occ = occCounts(that.seq) diff --git a/src/library/scala/collection/TraversableLike.scala b/src/library/scala/collection/TraversableLike.scala index 0da1cfb913..1f5beb5109 100644 --- a/src/library/scala/collection/TraversableLike.scala +++ b/src/library/scala/collection/TraversableLike.scala @@ -102,10 +102,6 @@ trait TraversableLike[+A, +Repr] extends Any protected[this] def parCombiner = ParIterable.newCombiner[A] /** Applies a function `f` to all elements of this $coll. - * - * Note: this method underlies the implementation of most other bulk operations. - * It's important to implement this method in an efficient way. - * * * @param f the function that is applied for its side-effect to every element. * The result of function `f` is discarded. @@ -115,6 +111,11 @@ trait TraversableLike[+A, +Repr] extends Any * but this is not necessary. * * @usecase def foreach(f: A => Unit): Unit + * @inheritdoc + * + * Note: this method underlies the implementation of most other bulk operations. + * It's important to implement this method in an efficient way. + * */ def foreach[U](f: A => U): Unit @@ -165,17 +166,6 @@ trait TraversableLike[+A, +Repr] extends Any * the resulting collection rather than the left one. * Mnemonic: the COLon is on the side of the new COLlection type. * - * Example: - * {{{ - * scala> val x = List(1) - * x: List[Int] = List(1) - * - * scala> val y = LinkedList(2) - * y: scala.collection.mutable.LinkedList[Int] = LinkedList(2) - * - * scala> val z = x ++: y - * z: scala.collection.mutable.LinkedList[Int] = LinkedList(1, 2) - * }}} * @param that the traversable to append. * @tparam B the element type of the returned collection. * @tparam That $thatinfo @@ -184,9 +174,22 @@ trait TraversableLike[+A, +Repr] extends Any * of this $coll followed by all elements of `that`. * * @usecase def ++:[B](that: TraversableOnce[B]): $Coll[B] - * - * @return a new $coll which contains all elements of this $coll - * followed by all elements of `that`. + * @inheritdoc + * + * Example: + * {{{ + * scala> val x = List(1) + * x: List[Int] = List(1) + * + * scala> val y = LinkedList(2) + * y: scala.collection.mutable.LinkedList[Int] = LinkedList(2) + * + * scala> val z = x ++: y + * z: scala.collection.mutable.LinkedList[Int] = LinkedList(1, 2) + * }}} + * + * @return a new $coll which contains all elements of this $coll + * followed by all elements of `that`. */ def ++:[B >: A, That](that: TraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = { val b = bf(repr) @@ -284,11 +287,12 @@ trait TraversableLike[+A, +Repr] extends Any * The order of the elements is preserved. * * @usecase def filterMap[B](f: A => Option[B]): $Coll[B] + * @inheritdoc * - * @param pf the partial function which filters and maps the $coll. - * @return a new $coll resulting from applying the given option-valued function - * `f` to each element and collecting all defined results. - * The order of the elements is preserved. + * @param pf the partial function which filters and maps the $coll. + * @return a new $coll resulting from applying the given option-valued function + * `f` to each element and collecting all defined results. + * The order of the elements is preserved. def filterMap[B, That](f: A => Option[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = { val b = bf(repr) for (x <- this) @@ -588,8 +592,6 @@ trait TraversableLike[+A, +Repr] extends Any * Copying will stop once either the end of the current $coll is reached, * or the end of the array is reached, or `len` elements have been copied. * - * $willNotTerminateInf - * * @param xs the array to fill. * @param start the starting index. * @param len the maximal number of elements to copy. @@ -597,6 +599,9 @@ trait TraversableLike[+A, +Repr] extends Any * * * @usecase def copyToArray(xs: Array[A], start: Int, len: Int): Unit + * @inheritdoc + * + * $willNotTerminateInf */ def copyToArray[B >: A](xs: Array[B], start: Int, len: Int) { var i = start @@ -694,10 +699,11 @@ trait TraversableLike[+A, +Repr] extends Any * that satisfies predicate `p` and collecting the results. * * @usecase def map[B](f: A => B): $Coll[B] + * @inheritdoc * - * @return a new $coll resulting from applying the given function - * `f` to each element of the outer $coll that satisfies - * predicate `p` and collecting the results. + * @return a new $coll resulting from applying the given function + * `f` to each element of the outer $coll that satisfies + * predicate `p` and collecting the results. */ def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = { val b = bf(repr) @@ -710,9 +716,6 @@ trait TraversableLike[+A, +Repr] extends Any * outer $coll containing this `WithFilter` instance that satisfy * predicate `p` and concatenating the results. * - * The type of the resulting collection will be guided by the static type - * of the outer $coll. - * * @param f the function to apply to each element. * @tparam B the element type of the returned collection. * @tparam That $thatinfo @@ -723,11 +726,15 @@ trait TraversableLike[+A, +Repr] extends Any * concatenating the results. * * @usecase def flatMap[B](f: A => TraversableOnce[B]): $Coll[B] + * @inheritdoc + * + * The type of the resulting collection will be guided by the static type + * of the outer $coll. * - * @return a new $coll resulting from applying the given - * collection-valued function `f` to each element of the - * outer $coll that satisfies predicate `p` and concatenating - * the results. + * @return a new $coll resulting from applying the given + * collection-valued function `f` to each element of the + * outer $coll that satisfies predicate `p` and concatenating + * the results. */ def flatMap[B, That](f: A => GenTraversableOnce[B])(implicit bf: CanBuildFrom[Repr, B, That]): That = { val b = bf(repr) @@ -747,6 +754,7 @@ trait TraversableLike[+A, +Repr] extends Any * but this is not necessary. * * @usecase def foreach(f: A => Unit): Unit + * @inheritdoc */ def foreach[U](f: A => U): Unit = for (x <- self) diff --git a/src/library/scala/collection/generic/GenericTraversableTemplate.scala b/src/library/scala/collection/generic/GenericTraversableTemplate.scala index 7333074778..6586434924 100644 --- a/src/library/scala/collection/generic/GenericTraversableTemplate.scala +++ b/src/library/scala/collection/generic/GenericTraversableTemplate.scala @@ -119,22 +119,25 @@ trait GenericTraversableTemplate[+A, +CC[X] <: GenTraversable[X]] extends HasNew * a $coll formed by the elements of these traversable * collections. * - * The resulting collection's type will be guided by the - * static type of $coll. For example: - * - * {{{ - * val xs = List(Set(1, 2, 3), Set(1, 2, 3)) - * // xs == List(1, 2, 3, 1, 2, 3) - * - * val ys = Set(List(1, 2, 3), List(3, 2, 1)) - * // ys == Set(1, 2, 3) - * }}} - * * @tparam B the type of the elements of each traversable collection. * @param asTraversable an implicit conversion which asserts that the element * type of this $coll is a `GenTraversable`. * @return a new $coll resulting from concatenating all element ${coll}s. + * * @usecase def flatten[B]: $Coll[B] + * + * @inheritdoc + * + * The resulting collection's type will be guided by the + * static type of $coll. For example: + * + * {{{ + * val xs = List(Set(1, 2, 3), Set(1, 2, 3)) + * // xs == List(1, 2, 3, 1, 2, 3) + * + * val ys = Set(List(1, 2, 3), List(3, 2, 1)) + * // ys == Set(1, 2, 3) + * }}} */ def flatten[B](implicit asTraversable: A => /*<:: A] (x: B): List[B] = new scala.collection.immutable.::(x, this) @@ -103,8 +107,12 @@ sealed abstract class List[+A] extends AbstractSeq[A] * @param prefix The list elements to prepend. * @return a list resulting from the concatenation of the given * list `prefix` and this list. - * @example `List(1, 2) ::: List(3, 4) = List(3, 4).:::(List(1, 2)) = List(1, 2, 3, 4)` + * * @usecase def :::(prefix: List[A]): List[A] + * @inheritdoc + * + * Example: + * {{{List(1, 2) ::: List(3, 4) = List(3, 4).:::(List(1, 2)) = List(1, 2, 3, 4)}}} */ def :::[B >: A](prefix: List[B]): List[B] = if (isEmpty) prefix @@ -117,7 +125,9 @@ sealed abstract class List[+A] extends AbstractSeq[A] * * @param prefix the prefix to reverse and then prepend * @return the concatenation of the reversed prefix and the current list. + * * @usecase def reverse_:::(prefix: List[A]): List[A] + * @inheritdoc */ def reverse_:::[B >: A](prefix: List[B]): List[B] = { var these: List[B] = this @@ -137,7 +147,9 @@ sealed abstract class List[+A] extends AbstractSeq[A] * @tparam B the element type of the returned collection. * @return a list resulting from applying the given function * `f` to each element of this list and collecting the results. + * * @usecase def mapConserve(f: A => A): List[A] + * @inheritdoc */ def mapConserve[B >: A <: AnyRef](f: A => B): List[B] = { @tailrec diff --git a/src/library/scala/collection/parallel/ParIterableLike.scala b/src/library/scala/collection/parallel/ParIterableLike.scala index 7e0fa366ab..5551c04ce2 100644 --- a/src/library/scala/collection/parallel/ParIterableLike.scala +++ b/src/library/scala/collection/parallel/ParIterableLike.scala @@ -668,8 +668,9 @@ self: ParIterableLike[T, Repr, Sequential] => * @return a collection containing the prefix scan of the elements in the original collection * * @usecase def scan(z: T)(op: (T, T) => T): $Coll[T] + * @inheritdoc * - * @return a new $coll containing the prefix scan of the elements in this $coll + * @return a new $coll containing the prefix scan of the elements in this $coll */ def scan[U >: T, That](z: U)(op: (U, U) => U)(implicit bf: CanBuildFrom[Repr, U, That]): That = if (bf(repr).isCombiner) { if (tasksupport.parallelismLevel > 1) { diff --git a/src/library/scala/collection/parallel/ParSeqLike.scala b/src/library/scala/collection/parallel/ParSeqLike.scala index 9f28a286ca..b3c527da84 100644 --- a/src/library/scala/collection/parallel/ParSeqLike.scala +++ b/src/library/scala/collection/parallel/ParSeqLike.scala @@ -285,7 +285,6 @@ self => } /** Computes the multiset intersection between this $coll and another sequence. - * $mayNotTerminateInf * * @param that the sequence of elements to intersect with. * @tparam B the element type of the returned $coll. @@ -296,12 +295,17 @@ self => * If an element value `x` appears * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained * in the result, but any following occurrences will be omitted. + * * @usecase def intersect(that: Seq[T]): $Coll[T] - * @return a new $coll which contains all elements of this $coll - * which also appear in `that`. - * If an element value `x` appears - * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained - * in the result, but any following occurrences will be omitted. + * @inheritdoc + * + * $mayNotTerminateInf + * + * @return a new $coll which contains all elements of this $coll + * which also appear in `that`. + * If an element value `x` appears + * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained + * in the result, but any following occurrences will be omitted. */ def intersect[U >: T](that: GenSeq[U]) = sequentially { _ intersect that -- cgit v1.2.3 From c82ecabad6fc050411495f3fd50c3bf79ac7e96e Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Mon, 19 Mar 2012 18:26:55 -0700 Subject: Finally did something about broken irrefutability. The parser has always been confused about tuple patterns in for comprehensions. It thinks it can fail to recognize an irrefutable pattern and have it removed in refchecks, but it is sadly mistaken, because the unnecessary filter has a tendency to fail the compile in typer. Look more intently for irrefutable patterns and don't insert the unnecessary filter. Closes SI-5589, SI-1336. --- src/compiler/scala/reflect/internal/TreeInfo.scala | 20 ++++++++++++- .../scala/tools/nsc/ast/parser/TreeBuilder.scala | 34 ++++++++++------------ test/files/neg/t5589neg.check | 30 +++++++++++++++++++ test/files/neg/t5589neg.scala | 28 ++++++++++++++++++ test/files/pos/t1336.scala | 10 +++++++ test/files/pos/t5589.scala | 22 ++++++++++++++ 6 files changed, 124 insertions(+), 20 deletions(-) create mode 100644 test/files/neg/t5589neg.check create mode 100644 test/files/neg/t5589neg.scala create mode 100644 test/files/pos/t1336.scala create mode 100644 test/files/pos/t5589.scala (limited to 'src') diff --git a/src/compiler/scala/reflect/internal/TreeInfo.scala b/src/compiler/scala/reflect/internal/TreeInfo.scala index 769d7a9ed1..ce3de94335 100644 --- a/src/compiler/scala/reflect/internal/TreeInfo.scala +++ b/src/compiler/scala/reflect/internal/TreeInfo.scala @@ -17,7 +17,7 @@ abstract class TreeInfo { val global: SymbolTable import global._ - import definitions.{ isVarArgsList, isCastSymbol, ThrowableClass } + import definitions.{ isVarArgsList, isCastSymbol, ThrowableClass, TupleClass } /* Does not seem to be used. Not sure what it does anyway. def isOwnerDefinition(tree: Tree): Boolean = tree match { @@ -312,6 +312,24 @@ abstract class TreeInfo { case _ => false } + /** Is this tree comprised of nothing but identifiers, + * but possibly in bindings or tuples? For instance + * + * foo @ (bar, (baz, quux)) + * + * is a variable pattern; if the structure matches, + * then the remainder is inevitable. + */ + def isVariablePattern(tree: Tree): Boolean = tree match { + case Bind(name, pat) => isVariablePattern(pat) + case Ident(name) => true + case Apply(sel, args) => + ( isReferenceToScalaMember(sel, TupleClass(args.size).name.toTermName) + && (args forall isVariablePattern) + ) + case _ => false + } + /** Is this argument node of the form : _* ? */ def isWildcardStarArg(tree: Tree): Boolean = tree match { diff --git a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala index 0d2fbc5372..80c258e456 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala @@ -262,29 +262,25 @@ abstract class TreeBuilder { else if (stats.length == 1) stats.head else Block(stats.init, stats.last) + def makeFilter(tree: Tree, condition: Tree, scrutineeName: String): Tree = { + val cases = List( + CaseDef(condition, EmptyTree, Literal(Constant(true))), + CaseDef(Ident(nme.WILDCARD), EmptyTree, Literal(Constant(false))) + ) + val matchTree = makeVisitor(cases, false, scrutineeName) + + atPos(tree.pos)(Apply(Select(tree, nme.filter), matchTree :: Nil)) + } + /** Create tree for for-comprehension generator */ def makeGenerator(pos: Position, pat: Tree, valeq: Boolean, rhs: Tree): Enumerator = { val pat1 = patvarTransformer.transform(pat) val rhs1 = - if (valeq) rhs - else matchVarPattern(pat1) match { - case Some(_) => - rhs - case None => - atPos(rhs.pos) { - Apply( - Select(rhs, nme.filter), - List( - makeVisitor( - List( - CaseDef(pat1.duplicate, EmptyTree, Literal(Constant(true))), - CaseDef(Ident(nme.WILDCARD), EmptyTree, Literal(Constant(false)))), - false, - nme.CHECK_IF_REFUTABLE_STRING - ))) - } - } - if (valeq) ValEq(pos, pat1, rhs1) else ValFrom(pos, pat1, rhs1) + if (valeq || treeInfo.isVariablePattern(pat)) rhs + else makeFilter(rhs, pat1.duplicate, nme.CHECK_IF_REFUTABLE_STRING) + + if (valeq) ValEq(pos, pat1, rhs1) + else ValFrom(pos, pat1, rhs1) } def makeParam(pname: TermName, tpe: Tree) = diff --git a/test/files/neg/t5589neg.check b/test/files/neg/t5589neg.check new file mode 100644 index 0000000000..e75fd2f4f7 --- /dev/null +++ b/test/files/neg/t5589neg.check @@ -0,0 +1,30 @@ +t5589neg.scala:24: error: constructor cannot be instantiated to expected type; + found : (T1, T2) + required: String + def f5(x: Either[Int, String]) = for ((y1, y2: String) <- x.right) yield ((y1, y2)) + ^ +t5589neg.scala:25: error: constructor cannot be instantiated to expected type; + found : (T1, T2) + required: String + def f6(x: Either[Int, String]) = for ((y1, y2: Any) <- x.right) yield ((y1, y2)) + ^ +t5589neg.scala:26: error: constructor cannot be instantiated to expected type; + found : (T1,) + required: (String, Int) + def f7(x: Either[Int, (String, Int)]) = for (y1 @ Tuple1(y2) <- x.right) yield ((y1, y2)) + ^ +t5589neg.scala:26: error: not found: value y2 + def f7(x: Either[Int, (String, Int)]) = for (y1 @ Tuple1(y2) <- x.right) yield ((y1, y2)) + ^ +t5589neg.scala:27: error: constructor cannot be instantiated to expected type; + found : (T1, T2, T3) + required: (String, Int) + def f8(x: Either[Int, (String, Int)]) = for ((y1, y2, y3) <- x.right) yield ((y1, y2)) + ^ +t5589neg.scala:27: error: not found: value y1 + def f8(x: Either[Int, (String, Int)]) = for ((y1, y2, y3) <- x.right) yield ((y1, y2)) + ^ +t5589neg.scala:27: error: not found: value y2 + def f8(x: Either[Int, (String, Int)]) = for ((y1, y2, y3) <- x.right) yield ((y1, y2)) + ^ +7 errors found diff --git a/test/files/neg/t5589neg.scala b/test/files/neg/t5589neg.scala new file mode 100644 index 0000000000..ddd382d8d8 --- /dev/null +++ b/test/files/neg/t5589neg.scala @@ -0,0 +1,28 @@ +class A { + // First three compile. + def f1(x: Either[Int, String]) = x.right map (y => y) + def f2(x: Either[Int, String]) = for (y <- x.right) yield y + def f3(x: Either[Int, (String, Int)]) = x.right map { case (y1, y2) => (y1, y2) } + // Last one fails. + def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2)) +/** +./a.scala:5: error: constructor cannot be instantiated to expected type; + found : (T1, T2) + required: Either[Nothing,(String, Int)] + def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2)) + ^ +./a.scala:5: error: not found: value y1 + def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2)) + ^ +./a.scala:5: error: not found: value y2 + def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2)) + ^ +three errors found +**/ + + + def f5(x: Either[Int, String]) = for ((y1, y2: String) <- x.right) yield ((y1, y2)) + def f6(x: Either[Int, String]) = for ((y1, y2: Any) <- x.right) yield ((y1, y2)) + def f7(x: Either[Int, (String, Int)]) = for (y1 @ Tuple1(y2) <- x.right) yield ((y1, y2)) + def f8(x: Either[Int, (String, Int)]) = for ((y1, y2, y3) <- x.right) yield ((y1, y2)) +} diff --git a/test/files/pos/t1336.scala b/test/files/pos/t1336.scala new file mode 100644 index 0000000000..63967985c7 --- /dev/null +++ b/test/files/pos/t1336.scala @@ -0,0 +1,10 @@ +object Foo { + def foreach( f : ((Int,Int)) => Unit ) { + println("foreach") + f(1,2) + } + + for( (a,b) <- this ) { + println((a,b)) + } +} diff --git a/test/files/pos/t5589.scala b/test/files/pos/t5589.scala new file mode 100644 index 0000000000..69cbb20391 --- /dev/null +++ b/test/files/pos/t5589.scala @@ -0,0 +1,22 @@ +class A { + // First three compile. + def f1(x: Either[Int, String]) = x.right map (y => y) + def f2(x: Either[Int, String]) = for (y <- x.right) yield y + def f3(x: Either[Int, (String, Int)]) = x.right map { case (y1, y2) => (y1, y2) } + // Last one fails. + def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2)) +/** +./a.scala:5: error: constructor cannot be instantiated to expected type; + found : (T1, T2) + required: Either[Nothing,(String, Int)] + def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2)) + ^ +./a.scala:5: error: not found: value y1 + def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2)) + ^ +./a.scala:5: error: not found: value y2 + def f4(x: Either[Int, (String, Int)]) = for ((y1, y2) <- x.right) yield ((y1, y2)) + ^ +three errors found +**/ +} -- cgit v1.2.3 From 365bb2b4e3ac880243736bf039b649a63b00ccb2 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Mon, 19 Mar 2012 19:44:58 -0700 Subject: Discovered filter was still being generated. Rather than withFilter, for a subset of for comprehension structures. Not sure if this was somewhat by design - only seems possible because refchecks was only looking for nme.filter, not nme.withFilter, so perhaps this was intended as some secret irrefutability backchannel? Really have to document that sort of thing if it's intentional. I assumed it wasn't and unified everything. --- .../scala/tools/nsc/ast/parser/TreeBuilder.scala | 2 +- .../scala/tools/nsc/typechecker/RefChecks.scala | 2 +- test/files/pos/irrefutable.scala | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 test/files/pos/irrefutable.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala index 80c258e456..0bc88d1efd 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/TreeBuilder.scala @@ -269,7 +269,7 @@ abstract class TreeBuilder { ) val matchTree = makeVisitor(cases, false, scrutineeName) - atPos(tree.pos)(Apply(Select(tree, nme.filter), matchTree :: Nil)) + atPos(tree.pos)(Apply(Select(tree, nme.withFilter), matchTree :: Nil)) } /** Create tree for for-comprehension generator */ diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index ec42d251ff..73369f09af 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -1445,7 +1445,7 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R private def transformApply(tree: Apply): Tree = tree match { case Apply( - Select(qual, nme.filter), + Select(qual, nme.filter | nme.withFilter), List(Function( List(ValDef(_, pname, tpt, _)), Match(_, CaseDef(pat1, _, _) :: _)))) diff --git a/test/files/pos/irrefutable.scala b/test/files/pos/irrefutable.scala new file mode 100644 index 0000000000..0a792b644a --- /dev/null +++ b/test/files/pos/irrefutable.scala @@ -0,0 +1,22 @@ +// The test which this should perform but does not +// is that f1 is recognized as irrefutable and f2 is not +// This can be recognized via the generated classes: +// +// A$$anonfun$f1$1.class +// A$$anonfun$f2$1.class +// A$$anonfun$f2$2.class +// +// The extra one in $f2$ is the filter. +// +// !!! Marking with exclamation points so maybe someday +// this test will be finished. +class A { + case class Foo[T](x: T) + + def f1(xs: List[Foo[Int]]) = { + for (Foo(x: Int) <- xs) yield x + } + def f2(xs: List[Foo[Any]]) = { + for (Foo(x: Int) <- xs) yield x + } +} -- cgit v1.2.3 From eb8afde6882a945caa029a2ea9daeb43c590f5ca Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Tue, 20 Mar 2012 14:38:50 +0100 Subject: Fixes SI-5248 --- build.xml | 2 +- src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala | 2 +- src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala | 12 ++++++++++++ src/compiler/scala/tools/nsc/doc/html/page/Template.scala | 12 +++++++++--- 4 files changed, 23 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/build.xml b/build.xml index dc70330d7f..37794d1743 100644 --- a/build.xml +++ b/build.xml @@ -1607,7 +1607,7 @@ DOCUMENTATION /** The title of this page. */ protected def title: String + /** The page description */ + protected def description: String = + // unless overwritten, will display the title in a spaced format, keeping - and . + title.replaceAll("[^a-zA-Z0-9\\.\\-]+", " ").replaceAll("\\-+", " - ").replaceAll(" +", " ") + + /** The page keywords */ + protected def keywords: String = + // unless overwritten, same as description, minus the " - " + description.replaceAll(" - ", " ") + /** Additional header elements (links, scripts, meta tags, etc.) required for this page. */ protected def headers: NodeSeq @@ -35,6 +45,8 @@ abstract class HtmlPage extends Page { thisPage => { title } + + -- cgit v1.2.3 From 972bf59a65d98286697ca8eed6a80239259808e4 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 28 Feb 2012 16:47:47 +0100 Subject: [vpm] TODO note: make unapply type list stricter when an unapply returns Option[T] where T is some ProductN, does that mean the unapply returns 1 result, i.e., that T, or did it mean to return N results? to disambiguate, falling back to stricter spec-adherence, which requires T be exactly TupleN for N results for now, allow extractor result to be any product, not just tuple --- src/compiler/scala/tools/nsc/typechecker/Unapplies.scala | 5 +++++ test/files/run/virtpatmat_extends_product.check | 1 + test/files/run/virtpatmat_extends_product.flags | 1 + test/files/run/virtpatmat_extends_product.scala | 11 +++++++++++ 4 files changed, 18 insertions(+) create mode 100644 test/files/run/virtpatmat_extends_product.check create mode 100644 test/files/run/virtpatmat_extends_product.flags create mode 100644 test/files/run/virtpatmat_extends_product.scala (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala index cc272b7b8d..4f5b6868ae 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Unapplies.scala @@ -45,6 +45,11 @@ trait Unapplies extends ast.TreeDSL case BooleanClass => Nil case OptionClass | SomeClass => val prod = tp.typeArgs.head +// the spec doesn't allow just any subtype of Product, it *must* be TupleN[...] -- see run/virtpatmat_extends_product.scala +// this breaks plenty of stuff, though... +// val targs = +// if (isTupleType(prod)) getProductArgs(prod) +// else List(prod) val targs = getProductArgs(prod) if (targs.isEmpty || targs.tail.isEmpty) List(prod) // special n == 0 || n == 1 diff --git a/test/files/run/virtpatmat_extends_product.check b/test/files/run/virtpatmat_extends_product.check new file mode 100644 index 0000000000..c07e8385a7 --- /dev/null +++ b/test/files/run/virtpatmat_extends_product.check @@ -0,0 +1 @@ +AnnotationInfo(a,1) diff --git a/test/files/run/virtpatmat_extends_product.flags b/test/files/run/virtpatmat_extends_product.flags new file mode 100644 index 0000000000..ac6b805bd0 --- /dev/null +++ b/test/files/run/virtpatmat_extends_product.flags @@ -0,0 +1 @@ +-Yvirtpatmat diff --git a/test/files/run/virtpatmat_extends_product.scala b/test/files/run/virtpatmat_extends_product.scala new file mode 100644 index 0000000000..e564f4430b --- /dev/null +++ b/test/files/run/virtpatmat_extends_product.scala @@ -0,0 +1,11 @@ +object Test extends App { + case class AnnotationInfo(a: String, b: Int) extends Product2[String, Int] + + // if we're not careful in unapplyTypeListFromReturnType, the generated unapply is + // thought to return two components instead of one, since AnnotationInfo (the result of the unapply) is a Product2 + case class NestedAnnotArg(ai: AnnotationInfo) + + NestedAnnotArg(AnnotationInfo("a", 1)) match { + case NestedAnnotArg(x) => println(x) + } +} \ No newline at end of file -- cgit v1.2.3 From 3e0f24d2e78aa3d8dace0b2b253dfe7870b330fe Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Mon, 12 Mar 2012 17:37:04 +0100 Subject: [vpm] label-based translation of matches --- .../tools/nsc/typechecker/PatMatVirtualiser.scala | 358 ++++++++++----------- 1 file changed, 173 insertions(+), 185 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala b/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala index b060fd7121..552c236bde 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala @@ -43,7 +43,7 @@ trait PatMatVirtualiser extends ast.TreeDSL { self: Analyzer => val outer = newTermName("") val runOrElse = newTermName("runOrElse") val zero = newTermName("zero") - val _match = newTermName("__match") // don't call it __match, since that will trigger virtual pattern matching... + val _match = newTermName("__match") // don't call it __match, since that will trigger virtual pattern matching... def counted(str: String, i: Int) = newTermName(str+i) } @@ -177,7 +177,7 @@ trait PatMatVirtualiser extends ast.TreeDSL { self: Analyzer => CaseDef( Bind(exSym, Ident(nme.WILDCARD)), // TODO: does this need fixing upping? EmptyTree, - combineCasesNoSubstOnly(CODE.REF(exSym), scrutSym, casesNoSubstOnly, pt, matchOwner, scrut => Throw(CODE.REF(exSym))) + combineCasesNoSubstOnly(CODE.REF(exSym), scrutSym, casesNoSubstOnly, pt, matchOwner, casegen => scrut => Throw(CODE.REF(exSym))) ) }) } @@ -733,23 +733,25 @@ class Foo(x: Other) { x._1 } // no error in this order private[this] var currSub: Substitution = null // build Tree that chains `next` after the current extractor - def chainBefore(next: Tree, pt: Type): Tree + def chainBefore(next: Tree)(casegen: Casegen): Tree } - case class TrivialTreeMaker(tree: Tree) extends TreeMaker { - val localSubstitution: Substitution = EmptySubstitution - def chainBefore(next: Tree, pt: Type): Tree = tree + trait NoNewBinders extends TreeMaker { + protected val localSubstitution: Substitution = EmptySubstitution } - case class BodyTreeMaker(body: Tree, matchPt: Type) extends TreeMaker { - val localSubstitution: Substitution = EmptySubstitution - def chainBefore(next: Tree, pt: Type): Tree = // assert(next eq EmptyTree) - atPos(body.pos)(substitution(codegen.one(body, body.tpe, matchPt))) // since SubstOnly treemakers are dropped, need to do it here + case class TrivialTreeMaker(tree: Tree) extends TreeMaker with NoNewBinders { + def chainBefore(next: Tree)(casegen: Casegen): Tree = tree + } + + case class BodyTreeMaker(body: Tree, matchPt: Type) extends TreeMaker with NoNewBinders { + def chainBefore(next: Tree)(casegen: Casegen): Tree = // assert(next eq EmptyTree) + atPos(body.pos)(casegen.one(substitution(body))) // since SubstOnly treemakers are dropped, need to do it here } case class SubstOnlyTreeMaker(prevBinder: Symbol, nextBinder: Symbol) extends TreeMaker { val localSubstitution = Substitution(prevBinder, CODE.REF(nextBinder)) - def chainBefore(next: Tree, pt: Type): Tree = substitution(next) + def chainBefore(next: Tree)(casegen: Casegen): Tree = substitution(next) } abstract class FunTreeMaker extends TreeMaker { @@ -766,8 +768,8 @@ class Foo(x: Other) { x._1 } // no error in this order lazy val nextBinder = freshSym(pos, nextBinderTp) lazy val localSubstitution = Substitution(List(prevBinder), List(CODE.REF(nextBinder))) - def chainBefore(next: Tree, pt: Type): Tree = - atPos(pos)(codegen.flatMapCond(cond, res, nextBinder, nextBinderTp, substitution(next))) + def chainBefore(next: Tree)(casegen: Casegen): Tree = + atPos(pos)(casegen.flatMapCond(cond, res, nextBinder, substitution(next))) } /** @@ -778,11 +780,11 @@ class Foo(x: Other) { x._1 } // no error in this order * in this function's body, and all the subsequent ones, references to the symbols in `from` will be replaced by the corresponding tree in `to` */ case class ExtractorTreeMaker(extractor: Tree, extraCond: Option[Tree], nextBinder: Symbol, localSubstitution: Substitution)(extractorReturnsBoolean: Boolean) extends FunTreeMaker { - def chainBefore(next: Tree, pt: Type): Tree = { - val condAndNext = extraCond map (codegen.ifThenElseZero(_, next)) getOrElse next + def chainBefore(next: Tree)(casegen: Casegen): Tree = { + val condAndNext = extraCond map (casegen.ifThenElseZero(_, next)) getOrElse next atPos(extractor.pos)( - if (extractorReturnsBoolean) codegen.flatMapCond(extractor, CODE.UNIT, nextBinder, nextBinder.info.widen, substitution(condAndNext)) - else codegen.flatMap(extractor, nextBinder, substitution(condAndNext)) + if (extractorReturnsBoolean) casegen.flatMapCond(extractor, CODE.UNIT, nextBinder, substitution(condAndNext)) + else casegen.flatMap(extractor, nextBinder, substitution(condAndNext)) ) } @@ -791,10 +793,10 @@ class Foo(x: Other) { x._1 } // no error in this order // TODO: allow user-defined unapplyProduct case class ProductExtractorTreeMaker(prevBinder: Symbol, extraCond: Option[Tree], localSubstitution: Substitution) extends TreeMaker { import CODE._ - def chainBefore(next: Tree, pt: Type): Tree = { + def chainBefore(next: Tree)(casegen: Casegen): Tree = { val nullCheck = REF(prevBinder) OBJ_NE NULL val cond = extraCond map (nullCheck AND _) getOrElse nullCheck - codegen.ifThenElseZero(cond, substitution(next)) + casegen.ifThenElseZero(cond, substitution(next)) } override def toString = "P"+(prevBinder, extraCond getOrElse "", localSubstitution) @@ -907,58 +909,30 @@ class Foo(x: Other) { x._1 } // no error in this order override def toString = "ET"+(prevBinder, patTree) } - case class AlternativesTreeMaker(prevBinder: Symbol, var altss: List[List[TreeMaker]], pos: Position) extends TreeMaker { + case class AlternativesTreeMaker(prevBinder: Symbol, var altss: List[List[TreeMaker]], pos: Position) extends TreeMaker with NoNewBinders { // don't substitute prevBinder to nextBinder, a set of alternatives does not need to introduce a new binder, simply reuse the previous one - val localSubstitution: Substitution = EmptySubstitution override private[TreeMakers] def incorporateOuterSubstitution(outerSubst: Substitution): Unit = { super.incorporateOuterSubstitution(outerSubst) altss = altss map (alts => propagateSubstitution(alts, substitution)) } - def chainBefore(next: Tree, pt: Type): Tree = { import CODE._ - // next does not contain deftrees, is pretty short - val canDuplicate = { - var okToInline = true - var sizeBudget = 100 / (altss.length max 1) // yep, totally arbitrary! - object travOkToInline extends Traverser { override def traverse(tree: Tree): Unit = if (sizeBudget >= 0) { sizeBudget -= 1; tree match { - case TypeApply(_, _) | Apply(_, _) | Select(_, _) - | Block(_, _) | Assign(_, _) | If(_, _, _) | Typed(_, _) => super.traverse(tree) // these are allowed if their subtrees are - case EmptyTree | This(_) | New(_) | Literal(_) | Ident(_) => // these are always ok - case _ if tree.isType => // these are always ok - case _ => okToInline = false //; println("not inlining: "+ (tree, tree.getClass)) - }}} - travOkToInline.traverse(next) - // println("(okToInline, sizeBudget): "+ (okToInline, sizeBudget)) - okToInline && sizeBudget > 0 // must be strict comparison - } + def chainBefore(next: Tree)(codegenAlt: Casegen): Tree = { import CODE._ + atPos(pos){ + // one alternative may still generate multiple trees (e.g., an extractor call + equality test) + // (for now,) alternatives may not bind variables (except wildcards), so we don't care about the final substitution built internally by makeTreeMakers + val combinedAlts = altss map (altTreeMakers => + ((casegen: Casegen) => combineExtractors(altTreeMakers :+ TrivialTreeMaker(casegen.one(TRUE)))(casegen)) + ) - atPos(pos)( - if (canDuplicate) { - altss map {altTreeMakers => - combineExtractors(altTreeMakers :+ TrivialTreeMaker(substitution(next).duplicate), pt) - } reduceLeft codegen.typedOrElse(pt) - } else { - val rest = freshSym(pos, functionType(List(), inMatchMonad(pt)), "rest") - // rest.info.member(nme.apply).withAnnotation(AnnotationInfo(ScalaInlineClass.tpe, Nil, Nil)) - - // one alternative may still generate multiple trees (e.g., an extractor call + equality test) - // (for now,) alternatives may not bind variables (except wildcards), so we don't care about the final substitution built internally by makeTreeMakers - val combinedAlts = altss map (altTreeMakers => - combineExtractors(altTreeMakers :+ TrivialTreeMaker(REF(rest) APPLY ()), pt) - ) - BLOCK( - VAL(rest) === Function(Nil, substitution(next)), - combinedAlts reduceLeft codegen.typedOrElse(pt) - ) - } - ) + val findAltMatcher = codegenAlt.matcher(EmptyTree, NoSymbol, BooleanClass.tpe)(combinedAlts, Some((casegen: Casegen) => x => casegen.one(FALSE))) + codegenAlt.ifThenElseZero(findAltMatcher, substitution(next)) + } } } - case class GuardTreeMaker(guardTree: Tree) extends TreeMaker { - val localSubstitution: Substitution = EmptySubstitution - def chainBefore(next: Tree, pt: Type): Tree = codegen.flatMapGuard(substitution(guardTree), next) + case class GuardTreeMaker(guardTree: Tree) extends TreeMaker with NoNewBinders { + def chainBefore(next: Tree)(casegen: Casegen): Tree = casegen.flatMapGuard(substitution(guardTree), next) override def toString = "G("+ guardTree +")" } @@ -978,49 +952,40 @@ class Foo(x: Other) { x._1 } // no error in this order // calls propagateSubstitution on the treemakers def combineCases(scrut: Tree, scrutSym: Symbol, casesRaw: List[List[TreeMaker]], pt: Type, owner: Symbol): Tree = { val casesNoSubstOnly = casesRaw map (propagateSubstitution(_, EmptySubstitution)) // drops SubstOnlyTreeMakers, since their effect is now contained in the TreeMakers that follow them - combineCasesNoSubstOnly(scrut, scrutSym, casesNoSubstOnly, pt, owner, CODE.MATCHERROR(_)) + combineCasesNoSubstOnly(scrut, scrutSym, casesNoSubstOnly, pt, owner, casegen => CODE.MATCHERROR(_)) } - def combineCasesNoSubstOnly(scrut: Tree, scrutSym: Symbol, casesNoSubstOnly: List[List[TreeMaker]], pt: Type, owner: Symbol, matchFail: Tree => Tree): Tree = fixerUpper(owner, scrut.pos){ - emitSwitch(scrut, scrutSym, casesNoSubstOnly, pt).getOrElse{ - val (matcher, hasDefault, toHoist) = - if (casesNoSubstOnly nonEmpty) { - // when specified, need to propagate pt explicitly (type inferencer can't handle it) - val optPt = - if (isFullyDefined(pt)) inMatchMonad(pt) - else NoType - - // do this check on casesNoSubstOnly, since DCE will eliminate trivial cases like `case _ =>`, even if they're the last one - // exhaustivity and reachability must be checked before optimization as well - // TODO: improve, a trivial type test before the body still makes for a default case - // ("trivial" depends on whether we're emitting a straight match or an exception, or more generally, any supertype of scrutSym.tpe is a no-op) - val hasDefault = casesNoSubstOnly.nonEmpty && { - val nonTrivLast = casesNoSubstOnly.last - nonTrivLast.nonEmpty && nonTrivLast.head.isInstanceOf[BodyTreeMaker] - } - - val (cases, toHoist) = optimizeCases(scrutSym, casesNoSubstOnly, pt) - - val combinedCases = - cases.map(combineExtractors(_, pt)).reduceLeft(codegen.typedOrElse(optPt)) + def combineCasesNoSubstOnly(scrut: Tree, scrutSym: Symbol, casesNoSubstOnly: List[List[TreeMaker]], pt: Type, owner: Symbol, matchFail: Casegen => Tree => Tree): Tree = fixerUpper(owner, scrut.pos){ + val ptDefined = if (isFullyDefined(pt)) pt else NoType - (combinedCases, hasDefault, toHoist) - } else (codegen.zero, false, Nil) - - // catch-all - val catchAll = - if (hasDefault) None // no need for a catch-all when there's already a default - else Some(matchFail) - val expr = codegen.runOrElse(scrut, scrutSym, matcher, if (isFullyDefined(pt)) pt else NoType, catchAll) - if (toHoist isEmpty) expr - else Block(toHoist, expr) + emitSwitch(scrut, scrutSym, casesNoSubstOnly, pt).getOrElse{ + if (casesNoSubstOnly nonEmpty) { + // check casesNoSubstOnly for presence of a default case, since DCE will eliminate trivial cases like `case _ =>`, even if they're the last one + // exhaustivity and reachability must be checked before optimization as well + // TODO: improve, a trivial type test before the body still makes for a default case + // ("trivial" depends on whether we're emitting a straight match or an exception, or more generally, any supertype of scrutSym.tpe is a no-op) + val catchAll = + if (casesNoSubstOnly.nonEmpty && { + val nonTrivLast = casesNoSubstOnly.last + nonTrivLast.nonEmpty && nonTrivLast.head.isInstanceOf[BodyTreeMaker] + }) None + else Some(matchFail) + + val (cases, toHoist) = optimizeCases(scrutSym, casesNoSubstOnly, pt) + + val matchRes = codegen.matcher(scrut, scrutSym, pt)(cases map combineExtractors, catchAll) + + if (toHoist isEmpty) matchRes else Block(toHoist, matchRes) + } else { + codegen.matcher(scrut, scrutSym, pt)(Nil, Some(matchFail)) + } } } // combineExtractors changes the current substitution's of the tree makers in `treeMakers` // requires propagateSubstitution(treeMakers) has been called - def combineExtractors(treeMakers: List[TreeMaker], pt: Type): Tree = - treeMakers.foldRight (EmptyTree: Tree) (_.chainBefore(_, pt)) + def combineExtractors(treeMakers: List[TreeMaker])(casegen: Casegen): Tree = + treeMakers.foldRight(EmptyTree: Tree)((a, b) => a.chainBefore(b)(casegen)) // TODO: do this during tree construction, but that will require tracking the current owner in treemakers // TODO: assign more fine-grained positions @@ -1070,34 +1035,36 @@ class Foo(x: Other) { x._1 } // no error in this order /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// trait CodegenCore extends MatchMonadInterface { private var ctr = 0 - def freshSym(pos: Position, tp: Type = NoType, prefix: String = "x") = {ctr += 1; - // assert(owner ne null) - // assert(owner ne NoSymbol) - NoSymbol.newTermSymbol(vpmName.counted(prefix, ctr), pos) setInfo repackExistential(tp) - } + def freshName(prefix: String) = {ctr += 1; vpmName.counted(prefix, ctr)} + + // assert(owner ne null); assert(owner ne NoSymbol) + def freshSym(pos: Position, tp: Type = NoType, prefix: String = "x") = + NoSymbol.newTermSymbol(freshName(prefix), pos) setInfo repackExistential(tp) // codegen relevant to the structure of the translation (how extractors are combined) trait AbsCodegen { - def runOrElse(scrut: Tree, scrutSym: Symbol, matcher: Tree, resTp: Type, catchAll: Option[Tree => Tree]): Tree - def one(res: Tree, bodyPt: Type, matchPt: Type): Tree - def zero: Tree - def flatMap(prev: Tree, b: Symbol, next: Tree): Tree - def typedOrElse(pt: Type)(thisCase: Tree, elseCase: Tree): Tree + def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], catchAllGen: Option[Casegen => Tree => Tree]): Tree - def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, nextBinderTp: Type, next: Tree): Tree - def flatMapGuard(cond: Tree, next: Tree): Tree - - def fun(arg: Symbol, body: Tree): Tree - def ifThenElseZero(c: Tree, then: Tree): Tree - def _equals(checker: Tree, binder: Symbol): Tree + // local / context-free def _asInstanceOf(b: Symbol, tp: Type): Tree + def _equals(checker: Tree, binder: Symbol): Tree + def _isInstanceOf(b: Symbol, tp: Type): Tree + def and(a: Tree, b: Tree): Tree + def drop(tgt: Tree)(n: Int): Tree + def index(tgt: Tree)(i: Int): Tree def mkZero(tp: Type): Tree - def tupleSel(binder: Symbol)(i: Int): Tree - def index(tgt: Tree)(i: Int): Tree - def drop(tgt: Tree)(n: Int): Tree - def and(a: Tree, b: Tree): Tree - def _isInstanceOf(b: Symbol, tp: Type): Tree + } + + // structure + trait Casegen extends AbsCodegen { import CODE._ + def one(res: Tree): Tree + + def flatMap(prev: Tree, b: Symbol, next: Tree): Tree + def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, next: Tree): Tree + def flatMapGuard(cond: Tree, next: Tree): Tree + def ifThenElseZero(c: Tree, then: Tree): Tree = IF (c) THEN then ELSE zero + protected def zero: Tree } def codegen: AbsCodegen @@ -1112,7 +1079,6 @@ class Foo(x: Other) { x._1 } // no error in this order def drop(tgt: Tree)(n: Int): Tree = (tgt DOT vpmName.drop) (LIT(n)) def _equals(checker: Tree, binder: Symbol): Tree = checker MEMBER_== REF(binder) // NOTE: checker must be the target of the ==, that's the patmat semantics for ya def and(a: Tree, b: Tree): Tree = a AND b - def ifThenElseZero(c: Tree, then: Tree): Tree = IF (c) THEN then ELSE zero // the force is needed mainly to deal with the GADT typing hack (we can't detect it otherwise as tp nor pt need contain an abstract type, we're just casting wildly) def _asInstanceOf(t: Tree, tp: Type, force: Boolean = false): Tree = { val tpX = repackExistential(tp) @@ -1166,28 +1132,29 @@ class Foo(x: Other) { x._1 } // no error in this order trait PureCodegen extends CodegenCore with PureMatchMonadInterface { def codegen: AbsCodegen = pureCodegen - object pureCodegen extends CommonCodegen { import CODE._ + object pureCodegen extends CommonCodegen with Casegen { import CODE._ //// methods in MatchingStrategy (the monad companion) -- used directly in translation // __match.runOrElse(`scrut`)(`scrutSym` => `matcher`) // TODO: consider catchAll, or virtualized matching will break in exception handlers - def runOrElse(scrut: Tree, scrutSym: Symbol, matcher: Tree, resTp: Type, catchAll: Option[Tree => Tree]): Tree - = _match(vpmName.runOrElse) APPLY (scrut) APPLY (fun(scrutSym, matcher)) + def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], catchAllGen: Option[Casegen => Tree => Tree]): Tree = + _match(vpmName.runOrElse) APPLY (scrut) APPLY (fun(scrutSym, cases map (f => f(this)) reduceLeft typedOrElse)) + // __match.one(`res`) - def one(res: Tree, bodyPt: Type, matchPt: Type): Tree = (_match(vpmName.one)) (res) + def one(res: Tree): Tree = (_match(vpmName.one)) (res) // __match.zero def zero: Tree = _match(vpmName.zero) // __match.guard(`c`, `then`) - def guard(c: Tree, then: Tree, tp: Type): Tree = _match(vpmName.guard) APPLY (c, then) + def guard(c: Tree, then: Tree): Tree = _match(vpmName.guard) APPLY (c, then) //// methods in the monad instance -- used directly in translation // `prev`.flatMap(`b` => `next`) def flatMap(prev: Tree, b: Symbol, next: Tree): Tree = (prev DOT vpmName.flatMap)(fun(b, next)) // `thisCase`.orElse(`elseCase`) - def typedOrElse(pt: Type)(thisCase: Tree, elseCase: Tree): Tree = (thisCase DOT vpmName.orElse) APPLY (elseCase) + def typedOrElse(thisCase: Tree, elseCase: Tree): Tree = (thisCase DOT vpmName.orElse) APPLY (elseCase) // __match.guard(`cond`, `res`).flatMap(`nextBinder` => `next`) - def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, nextBinderTp: Type, next: Tree): Tree = flatMap(guard(cond, res, nextBinderTp), nextBinder, next) + def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, next: Tree): Tree = flatMap(guard(cond, res), nextBinder, next) // __match.guard(`guardTree`, ()).flatMap((_: P[Unit]) => `next`) - def flatMapGuard(guardTree: Tree, next: Tree): Tree = flatMapCond(guardTree, CODE.UNIT, freshSym(guardTree.pos, pureType(UnitClass.tpe)), pureType(UnitClass.tpe), next) + def flatMapGuard(guardTree: Tree, next: Tree): Tree = flatMapCond(guardTree, CODE.UNIT, freshSym(guardTree.pos, pureType(UnitClass.tpe)), next) } } @@ -1456,8 +1423,8 @@ class Foo(x: Other) { x._1 } // no error in this order } // TODO: finer-grained duplication - def chainBefore(next: Tree, pt: Type): Tree = // assert(codegen eq optimizedCodegen) - atPos(pos)(optimizedCodegen.flatMapCondStored(cond, storedCond, res, nextBinder, substitution(next).duplicate)) + def chainBefore(next: Tree)(casegen: Casegen): Tree = // assert(codegen eq optimizedCodegen) + atPos(pos)(casegen.asInstanceOf[optimizedCodegen.OptimizedCasegen].flatMapCondStored(cond, storedCond, res, nextBinder, substitution(next).duplicate)) } case class ReusingCondTreeMaker(sharedPrefix: List[Test], toReused: TreeMaker => TreeMaker) extends TreeMaker { import CODE._ @@ -1474,12 +1441,11 @@ class Foo(x: Other) { x._1 } // no error in this order oldSubs.foldLeft(Substitution(from, to))(_ >> _) } - def chainBefore(next: Tree, pt: Type): Tree = { + def chainBefore(next: Tree)(casegen: Casegen): Tree = { val cond = REF(dropped_priors.reverse.collectFirst{case (_, Some(ctm: ReusedCondTreeMaker)) => ctm}.get.storedCond) - IF (cond) THEN BLOCK( - substitution(next).duplicate // TODO: finer-grained duplication -- MUST duplicate though, or we'll get VerifyErrors since sharing trees confuses lambdalift, and its confusion it emits illegal casts (diagnosed by Grzegorz: checkcast T ; invokevirtual S.m, where T not a subtype of S) - ) ELSE codegen.zero + // TODO: finer-grained duplication -- MUST duplicate though, or we'll get VerifyErrors since sharing trees confuses lambdalift, and its confusion it emits illegal casts (diagnosed by Grzegorz: checkcast T ; invokevirtual S.m, where T not a subtype of S) + casegen.ifThenElseZero(cond, substitution(next).duplicate) } } } @@ -1657,8 +1623,7 @@ class Foo(x: Other) { x._1 } // no error in this order // for example, `o.flatMap(f)` becomes `if(o == None) None else f(o.get)`, similarly for orElse and guard // this is a special instance of the advanced inlining optimization that takes a method call on // an object of a type that only has two concrete subclasses, and inlines both bodies, guarded by an if to distinguish the two cases - object optimizedCodegen extends CommonCodegen /*with AbsOptimizedCodegen*/ { import CODE._ - lazy val zeroSym = freshSym(NoPosition, optionType(NothingClass.tpe), "zero") + object optimizedCodegen extends CommonCodegen { import CODE._ /** Inline runOrElse and get rid of Option allocations * @@ -1666,67 +1631,90 @@ class Foo(x: Other) { x._1 } // no error in this order * the matcher's optional result is encoded as a flag, keepGoing, where keepGoing == true encodes result.isEmpty, * if keepGoing is false, the result Some(x) of the naive translation is encoded as matchRes == x */ - @inline private def dontStore(tp: Type) = (tp.typeSymbol eq UnitClass) || (tp.typeSymbol eq NothingClass) - lazy val keepGoing = freshSym(NoPosition, BooleanClass.tpe, "keepGoing") setFlag MUTABLE - lazy val matchRes = freshSym(NoPosition, AnyClass.tpe, "matchRes") setFlag MUTABLE - def runOrElse(scrut: Tree, scrutSym: Symbol, matcher: Tree, resTp: Type, catchAll: Option[Tree => Tree]) = { - matchRes.info = if (resTp ne NoType) resTp.widen else AnyClass.tpe // we don't always know resTp, and it might be AnyVal, in which case we can't assign NULL - if (dontStore(resTp)) matchRes resetFlag MUTABLE // don't assign to Unit-typed var's, in fact, make it a val -- conveniently also works around SI-5245 - BLOCK( - VAL(zeroSym) === REF(NoneModule), // TODO: can we just get rid of explicitly emitted zero? don't know how to do that as a local rewrite... - VAL(scrutSym) === scrut, - VAL(matchRes) === mkZero(matchRes.info), // must cast to deal with GADT typing, hence the private mkZero above - VAL(keepGoing) === TRUE, - matcher, - catchAll map { catchAllGen => (IF (REF(keepGoing)) THEN catchAllGen(REF(scrutSym)) ELSE REF(matchRes)) } getOrElse REF(matchRes) - ) - } + def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], catchAllGen: Option[Casegen => Tree => Tree]): Tree = { + val matchEnd = NoSymbol.newLabel(freshName("matchEnd"), NoPosition) setFlag (SYNTHETIC | Flags.CASE) + val matchRes = NoSymbol.newValueParameter(newTermName("x"), NoPosition, SYNTHETIC) setInfo restpe + matchEnd setInfo MethodType(List(matchRes), restpe) + + def newCaseSym = NoSymbol.newLabel(freshName("case"), NoPosition) setInfo MethodType(Nil, restpe) setFlag (SYNTHETIC | Flags.CASE) + var nextCase = newCaseSym + def caseDef(mkCase: Casegen => Tree): Tree = { + val currCase = nextCase + nextCase = newCaseSym + val casegen = new OptimizedCasegen(matchEnd, nextCase) + LabelDef(currCase, Nil, mkCase(casegen)) + } - // only used to wrap the RHS of a body - def one(res: Tree, bodyPt: Type, matchPt: Type): Tree = { - BLOCK( - REF(keepGoing) === FALSE, // comes before assignment to matchRes, so the latter is in tail positions (can ignore the trailing zero -- will disappear when we flatten blocks, which is TODO) - if (dontStore(matchPt)) res else (REF(matchRes) === res), // runOrElse hasn't been called yet, so matchRes.isMutable is irrelevant, also, tp may be a subtype of resTp used in runOrElse... - zero // to have a nice lub for lubs -- otherwise we'll get a boxed unit here -- TODO: get rid of all those dangling else zero's + def catchAll = catchAllGen map { catchAllGen => + val casegen = new OptimizedCasegen(matchEnd, NoSymbol) + val scrutRef = if(scrutSym eq NoSymbol) EmptyTree else REF(scrutSym) + LabelDef(nextCase, Nil, catchAllGen(casegen)(scrutRef)) + } toList + + val prologue = if(scrutSym ne NoSymbol) List(VAL(scrutSym) === scrut) else Nil + Block( + prologue ++ (cases map caseDef) ++ catchAll, + LabelDef(matchEnd, List(matchRes), REF(matchRes)) ) } - def zero: Tree = REF(zeroSym) + class OptimizedCasegen(matchEnd: Symbol, nextCase: Symbol) extends CommonCodegen with Casegen { + def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], catchAllGen: Option[Casegen => Tree => Tree]): Tree = + optimizedCodegen.matcher(scrut, scrutSym, restpe)(cases, catchAllGen) - def flatMap(prev: Tree, b: Symbol, next: Tree): Tree = { - val tp = inMatchMonad(b.tpe) - val prevSym = freshSym(prev.pos, tp, "o") - val isEmpty = tp member vpmName.isEmpty - val get = tp member vpmName.get + def zero: Tree = nextCase APPLY () - BLOCK( - VAL(prevSym) === prev, - IF (prevSym DOT isEmpty) THEN zero ELSE Substitution(b, prevSym DOT get)(next) // must be isEmpty and get as we don't control the target of the call (could be the result of a user-defined extractor) - ) - } + // only used to wrap the RHS of a body + // res: T + // returns MatchMonad[T] + def one(res: Tree): Tree = matchEnd APPLY (res) - def typedOrElse(pt: Type)(thisCase: Tree, elseCase: Tree): Tree = { - BLOCK( - thisCase, - IF (REF(keepGoing)) THEN elseCase ELSE zero // leave trailing zero for now, otherwise typer adds () anyway - ) + + override def ifThenElseZero(c: Tree, then: Tree): Tree = + IF (c) THEN then ELSE zero + + // prev: MatchMonad[T] + // b: T + // next: MatchMonad[U] + // returns MatchMonad[U] + def flatMap(prev: Tree, b: Symbol, next: Tree): Tree = { + val tp = inMatchMonad(b.tpe) + val prevSym = freshSym(prev.pos, tp, "o") + val isEmpty = tp member vpmName.isEmpty + val get = tp member vpmName.get + + BLOCK( + VAL(prevSym) === prev, + // must be isEmpty and get as we don't control the target of the call (prev is an extractor call) + ifThenElseZero(NOT(prevSym DOT isEmpty), Substitution(b, prevSym DOT get)(next)) + ) + } + + // cond: Boolean + // res: T + // nextBinder: T + // next == MatchMonad[U] + // returns MatchMonad[U] + def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, next: Tree): Tree = + ifThenElseZero(cond, BLOCK( + VAL(nextBinder) === res, + next + )) + + // guardTree: Boolean + // next: MatchMonad[T] + // returns MatchMonad[T] + def flatMapGuard(guardTree: Tree, next: Tree): Tree = + ifThenElseZero(guardTree, next) + + def flatMapCondStored(cond: Tree, condSym: Symbol, res: Tree, nextBinder: Symbol, next: Tree): Tree = + ifThenElseZero(cond, BLOCK( + condSym === TRUE, + nextBinder === res, + next + )) } - def flatMapCond(cond: Tree, res: Tree, nextBinder: Symbol, nextBinderTp: Type, next: Tree): Tree = - IF (cond) THEN BLOCK( - VAL(nextBinder) === res, - next - ) ELSE zero - - def flatMapCondStored(cond: Tree, condSym: Symbol, res: Tree, nextBinder: Symbol, next: Tree): Tree = - IF (cond) THEN BLOCK( - condSym === TRUE, - nextBinder === res, - next - ) ELSE zero - - def flatMapGuard(guardTree: Tree, next: Tree): Tree = - IF (guardTree) THEN next ELSE zero } } -- cgit v1.2.3 From cd3d342032613e52e5917f3900f2461536a54e26 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 15 Mar 2012 19:07:15 +0100 Subject: [vpm] tailcalls support for jumpy vpm --- .../scala/tools/nsc/transform/TailCalls.scala | 126 +++++++++++++++++++-- .../tools/nsc/typechecker/PatMatVirtualiser.scala | 5 + 2 files changed, 119 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/transform/TailCalls.scala b/src/compiler/scala/tools/nsc/transform/TailCalls.scala index fdb5c7e52e..6ebecb02c6 100644 --- a/src/compiler/scala/tools/nsc/transform/TailCalls.scala +++ b/src/compiler/scala/tools/nsc/transform/TailCalls.scala @@ -36,6 +36,8 @@ abstract class TailCalls extends Transform { } } + private def hasSynthCaseSymbol(t: Tree) = (t.symbol ne null) && (t.symbol hasFlag (Flags.CASE | Flags.SYNTHETIC)) + /** * A Tail Call Transformer * @@ -87,10 +89,22 @@ abstract class TailCalls extends Transform { class TailCallElimination(unit: CompilationUnit) extends Transformer { private val defaultReason = "it contains a recursive call not in tail position" + /** Has the label been accessed? Then its symbol is in this set. */ + private val accessed = new collection.mutable.HashSet[Symbol]() + // `accessed` was stored as boolean in the current context -- this is no longer tenable + // with jumps to labels in tailpositions now considered in tailposition, + // a downstream context may access the label, and the upstream one will be none the wiser + // this is necessary because tail-calls may occur in places where syntactically they seem impossible + // (since we now consider jumps to labels that are in tailposition, such as matchEnd(x) {x}) + + class Context() { /** The current method */ var method: Symbol = NoSymbol + // symbols of label defs in this method that are in tail position + var tailLabels: Set[Symbol] = Set() + /** The current tail-call label */ var label: Symbol = NoSymbol @@ -104,24 +118,20 @@ abstract class TailCalls extends Transform { var failReason = defaultReason var failPos = method.pos - /** Has the label been accessed? */ - var accessed = false - def this(that: Context) = { this() this.method = that.method this.tparams = that.tparams this.tailPos = that.tailPos - this.accessed = that.accessed this.failPos = that.failPos this.label = that.label + this.tailLabels = that.tailLabels } def this(dd: DefDef) { this() this.method = dd.symbol this.tparams = dd.tparams map (_.symbol) this.tailPos = true - this.accessed = false this.failPos = dd.pos /** Create a new method symbol for the current method and store it in @@ -141,14 +151,14 @@ abstract class TailCalls extends Transform { def isEligible = method.isEffectivelyFinal // @tailrec annotation indicates mandatory transformation def isMandatory = method.hasAnnotation(TailrecClass) && !forMSIL - def isTransformed = isEligible && accessed + def isTransformed = isEligible && accessed(label) def tailrecFailure() = unit.error(failPos, "could not optimize @tailrec annotated " + method + ": " + failReason) def newThis(pos: Position) = method.newValue(nme.THIS, pos, SYNTHETIC) setInfo currentClass.typeOfThis override def toString(): String = ( "" + method.name + " tparams: " + tparams + " tailPos: " + tailPos + - " accessed: " + accessed + "\nLabel: " + label + "\nLabel type: " + label.info + " Label: " + label + " Label type: " + label.info ) } @@ -206,7 +216,7 @@ abstract class TailCalls extends Transform { def rewriteTailCall(recv: Tree): Tree = { debuglog("Rewriting tail recursive call: " + fun.pos.lineContent.trim) - ctx.accessed = true + accessed += ctx.label typedPos(fun.pos)(Apply(Ident(ctx.label), recv :: transformArgs)) } @@ -242,10 +252,16 @@ abstract class TailCalls extends Transform { unit.error(tree.pos, "@tailrec annotated method contains no recursive calls") } } - debuglog("Considering " + dd.name + " for tailcalls") + + // labels are local to a method, so only traverse the rhs of a defdef + val collectTailPosLabels = new TailPosLabelsTraverser + collectTailPosLabels traverse rhs0 + newCtx.tailLabels = collectTailPosLabels.tailLabels.toSet + + debuglog("Considering " + dd.name + " for tailcalls, with labels in tailpos: "+ newCtx.tailLabels) val newRHS = transform(rhs0, newCtx) - deriveDefDef(tree)(rhs => + deriveDefDef(tree){rhs => if (newCtx.isTransformed) { /** We have rewritten the tree, but there may be nested recursive calls remaining. * If @tailrec is given we need to fail those now. @@ -270,8 +286,22 @@ abstract class TailCalls extends Transform { newRHS } + } + + // a translated match + case Block(stats, expr) if stats forall hasSynthCaseSymbol => + // the assumption is once we encounter a case, the remainder of the block will consist of cases + // the prologue may be empty, usually it is the valdef that stores the scrut + val (prologue, cases) = stats span (s => !s.isInstanceOf[LabelDef]) + treeCopy.Block(tree, + noTailTransforms(prologue) ++ transformTrees(cases), + transform(expr) ) + // a translated casedef + case LabelDef(_, _, body) if hasSynthCaseSymbol(tree) => + deriveLabelDef(tree)(transform) + case Block(stats, expr) => treeCopy.Block(tree, noTailTransforms(stats), @@ -308,8 +338,18 @@ abstract class TailCalls extends Transform { case Apply(fun, args) => if (fun.symbol == Boolean_or || fun.symbol == Boolean_and) treeCopy.Apply(tree, fun, transformTrees(args)) - else - rewriteApply(fun, fun, Nil, args) + else if (fun.symbol.isLabel && args.nonEmpty && args.tail.isEmpty && ctx.tailLabels(fun.symbol)) { + // this is to detect tailcalls in translated matches + // it's a one-argument call to a label that is in a tailposition and that looks like label(x) {x} + // thus, the argument to the call is in tailposition and we don't need to jump to the label, tail jump instead + val saved = ctx.tailPos + ctx.tailPos = true + debuglog("in tailpos label: "+ args.head) + val res = transform(args.head) + ctx.tailPos = saved + if (res ne args.head) res // we tail-called -- TODO: shield from false-positives where we rewrite but don't tail-call + else rewriteApply(fun, fun, Nil, args) + } else rewriteApply(fun, fun, Nil, args) case Alternative(_) | Star(_) | Bind(_, _) => sys.error("We should've never gotten inside a pattern") @@ -320,4 +360,66 @@ abstract class TailCalls extends Transform { } } } + + // collect the LabelDefs (generated by the pattern matcher) in a DefDef that are in tail position + // the labels all look like: matchEnd(x) {x} + // then, in a forward jump `matchEnd(expr)`, `expr` is considered in tail position (and the matchEnd jump is replaced by the jump generated by expr) + class TailPosLabelsTraverser extends Traverser { + val tailLabels = new collection.mutable.ListBuffer[Symbol]() + + private var maybeTail: Boolean = true // since we start in the rhs of a DefDef + + def traverse(tree: Tree, maybeTailNew: Boolean): Unit = { + val saved = maybeTail + maybeTail = maybeTailNew + try traverse(tree) + finally maybeTail = saved + } + + def traverseNoTail(tree: Tree) = traverse(tree, false) + def traverseTreesNoTail(trees: List[Tree]) = trees foreach traverseNoTail + + override def traverse(tree: Tree) = tree match { + case LabelDef(_, List(arg), body@Ident(_)) if arg.symbol == body.symbol => // we're looking for label(x){x} in tail position, since that means `a` is in tail position in a call `label(a)` + if (maybeTail) tailLabels += tree.symbol + + // a translated casedef + case LabelDef(_, _, body) if hasSynthCaseSymbol(tree) => + traverse(body) + + // a translated match + case Block(stats, expr) if stats forall hasSynthCaseSymbol => + // the assumption is once we encounter a case, the remainder of the block will consist of cases + // the prologue may be empty, usually it is the valdef that stores the scrut + val (prologue, cases) = stats span (s => !s.isInstanceOf[LabelDef]) + traverseTreesNoTail(prologue) // selector (may be absent) + traverseTrees(cases) + traverse(expr) + + case CaseDef(pat, guard, body) => + traverse(body) + + case Match(selector, cases) => + traverseNoTail(selector) + traverseTrees(cases) + + case dd @ DefDef(_, _, _, _, _, _) => // we are run per-method + + case Block(stats, expr) => + traverseTreesNoTail(stats) + traverse(expr) + + case If(cond, thenp, elsep) => + traverse(thenp) + traverse(elsep) + + case Try(block, catches, finalizer) => + traverseNoTail(block) + traverseTreesNoTail(catches) + traverseNoTail(finalizer) + + case EmptyTree | Super(_, _) | This(_) | Select(_, _) | Ident(_) | Literal(_) | Function(_, _) | TypeTree() => + case _ => super.traverse(tree) + } + } } diff --git a/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala b/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala index 552c236bde..0422da54e0 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala @@ -1651,6 +1651,11 @@ class Foo(x: Other) { x._1 } // no error in this order LabelDef(nextCase, Nil, catchAllGen(casegen)(scrutRef)) } toList + // the generated block is taken apart in TailCalls under the following assumptions + // the assumption is once we encounter a case, the remainder of the block will consist of cases + // the prologue may be empty, usually it is the valdef that stores the scrut + // val (prologue, cases) = stats span (s => !s.isInstanceOf[LabelDef]) + val prologue = if(scrutSym ne NoSymbol) List(VAL(scrutSym) === scrut) else Nil Block( prologue ++ (cases map caseDef) ++ catchAll, -- cgit v1.2.3 From 2d3b309a36757545901b99da1b77698749fcc0c5 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 16 Mar 2012 11:55:44 +0100 Subject: specialize symbols for labeldef params --- src/compiler/scala/tools/nsc/typechecker/Duplicators.scala | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala index 29831c8469..eb0d489901 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Duplicators.scala @@ -256,8 +256,16 @@ abstract class Duplicators extends Analyzer { case ldef @ LabelDef(name, params, rhs) => // log("label def: " + ldef) ldef.tpe = null - val params1 = params map (p => Ident(updateSym(p.symbol))) - super.typed(treeCopy.LabelDef(tree, name, params1, rhs), mode, pt) + // since typer does not create the symbols for a LabelDef's params, + // we do that manually here -- we should really refactor LabelDef to be a subclass of DefDef + def newParam(p: Tree): Ident = { + val newsym = p.symbol.cloneSymbol //(context.owner) // TODO owner? + Ident(newsym.setInfo(fixType(p.symbol.info))) + } + val params1 = params map newParam + val rhs1 = (new TreeSubstituter(params map (_.symbol), params1) transform rhs) // TODO: duplicate? + rhs1.tpe = null + super.typed(treeCopy.LabelDef(tree, name, params1, rhs1), mode, pt) case Bind(name, _) => // log("bind: " + tree) -- cgit v1.2.3 From b046a6e3316df8b27ac31e71da1a139c800ccce7 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 14 Mar 2012 11:47:59 +0100 Subject: [vpm] typer synths Function{} for empty-sel match typedMatchAnonFun is called from typedFunction when the function's body is a match this is work-in-progres: the compiler currently won't bootstrap under -Yvirtpatmat see also the pending test make sure to use the right context in typeFunction when the body is a Match when typer is set up for type checking a Function, the current owner is the symbol for the function, but we'll type check a Block(List(ClassDef(cd)), New(cd)) when the function is a match, and the function symbol is nowhere to be found, so go to outer context in patmatvirt: - simplified default case gen (no need for a Casegen instance) - using CASE | SYNTHETIC to detect generated matches (for switches) and avoid typing them endlessly more uniform, and necessary for new-style anon Function class instance gen for matches --- src/compiler/scala/reflect/internal/Trees.scala | 5 +- .../scala/tools/nsc/transform/UnCurry.scala | 86 +++---- .../tools/nsc/typechecker/PatMatVirtualiser.scala | 141 ++++++----- .../scala/tools/nsc/typechecker/Typers.scala | 210 +++++++++++++---- test/files/pos/virtpatmat_anonfun_for.flags | 1 + test/files/pos/virtpatmat_anonfun_for.scala | 8 + test/files/run/virtpatmat_partial.check | 17 +- test/files/run/virtpatmat_partial.scala | 257 ++++++++++++++------- .../run/virtpatmat_anonfun_underscore.check | 0 .../run/virtpatmat_anonfun_underscore.flags | 1 + .../run/virtpatmat_anonfun_underscore.scala | 4 + 11 files changed, 481 insertions(+), 249 deletions(-) create mode 100644 test/files/pos/virtpatmat_anonfun_for.flags create mode 100644 test/files/pos/virtpatmat_anonfun_for.scala create mode 100644 test/pending/run/virtpatmat_anonfun_underscore.check create mode 100644 test/pending/run/virtpatmat_anonfun_underscore.flags create mode 100644 test/pending/run/virtpatmat_anonfun_underscore.scala (limited to 'src') diff --git a/src/compiler/scala/reflect/internal/Trees.scala b/src/compiler/scala/reflect/internal/Trees.scala index 9b1712b790..1a40e0105c 100644 --- a/src/compiler/scala/reflect/internal/Trees.scala +++ b/src/compiler/scala/reflect/internal/Trees.scala @@ -350,8 +350,9 @@ trait Trees extends api.Trees { self: SymbolTable => "subst[%s, %s](%s)".format(fromStr, toStr, (from, to).zipped map (_ + " -> " + _) mkString ", ") } - // NOTE: if symbols in `from` occur multiple times in the `tree` passed to `transform`, - // the resulting Tree will be a graph, not a tree... this breaks all sorts of stuff, + // NOTE: calls shallowDuplicate on trees in `to` to avoid problems when symbols in `from` + // occur multiple times in the `tree` passed to `transform`, + // otherwise, the resulting Tree would be a graph, not a tree... this breaks all sorts of stuff, // notably concerning the mutable aspects of Trees (such as setting their .tpe) class TreeSubstituter(from: List[Symbol], to: List[Tree]) extends Transformer { override def transform(tree: Tree): Tree = tree match { diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index ee565530b7..03bef83a90 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -237,8 +237,10 @@ abstract class UnCurry extends InfoTransform def targs = fun.tpe.typeArgs def isPartial = fun.tpe.typeSymbol == PartialFunctionClass + // if the function was eta-expanded, it's not a match without a selector if (fun1 ne fun) fun1 else { + assert(!(opt.virtPatmat && isPartial)) // empty-selector matches have already been translated into instantiations of anonymous (partial) functions val (formals, restpe) = (targs.init, targs.last) val anonClass = owner.newAnonymousFunctionClass(fun.pos, inConstructorFlag) def parents = @@ -286,52 +288,54 @@ abstract class UnCurry extends InfoTransform def defaultCase = CaseDef(Ident(nme.WILDCARD), EmptyTree, Literal(Constant(false))) - val casesNoSynthCatchAll = dropSyntheticCatchAll(cases) +// val casesNoSynthCatchAll = dropSyntheticCatchAll(cases) gen.mkUncheckedMatch( - if (casesNoSynthCatchAll exists treeInfo.isDefaultCase) Literal(Constant(true)) - else substTree(wrap(Match(selector, (casesNoSynthCatchAll map transformCase) :+ defaultCase)).duplicate) + if (cases exists treeInfo.isDefaultCase) Literal(Constant(true)) + else substTree(wrap(Match(selector, (cases map transformCase) :+ defaultCase)).duplicate) ) } - override def caseVirtualizedMatch(orig: Tree, _match: Tree, targs: List[Tree], scrut: Tree, matcher: Tree): Tree = { - object noOne extends Transformer { - override val treeCopy = newStrictTreeCopier // must duplicate everything - val one = _match.tpe member newTermName("one") - override def transform(tree: Tree): Tree = tree match { - case Apply(fun, List(a)) if fun.symbol == one => - // blow one's argument away since all we want to know is whether the match succeeds or not - // (the alternative, making `one` CBN, would entail moving away from Option) - Apply(fun.duplicate, List(gen.mkZeroContravariantAfterTyper(a.tpe))) - case _ => - super.transform(tree) - } - } - substTree(Apply(Apply(TypeApply(Select(_match.duplicate, _match.tpe.member(newTermName("isSuccess"))), targs map (_.duplicate)), List(scrut.duplicate)), List(noOne.transform(matcher)))) - } - - override def caseVirtualizedMatchOpt(orig: Tree, zero: ValDef, x: ValDef, matchRes: ValDef, keepGoing: ValDef, stats: List[Tree], epilogue: Tree, wrap: Tree => Tree) = { - object dropMatchResAssign extends Transformer { - // override val treeCopy = newStrictTreeCopier // will duplicate below - override def transform(tree: Tree): Tree = tree match { - // don't compute the result of the match -- remove the block for the RHS (emitted by pmgen.one), except for the assignment to keepGoing - case gen.VirtualCaseDef(assignKeepGoing, matchRes, zero) if assignKeepGoing.lhs.symbol eq keepGoing.symbol => - Block(List(assignKeepGoing), zero) - case _ => - super.transform(tree) - } - } - val statsNoMatchRes: List[Tree] = stats map (dropMatchResAssign.transform) toList - val idaBlock = wrap(Block( - zero :: - x :: - /* drop matchRes def */ - keepGoing :: - statsNoMatchRes, - NOT(REF(keepGoing.symbol)) // replace `if (keepGoing) throw new MatchError(...) else matchRes` epilogue by `!keepGoing` - )) - substTree(idaBlock.duplicate) // duplicate on block as a whole to ensure valdefs are properly cloned and substed - } + override def caseVirtualizedMatch(orig: Tree, _match: Tree, targs: List[Tree], scrut: Tree, matcher: Tree): Tree = {assert(false); orig} + // { + // object noOne extends Transformer { + // override val treeCopy = newStrictTreeCopier // must duplicate everything + // val one = _match.tpe member newTermName("one") + // override def transform(tree: Tree): Tree = tree match { + // case Apply(fun, List(a)) if fun.symbol == one => + // // blow one's argument away since all we want to know is whether the match succeeds or not + // // (the alternative, making `one` CBN, would entail moving away from Option) + // Apply(fun.duplicate, List(gen.mkZeroContravariantAfterTyper(a.tpe))) + // case _ => + // super.transform(tree) + // } + // } + // substTree(Apply(Apply(TypeApply(Select(_match.duplicate, _match.tpe.member(newTermName("isSuccess"))), targs map (_.duplicate)), List(scrut.duplicate)), List(noOne.transform(matcher)))) + // } + + override def caseVirtualizedMatchOpt(orig: Tree, zero: ValDef, x: ValDef, matchRes: ValDef, keepGoing: ValDef, stats: List[Tree], epilogue: Tree, wrap: Tree => Tree) = {assert(false); orig} + // { + // object dropMatchResAssign extends Transformer { + // // override val treeCopy = newStrictTreeCopier // will duplicate below + // override def transform(tree: Tree): Tree = tree match { + // // don't compute the result of the match -- remove the block for the RHS (emitted by pmgen.one), except for the assignment to keepGoing + // case gen.VirtualCaseDef(assignKeepGoing, matchRes, zero) if assignKeepGoing.lhs.symbol eq keepGoing.symbol => + // Block(List(assignKeepGoing), zero) + // case _ => + // super.transform(tree) + // } + // } + // val statsNoMatchRes: List[Tree] = stats map (dropMatchResAssign.transform) toList + // val idaBlock = wrap(Block( + // zero :: + // x :: + // /* drop matchRes def */ + // keepGoing :: + // statsNoMatchRes, + // NOT(REF(keepGoing.symbol)) // replace `if (keepGoing) throw new MatchError(...) else matchRes` epilogue by `!keepGoing` + // )) + // substTree(idaBlock.duplicate) // duplicate on block as a whole to ensure valdefs are properly cloned and substed + // } } DefDef(m, isDefinedAtTransformer(fun.body)) diff --git a/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala b/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala index 0422da54e0..34fefd20fe 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala @@ -49,7 +49,7 @@ trait PatMatVirtualiser extends ast.TreeDSL { self: Analyzer => } object MatchTranslator { - def apply(typer: Typer): MatchTranslation = { + def apply(typer: Typer): MatchTranslation with CodegenCore = { import typer._ // typing `_match` to decide which MatchTranslator to create adds 4% to quick.comp.timer newTyper(context.makeImplicit(reportAmbiguousErrors = false)).silent(_.typed(Ident(vpmName._match), EXPRmode, WildcardType), reportAmbiguousErrors = false) match { @@ -116,10 +116,6 @@ trait PatMatVirtualiser extends ast.TreeDSL { self: Analyzer => trait MatchTranslation extends MatchMonadInterface { self: TreeMakers with CodegenCore => import typer.{typed, context, silent, reallyExists} - private def repeatedToSeq(tp: Type): Type = (tp baseType RepeatedParamClass) match { - case TypeRef(_, RepeatedParamClass, args) => appliedType(SeqClass.typeConstructor, args) - case _ => tp - } /** Implement a pattern match by turning its cases (including the implicit failure case) * into the corresponding (monadic) extractors, and combining them with the `orElse` combinator. @@ -131,18 +127,15 @@ trait PatMatVirtualiser extends ast.TreeDSL { self: Analyzer => * thus, you must typecheck the result (and that will in turn translate nested matches) * this could probably optimized... (but note that the matchStrategy must be solved for each nested patternmatch) */ - def translateMatch(scrut: Tree, cases: List[CaseDef], pt: Type): Tree = { + def translateMatch(scrut: Tree, cases: List[CaseDef], pt: Type, scrutType: Type, matchFailGenOverride: Option[Tree => Tree] = None): Tree = { // we don't transform after typers // (that would require much more sophistication when generating trees, // and the only place that emits Matches after typers is for exception handling anyway) assert(phase.id <= currentRun.typerPhase.id, phase) - val scrutType = repeatedToSeq(elimAnonymousClass(scrut.tpe.widen)) - - val scrutSym = freshSym(scrut.pos, pureType(scrutType)) - val okPt = repeatedToSeq(pt) + val scrutSym = freshSym(scrut.pos, pureType(scrutType)) setFlag (Flags.CASE | SYNTHETIC) // the flags allow us to detect generated matches by looking at the scrutinee's symbol (needed to avoid recursing endlessly on generated switches) // pt = Any* occurs when compiling test/files/pos/annotDepMethType.scala with -Xexperimental - combineCases(scrut, scrutSym, cases map translateCase(scrutSym, okPt), okPt, matchOwner) + combineCases(scrut, scrutSym, cases map translateCase(scrutSym, pt), pt, matchOwner, matchFailGenOverride) } // return list of typed CaseDefs that are supported by the backend (typed/bind/wildcard) @@ -154,13 +147,12 @@ trait PatMatVirtualiser extends ast.TreeDSL { self: Analyzer => // if they're already simple enough to be handled by the back-end, we're done if (caseDefs forall treeInfo.isCatchCase) caseDefs else { - val okPt = repeatedToSeq(pt) val switch = { val bindersAndCases = caseDefs map { caseDef => // generate a fresh symbol for each case, hoping we'll end up emitting a type-switch (we don't have a global scrut there) // if we fail to emit a fine-grained switch, have to do translateCase again with a single scrutSym (TODO: uniformize substitution on treemakers so we can avoid this) val caseScrutSym = freshSym(pos, pureType(ThrowableClass.tpe)) - (caseScrutSym, propagateSubstitution(translateCase(caseScrutSym, okPt)(caseDef), EmptySubstitution)) + (caseScrutSym, propagateSubstitution(translateCase(caseScrutSym, pt)(caseDef), EmptySubstitution)) } (emitTypeSwitch(bindersAndCases, pt) map (_.map(fixerUpper(matchOwner, pos).apply(_).asInstanceOf[CaseDef]))) @@ -168,7 +160,7 @@ trait PatMatVirtualiser extends ast.TreeDSL { self: Analyzer => val catches = switch getOrElse { val scrutSym = freshSym(pos, pureType(ThrowableClass.tpe)) - val casesNoSubstOnly = caseDefs map { caseDef => (propagateSubstitution(translateCase(scrutSym, okPt)(caseDef), EmptySubstitution))} + val casesNoSubstOnly = caseDefs map { caseDef => (propagateSubstitution(translateCase(scrutSym, pt)(caseDef), EmptySubstitution))} val exSym = freshSym(pos, pureType(ThrowableClass.tpe), "ex") @@ -177,7 +169,7 @@ trait PatMatVirtualiser extends ast.TreeDSL { self: Analyzer => CaseDef( Bind(exSym, Ident(nme.WILDCARD)), // TODO: does this need fixing upping? EmptyTree, - combineCasesNoSubstOnly(CODE.REF(exSym), scrutSym, casesNoSubstOnly, pt, matchOwner, casegen => scrut => Throw(CODE.REF(exSym))) + combineCasesNoSubstOnly(CODE.REF(exSym), scrutSym, casesNoSubstOnly, pt, matchOwner, Some(scrut => Throw(CODE.REF(exSym)))) ) }) } @@ -706,10 +698,10 @@ class Foo(x: Other) { x._1 } // no error in this order def optimizeCases(prevBinder: Symbol, cases: List[List[TreeMaker]], pt: Type): (List[List[TreeMaker]], List[Tree]) = (cases, Nil) - def emitSwitch(scrut: Tree, scrutSym: Symbol, cases: List[List[TreeMaker]], pt: Type): Option[Tree] = + def emitSwitch(scrut: Tree, scrutSym: Symbol, cases: List[List[TreeMaker]], pt: Type, matchFailGenOverride: Option[Tree => Tree]): Option[Tree] = None - // for catch + // for catch (no need to customize match failure) def emitTypeSwitch(bindersAndCases: List[(Symbol, List[TreeMaker])], pt: Type): Option[List[CaseDef]] = None @@ -925,7 +917,7 @@ class Foo(x: Other) { x._1 } // no error in this order ((casegen: Casegen) => combineExtractors(altTreeMakers :+ TrivialTreeMaker(casegen.one(TRUE)))(casegen)) ) - val findAltMatcher = codegenAlt.matcher(EmptyTree, NoSymbol, BooleanClass.tpe)(combinedAlts, Some((casegen: Casegen) => x => casegen.one(FALSE))) + val findAltMatcher = codegenAlt.matcher(EmptyTree, NoSymbol, BooleanClass.tpe)(combinedAlts, Some(x => FALSE)) codegenAlt.ifThenElseZero(findAltMatcher, substitution(next)) } } @@ -936,6 +928,12 @@ class Foo(x: Other) { x._1 } // no error in this order override def toString = "G("+ guardTree +")" } + // combineExtractors changes the current substitution's of the tree makers in `treeMakers` + // requires propagateSubstitution(treeMakers) has been called + def combineExtractors(treeMakers: List[TreeMaker])(casegen: Casegen): Tree = + treeMakers.foldRight(EmptyTree: Tree)((a, b) => a.chainBefore(b)(casegen)) + + def removeSubstOnly(makers: List[TreeMaker]) = makers filterNot (_.isInstanceOf[SubstOnlyTreeMaker]) // a foldLeft to accumulate the localSubstitution left-to-right @@ -950,42 +948,42 @@ class Foo(x: Other) { x._1 } // no error in this order } // calls propagateSubstitution on the treemakers - def combineCases(scrut: Tree, scrutSym: Symbol, casesRaw: List[List[TreeMaker]], pt: Type, owner: Symbol): Tree = { - val casesNoSubstOnly = casesRaw map (propagateSubstitution(_, EmptySubstitution)) // drops SubstOnlyTreeMakers, since their effect is now contained in the TreeMakers that follow them - combineCasesNoSubstOnly(scrut, scrutSym, casesNoSubstOnly, pt, owner, casegen => CODE.MATCHERROR(_)) + def combineCases(scrut: Tree, scrutSym: Symbol, casesRaw: List[List[TreeMaker]], pt: Type, owner: Symbol, matchFailGenOverride: Option[Tree => Tree]): Tree = { + // drops SubstOnlyTreeMakers, since their effect is now contained in the TreeMakers that follow them + val casesNoSubstOnly = casesRaw map (propagateSubstitution(_, EmptySubstitution)) + combineCasesNoSubstOnly(scrut, scrutSym, casesNoSubstOnly, pt, owner, matchFailGenOverride) } - def combineCasesNoSubstOnly(scrut: Tree, scrutSym: Symbol, casesNoSubstOnly: List[List[TreeMaker]], pt: Type, owner: Symbol, matchFail: Casegen => Tree => Tree): Tree = fixerUpper(owner, scrut.pos){ - val ptDefined = if (isFullyDefined(pt)) pt else NoType - - emitSwitch(scrut, scrutSym, casesNoSubstOnly, pt).getOrElse{ - if (casesNoSubstOnly nonEmpty) { - // check casesNoSubstOnly for presence of a default case, since DCE will eliminate trivial cases like `case _ =>`, even if they're the last one - // exhaustivity and reachability must be checked before optimization as well - // TODO: improve, a trivial type test before the body still makes for a default case - // ("trivial" depends on whether we're emitting a straight match or an exception, or more generally, any supertype of scrutSym.tpe is a no-op) - val catchAll = - if (casesNoSubstOnly.nonEmpty && { - val nonTrivLast = casesNoSubstOnly.last - nonTrivLast.nonEmpty && nonTrivLast.head.isInstanceOf[BodyTreeMaker] - }) None - else Some(matchFail) - - val (cases, toHoist) = optimizeCases(scrutSym, casesNoSubstOnly, pt) - - val matchRes = codegen.matcher(scrut, scrutSym, pt)(cases map combineExtractors, catchAll) - - if (toHoist isEmpty) matchRes else Block(toHoist, matchRes) - } else { - codegen.matcher(scrut, scrutSym, pt)(Nil, Some(matchFail)) + def combineCasesNoSubstOnly(scrut: Tree, scrutSym: Symbol, casesNoSubstOnly: List[List[TreeMaker]], pt: Type, owner: Symbol, matchFailGenOverride: Option[Tree => Tree]): Tree = + fixerUpper(owner, scrut.pos){ + val ptDefined = if (isFullyDefined(pt)) pt else NoType + def matchFailGen = (matchFailGenOverride orElse Some(CODE.MATCHERROR(_: Tree))) + + emitSwitch(scrut, scrutSym, casesNoSubstOnly, pt, matchFailGenOverride).getOrElse{ + if (casesNoSubstOnly nonEmpty) { + // before optimizing, check casesNoSubstOnly for presence of a default case, + // since DCE will eliminate trivial cases like `case _ =>`, even if they're the last one + // exhaustivity and reachability must be checked before optimization as well + // TODO: improve notion of trivial/irrefutable -- a trivial type test before the body still makes for a default case + // ("trivial" depends on whether we're emitting a straight match or an exception, or more generally, any supertype of scrutSym.tpe is a no-op) + // irrefutability checking should use the approximation framework also used for CSE, unreachability and exhaustivity checking + val synthCatchAll = + if (casesNoSubstOnly.nonEmpty && { + val nonTrivLast = casesNoSubstOnly.last + nonTrivLast.nonEmpty && nonTrivLast.head.isInstanceOf[BodyTreeMaker] + }) None + else matchFailGen + + val (cases, toHoist) = optimizeCases(scrutSym, casesNoSubstOnly, pt) + + val matchRes = codegen.matcher(scrut, scrutSym, pt)(cases map combineExtractors, synthCatchAll) + + if (toHoist isEmpty) matchRes else Block(toHoist, matchRes) + } else { + codegen.matcher(scrut, scrutSym, pt)(Nil, matchFailGen) + } } } - } - - // combineExtractors changes the current substitution's of the tree makers in `treeMakers` - // requires propagateSubstitution(treeMakers) has been called - def combineExtractors(treeMakers: List[TreeMaker])(casegen: Casegen): Tree = - treeMakers.foldRight(EmptyTree: Tree)((a, b) => a.chainBefore(b)(casegen)) // TODO: do this during tree construction, but that will require tracking the current owner in treemakers // TODO: assign more fine-grained positions @@ -1043,7 +1041,7 @@ class Foo(x: Other) { x._1 } // no error in this order // codegen relevant to the structure of the translation (how extractors are combined) trait AbsCodegen { - def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], catchAllGen: Option[Casegen => Tree => Tree]): Tree + def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], matchFailGen: Option[Tree => Tree]): Tree // local / context-free def _asInstanceOf(b: Symbol, tp: Type): Tree @@ -1136,13 +1134,13 @@ class Foo(x: Other) { x._1 } // no error in this order //// methods in MatchingStrategy (the monad companion) -- used directly in translation // __match.runOrElse(`scrut`)(`scrutSym` => `matcher`) // TODO: consider catchAll, or virtualized matching will break in exception handlers - def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], catchAllGen: Option[Casegen => Tree => Tree]): Tree = + def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], matchFailGen: Option[Tree => Tree]): Tree = _match(vpmName.runOrElse) APPLY (scrut) APPLY (fun(scrutSym, cases map (f => f(this)) reduceLeft typedOrElse)) // __match.one(`res`) def one(res: Tree): Tree = (_match(vpmName.one)) (res) // __match.zero - def zero: Tree = _match(vpmName.zero) + protected def zero: Tree = _match(vpmName.zero) // __match.guard(`c`, `then`) def guard(c: Tree, then: Tree): Tree = _match(vpmName.guard) APPLY (c, then) @@ -1517,7 +1515,7 @@ class Foo(x: Other) { x._1 } // no error in this order } } - class RegularSwitchMaker(scrutSym: Symbol) extends SwitchMaker { + class RegularSwitchMaker(scrutSym: Symbol, matchFailGenOverride: Option[Tree => Tree]) extends SwitchMaker { val switchableTpe = Set(ByteClass.tpe, ShortClass.tpe, IntClass.tpe, CharClass.tpe) val alternativesSupported = true @@ -1540,14 +1538,14 @@ class Foo(x: Other) { x._1 } // no error in this order } def defaultSym: Symbol = scrutSym - def defaultBody: Tree = { import CODE._; MATCHERROR(REF(scrutSym)) } + def defaultBody: Tree = { import CODE._; matchFailGenOverride map (gen => gen(REF(scrutSym))) getOrElse MATCHERROR(REF(scrutSym)) } def defaultCase(scrutSym: Symbol = defaultSym, body: Tree = defaultBody): CaseDef = { import CODE._; atPos(body.pos) { DEFAULT ==> body }} } - override def emitSwitch(scrut: Tree, scrutSym: Symbol, cases: List[List[TreeMaker]], pt: Type): Option[Tree] = { import CODE._ - val regularSwitchMaker = new RegularSwitchMaker(scrutSym) + override def emitSwitch(scrut: Tree, scrutSym: Symbol, cases: List[List[TreeMaker]], pt: Type, matchFailGenOverride: Option[Tree => Tree]): Option[Tree] = { import CODE._ + val regularSwitchMaker = new RegularSwitchMaker(scrutSym, matchFailGenOverride) // TODO: if patterns allow switch but the type of the scrutinee doesn't, cast (type-test) the scrutinee to the corresponding switchable type and switch on the result if (regularSwitchMaker.switchableTpe(scrutSym.tpe)) { val caseDefsWithDefault = regularSwitchMaker(cases map {c => (scrutSym, c)}, pt) @@ -1555,7 +1553,7 @@ class Foo(x: Other) { x._1 } // no error in this order else { // match on scrutSym -- converted to an int if necessary -- not on scrut directly (to avoid duplicating scrut) val scrutToInt: Tree = - if(scrutSym.tpe =:= IntClass.tpe) REF(scrutSym) + if (scrutSym.tpe =:= IntClass.tpe) REF(scrutSym) else (REF(scrutSym) DOT (nme.toInt)) Some(BLOCK( VAL(scrutSym) === scrut, @@ -1631,7 +1629,7 @@ class Foo(x: Other) { x._1 } // no error in this order * the matcher's optional result is encoded as a flag, keepGoing, where keepGoing == true encodes result.isEmpty, * if keepGoing is false, the result Some(x) of the naive translation is encoded as matchRes == x */ - def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], catchAllGen: Option[Casegen => Tree => Tree]): Tree = { + def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], matchFailGen: Option[Tree => Tree]): Tree = { val matchEnd = NoSymbol.newLabel(freshName("matchEnd"), NoPosition) setFlag (SYNTHETIC | Flags.CASE) val matchRes = NoSymbol.newValueParameter(newTermName("x"), NoPosition, SYNTHETIC) setInfo restpe matchEnd setInfo MethodType(List(matchRes), restpe) @@ -1645,38 +1643,35 @@ class Foo(x: Other) { x._1 } // no error in this order LabelDef(currCase, Nil, mkCase(casegen)) } - def catchAll = catchAllGen map { catchAllGen => - val casegen = new OptimizedCasegen(matchEnd, NoSymbol) - val scrutRef = if(scrutSym eq NoSymbol) EmptyTree else REF(scrutSym) - LabelDef(nextCase, Nil, catchAllGen(casegen)(scrutRef)) + def catchAll = matchFailGen map { matchFailGen => + val scrutRef = if(scrutSym ne NoSymbol) REF(scrutSym) else EmptyTree // for alternatives + LabelDef(nextCase, Nil, matchEnd APPLY (matchFailGen(scrutRef))) // need to jump to matchEnd with result generated by matchFailGen (could be `FALSE` for isDefinedAt) } toList + // catchAll.isEmpty iff no synthetic default case needed (the (last) user-defined case is a default) + // if the last user-defined case is a default, it will never jump to the next case; it will go immediately to matchEnd // the generated block is taken apart in TailCalls under the following assumptions // the assumption is once we encounter a case, the remainder of the block will consist of cases // the prologue may be empty, usually it is the valdef that stores the scrut // val (prologue, cases) = stats span (s => !s.isInstanceOf[LabelDef]) - val prologue = if(scrutSym ne NoSymbol) List(VAL(scrutSym) === scrut) else Nil + // scrutSym == NoSymbol when generating an alternatives matcher + val scrutDef = if(scrutSym ne NoSymbol) List(VAL(scrutSym) === scrut) else Nil // for alternatives Block( - prologue ++ (cases map caseDef) ++ catchAll, + scrutDef ++ (cases map caseDef) ++ catchAll, LabelDef(matchEnd, List(matchRes), REF(matchRes)) ) } class OptimizedCasegen(matchEnd: Symbol, nextCase: Symbol) extends CommonCodegen with Casegen { - def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], catchAllGen: Option[Casegen => Tree => Tree]): Tree = - optimizedCodegen.matcher(scrut, scrutSym, restpe)(cases, catchAllGen) - - def zero: Tree = nextCase APPLY () + def matcher(scrut: Tree, scrutSym: Symbol, restpe: Type)(cases: List[Casegen => Tree], matchFailGen: Option[Tree => Tree]): Tree = + optimizedCodegen.matcher(scrut, scrutSym, restpe)(cases, matchFailGen) // only used to wrap the RHS of a body // res: T // returns MatchMonad[T] def one(res: Tree): Tree = matchEnd APPLY (res) - - - override def ifThenElseZero(c: Tree, then: Tree): Tree = - IF (c) THEN then ELSE zero + protected def zero: Tree = nextCase APPLY () // prev: MatchMonad[T] // b: T diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 0dd4b37131..506e347828 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2143,6 +2143,137 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { newTyper(context.makeNewScope(cdef, context.owner)).typedCase(cdef, pattp, pt) } + def adaptCase(cdef: CaseDef, mode: Int, tpe: Type): CaseDef = deriveCaseDef(cdef)(adapt(_, mode, tpe)) + + def translateMatch(selector: Tree, cases: List[CaseDef], mode: Int, resTp: Type, scrutTp: Type = NoType, matchFailGen: Option[Tree => Tree] = None) = { + val selector1 = if(scrutTp eq NoType) checkDead(typed(selector, EXPRmode | BYVALmode, WildcardType)) else selector + val selectorTp = if(scrutTp eq NoType) packCaptured(selector1.tpe.widen) else scrutTp + val casesTyped = typedCases(cases, selectorTp, resTp) + val (ownType, needAdapt) = if (isFullyDefined(resTp)) (resTp, false) else weakLub(casesTyped map (_.tpe.deconst)) + val casesAdapted = if (!needAdapt) casesTyped else casesTyped map (adaptCase(_, mode, ownType)) + // val (owntype0, needAdapt) = ptOrLub(casesTyped map (x => repackExistential(x.tpe))) + // val owntype = elimAnonymousClass(owntype0) + + def repeatedToSeq(tp: Type): Type = (tp baseType RepeatedParamClass) match { + case TypeRef(_, RepeatedParamClass, args) => appliedType(SeqClass.typeConstructor, args) + case _ => tp + } + + def isSynthSelector(selector: Tree): Boolean = selector match { + case Ident(_) if selector.symbol.hasFlag(SYNTHETIC | CASE) => true + case Select(sel, nme.toInt) => isSynthSelector(sel) // switch may need to convert to int first + case _ => false + } + + if (isSynthSelector(selector1)) { // a switch + (Match(selector1, casesAdapted) setType ownType, ownType) // setType of the Match to avoid recursing endlessly + } else { + val scrutType = repeatedToSeq(elimAnonymousClass(selectorTp)) + (MatchTranslator(this).translateMatch(selector1, casesAdapted, repeatedToSeq(ownType), scrutType, matchFailGen), ownType) + } + } + + // TODO: use this to synthesize (partial)function implementation for matches from the get-go, + // instead of the dirty post-factum hacks in uncurry -- typedMatchAnonFun is currently not used due to mindboggling failures (see virtpatmat_anonfun_for.scala) + def typedMatchAnonFun(tree: Tree, cases: List[CaseDef], mode: Int, pt0: Type, selOverride: Option[(List[Symbol], Tree)] = None) = { + val pt = deskolemizeGADTSkolems(pt0) + val targs = pt.normalize.typeArgs + val arity = if (isFunctionType(pt)) targs.length - 1 else 1 + val scrutTp0 = if (arity == 1) targs.head else /* arity > 1 */ tupleType(targs.init) + val scrutTp = packCaptured(scrutTp0) + val ptRes = targs.last // may not be fully defined + val isPartial = pt.typeSymbol == PartialFunctionClass + val cname = tpnme.ANON_FUN_NAME + val funThis = This(cname) + // used to create a new context for pattern matching translation so that + // we can easily rejig the owner structure when we have the actual symbols for these methods + // (after type checking them, but type checking requires translation -- this seems like the easiest way to end this vicious cycle) + val applySentinel = NoSymbol.newMethod(nme.apply) + val idaSentinel = NoSymbol.newMethod(nme._isDefinedAt) + + def mkParams = { + val params = + for (i <- List.range(0, arity)) yield atPos(tree.pos.focusStart) { + ValDef(Modifiers(SYNTHETIC | PARAM), unit.freshTermName("x" + i + "$"), TypeTree(), EmptyTree) + } + val ids = params map (p => Ident(p.name)) + + val paramsRef = selOverride match { + case None => atPos(tree.pos.focusStart) { if (arity == 1) ids.head else gen.mkTuple(ids) } + case Some((_, sel)) => sel.duplicate // we'll replace the symbols that refer to the function's original syms by the ones introduced by the DefDef once the method's been type checked (until then, we don't know them) + } + + (params, paramsRef) // paramsRef can't be typed until after match has been translated, thus supply explicit scrutTp to translate below + } + + import CODE._ + + // need to duplicate the cases before typing them to generate the apply method, or the symbols will be all messed up + val casesTrue = if (isPartial) cases map (c => deriveCaseDef(c)(x => TRUE).duplicate) else Nil + + val (applyMethod, parents) = { + val (params, paramsRef) = mkParams + val (body, resTp) = newTyper(context.make(context.tree, applySentinel)).translateMatch(paramsRef, cases, mode, ptRes, scrutTp, if (isPartial) Some(scrut => (funThis DOT nme.missingCase) (scrut)) else None) + + def abstractFunctionType = { + val sym = AbstractFunctionClass(arity) + typeRef(sym.typeConstructor.prefix, sym, targs.init :+ resTp) + } + + val parents = + if (isFunctionType(pt)) List(abstractFunctionType, SerializableClass.tpe) + else if (isPartial) List(appliedType(AbstractPartialFunctionClass.typeConstructor, List(scrutTp, resTp)), SerializableClass.tpe) + else List(ObjectClass.tpe, pt, SerializableClass.tpe) + + (atPos(tree.pos.focus)(DefDef(Modifiers(FINAL), nme.apply, Nil, List(params), TypeTree() setType resTp, body)), parents) + } + + def isDefinedAtMethod = { + val (params, paramsRef) = mkParams + val (body, _) = newTyper(context.make(context.tree, idaSentinel)).translateMatch(paramsRef, casesTrue, mode, BooleanClass.tpe, scrutTp, Some(scrutinee => FALSE)) + atPos(tree.pos.focus)( + DefDef(Modifiers(FINAL), nme._isDefinedAt, Nil, List(params), TypeTree() setType BooleanClass.tpe, body) + ) + } + + val members = if (!isPartial) List(applyMethod) else List(applyMethod, isDefinedAtMethod) + + val cmods = Modifiers(FINAL | SYNTHETIC /*TODO: when do we need INCONSTRUCTOR ?*/) withAnnotations ( + List(NEW(SerialVersionUIDAttr, LIT(0)))) + val cdef = + ClassDef(cmods, cname, Nil, + Template(parents map (TypeTree() setType _), emptyValDef, Modifiers(0), Nil, List(Nil), members, tree.pos) + ) + val funInst = (Block(List(cdef), Apply(Select(New(Ident(cname)), nme.CONSTRUCTOR), Nil))) + + val res = typed(funInst, mode, pt) + + // now that we have the symbols corresponding to the apply/isDefinedAt methods, + // we can fix up the result of fixerUpper... URGH + // fixerUpper nests the top-level definitions generated in the match under context.owner, but they should be owner by the apply/isDefinedAt method + res foreach { + case d: DefDef if (d.symbol.name == nme.apply) => + d.rhs.changeOwner(applySentinel -> d.symbol) + case d: DefDef if (d.symbol.name == nme._isDefinedAt) => + d.rhs.changeOwner(idaSentinel -> d.symbol) + case _ => + } + + selOverride match { + case None => res + case Some((paramSyms, sel)) => + object substParamSyms extends Transformer { + override def transform(t: Tree): Tree = t match { + case d: DefDef if (d.symbol.name == nme.apply) || (d.symbol.name == nme._isDefinedAt) && (d.symbol.owner == res.tpe.typeSymbol) => + deriveDefDef(d)(rhs => rhs.substTreeSyms(paramSyms, d.vparamss.head.map(_.symbol))) + case _ => + super.transform(t) + } + } + substParamSyms.transform(res) + } + } + /** * @param fun ... * @param mode ... @@ -2155,14 +2286,10 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { return MaxFunctionArityError(fun) def decompose(pt: Type): (Symbol, List[Type], Type) = - if ((isFunctionType(pt) - || - pt.typeSymbol == PartialFunctionClass && - numVparams == 1 && fun.body.isInstanceOf[Match]) - && // see bug901 for a reason why next conditions are needed - (pt.normalize.typeArgs.length - 1 == numVparams - || - fun.vparams.exists(_.tpt.isEmpty))) + if ((isFunctionType(pt) || (pt.typeSymbol == PartialFunctionClass && numVparams == 1 && fun.body.isInstanceOf[Match])) && // see bug901 for a reason why next conditions are needed + ( pt.normalize.typeArgs.length - 1 == numVparams + || fun.vparams.exists(_.tpt.isEmpty) + )) (pt.typeSymbol, pt.normalize.typeArgs.init, pt.normalize.typeArgs.last) else (FunctionClass(numVparams), fun.vparams map (x => NoType), WildcardType) @@ -2204,13 +2331,27 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { // for (vparam <- vparams) { // checkNoEscaping.locals(context.scope, WildcardType, vparam.tpt); () // } - val body1 = typed(fun.body, respt) - val formals = vparamSyms map (_.tpe) - val restpe = packedType(body1, fun.symbol).deconst.resultType - val funtpe = typeRef(clazz.tpe.prefix, clazz, formals :+ restpe) -// body = checkNoEscaping.locals(context.scope, restpe, body) - treeCopy.Function(fun, vparams, body1).setType(funtpe) - } + + def recompose(from: Type, to: Type) = + if(clazz == PartialFunctionClass) appliedType(PartialFunctionClass.typeConstructor, List(from, to)) + else functionType(List(from), to) + + fun.body match { + case Match(sel, cases) if opt.virtPatmat => + val typedSel = typed(sel, EXPRmode | BYVALmode, WildcardType) + // go to outer context -- must discard the context that was created for the Function since we're discarding the function + // thus, its symbol, which serves as the current context.owner, is not the right owner + // you won't know you're using the wrong owner until lambda lift crashes (unless you know better than to use the wrong owner) + newTyper(context.outer).typedMatchAnonFun(fun, cases, mode, recompose(typedSel.tpe, respt), Some((vparamSyms, typedSel))) + case _ => + val body1 = typed(fun.body, respt) + val formals = vparamSyms map (_.tpe) + val restpe = packedType(body1, fun.symbol).deconst.resultType + val funtpe = typeRef(clazz.tpe.prefix, clazz, formals :+ restpe) + // body = checkNoEscaping.locals(context.scope, restpe, body) + treeCopy.Function(fun, vparams, body1).setType(funtpe) + } + } } def typedRefinement(stats: List[Tree]) { @@ -3431,7 +3572,10 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { } def typedMatch(tree: Tree, selector: Tree, cases: List[CaseDef]): Tree = { - if (selector == EmptyTree) { + if (opt.virtPatmat && !isPastTyper) { + if (selector ne EmptyTree) typed(translateMatch(selector, cases, mode, pt)._1, mode, pt) + else typedMatchAnonFun(tree, cases, mode, pt) + } else if (selector == EmptyTree) { val arity = if (isFunctionType(pt)) pt.normalize.typeArgs.length - 1 else 1 val params = for (i <- List.range(0, arity)) yield atPos(tree.pos.focusStart) { @@ -3445,32 +3589,11 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { } else { val selector1 = checkDead(typed(selector, EXPRmode | BYVALmode, WildcardType)) var cases1 = typedCases(cases, packCaptured(selector1.tpe.widen), pt) - - if (isPastTyper || !opt.virtPatmat) { - val (owntype, needAdapt) = ptOrLub(cases1 map (_.tpe)) - if (needAdapt) { - cases1 = cases1 map (adaptCase(_, owntype)) - } - treeCopy.Match(tree, selector1, cases1) setType owntype - } else { // don't run translator after typers (see comments in PatMatVirtualiser) - val (owntype0, needAdapt) = ptOrLub(cases1 map (x => repackExistential(x.tpe))) - val owntype = elimAnonymousClass(owntype0) - if (needAdapt) cases1 = cases1 map (adaptCase(_, owntype)) - - (MatchTranslator(this)).translateMatch(selector1, cases1, owntype) match { - case Block(vd :: Nil, tree@Match(selector, cases)) => - val selector1 = checkDead(typed(selector, EXPRmode | BYVALmode, WildcardType)) - var cases1 = typedCases(cases, packCaptured(selector1.tpe.widen), pt) - val (owntype, needAdapt) = ptOrLub(cases1 map (_.tpe)) - if (needAdapt) - cases1 = cases1 map (adaptCase(_, owntype)) - typed(Block(vd :: Nil, treeCopy.Match(tree, selector1, cases1) setType owntype)) - case translated => - // TODO: get rid of setType owntype -- it should all typecheck - // must call typed, not typed1, or we overflow the stack when emitting switches - typed(translated, mode, WildcardType) setType owntype - } + val (owntype, needAdapt) = ptOrLub(cases1 map (_.tpe)) + if (needAdapt) { + cases1 = cases1 map (adaptCase(_, mode, owntype)) } + treeCopy.Match(tree, selector1, cases1) setType owntype } } @@ -4240,9 +4363,6 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { } } - def adaptCase(cdef: CaseDef, tpe: Type): CaseDef = - deriveCaseDef(cdef)(adapt(_, mode, tpe)) - // begin typed1 val sym: Symbol = tree.symbol if ((sym ne null) && (sym ne NoSymbol)) sym.initialize @@ -4351,7 +4471,7 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { val (owntype, needAdapt) = ptOrLub(block1.tpe :: (catches1 map (_.tpe))) if (needAdapt) { block1 = adapt(block1, mode, owntype) - catches1 = catches1 map (adaptCase(_, owntype)) + catches1 = catches1 map (adaptCase(_, mode, owntype)) } if(!isPastTyper && opt.virtPatmat) { diff --git a/test/files/pos/virtpatmat_anonfun_for.flags b/test/files/pos/virtpatmat_anonfun_for.flags new file mode 100644 index 0000000000..23e3dc7d26 --- /dev/null +++ b/test/files/pos/virtpatmat_anonfun_for.flags @@ -0,0 +1 @@ +-Yvirtpatmat \ No newline at end of file diff --git a/test/files/pos/virtpatmat_anonfun_for.scala b/test/files/pos/virtpatmat_anonfun_for.scala new file mode 100644 index 0000000000..8623cd97ba --- /dev/null +++ b/test/files/pos/virtpatmat_anonfun_for.scala @@ -0,0 +1,8 @@ +trait Foo { + def bla = { + val tvs = "tvs" + Nil.foreach(x => x match { + case _ => println(tvs) + }) + } +} \ No newline at end of file diff --git a/test/files/run/virtpatmat_partial.check b/test/files/run/virtpatmat_partial.check index 1555eca82b..137d16da79 100644 --- a/test/files/run/virtpatmat_partial.check +++ b/test/files/run/virtpatmat_partial.check @@ -1,4 +1,17 @@ Map(a -> Some(1), b -> None) -79 -undefined Map(a -> 1) +a +undefined +a +undefined +a +undefined +a +undefined +hai! +hai! +2 +hai! +undefined +1 +undefined diff --git a/test/files/run/virtpatmat_partial.scala b/test/files/run/virtpatmat_partial.scala index 6597f2f5ae..a235314610 100644 --- a/test/files/run/virtpatmat_partial.scala +++ b/test/files/run/virtpatmat_partial.scala @@ -2,95 +2,180 @@ object Test extends App { val a = Map("a" -> Some(1), "b" -> None) println(a) +// inferred type should be Map[String, Int] val res = a collect {case (p, Some(a)) => (p, a)} - final val GT = 79 - final val GTGT = 93 - final val GTGTGT = 94 - final val GTEQ = 81 - final val GTGTEQ = 113 - final val GTGTGTEQ = 114 - final val ASSIGN = 75 - - def acceptClosingAngle(in: Int) { - val closers: PartialFunction[Int, Int] = { - case GTGTGTEQ => GTGTEQ - case GTGTGT => GTGT - case GTGTEQ => GTEQ - case GTGT => GT - case GTEQ => ASSIGN +// variations: const target -> switch, non-const -> normal match, char target --> scrut needs toInt, +// eta-expanded --> work is done by typedFunction, non-eta-expanded --> typedMatch + + object nonConstCharEta { + final val GT : Char = 'a' + final val GTGT : Char = 'b' + final val GTGTGT : Char = 'c' + final val GTEQ : Char = 'd' + final val GTGTEQ : Char = 'e' + final val GTGTGTEQ: Char = 'f' + final val ASSIGN : Char = 'g' + + def acceptClosingAngle(in: Char) { + val closers: PartialFunction[Char, Char] = { + case GTGTGTEQ => GTGTEQ + case GTGTGT => GTGT + case GTGTEQ => GTEQ + case GTGT => GT + case GTEQ => ASSIGN + } + if (closers isDefinedAt in) println(closers(in)) + else println("undefined") + } + + def test() = { + acceptClosingAngle(GTGT) + acceptClosingAngle(ASSIGN) + } + } + + object nonConstChar { + final val GT : Char = 'a' + final val GTGT : Char = 'b' + final val GTGTGT : Char = 'c' + final val GTEQ : Char = 'd' + final val GTGTEQ : Char = 'e' + final val GTGTGTEQ: Char = 'f' + final val ASSIGN : Char = 'g' + + def acceptClosingAngle(in: Char) { + val closers: PartialFunction[Char, Char] = x => x match { + case GTGTGTEQ => GTGTEQ + case GTGTGT => GTGT + case GTGTEQ => GTEQ + case GTGT => GT + case GTEQ => ASSIGN + } + if (closers isDefinedAt in) println(closers(in)) + else println("undefined") + } + + def test() = { + acceptClosingAngle(GTGT) + acceptClosingAngle(ASSIGN) } - if (closers isDefinedAt in) println(closers(in)) - else println("undefined") } - acceptClosingAngle(GTGT) - acceptClosingAngle(ASSIGN) - - // should uncurry to: - // val res: Map[String,Int] = a.collect[(String, Int), Map[String,Int]]( - // new PartialFunction[(String, Option[Int]),(String, Int)] { - // def apply(x0_1: (String, Option[Int])): (String, Int) = MatchingStrategy.OptionMatchingStrategy.runOrElse[(String, Option[Int]), (String, Int)](x0_1)( - // (x1: (String, Option[Int])) => { - // val o9: Option[(String, Int)] = ({ - // val o8: Option[(String, Option[Int])] = Tuple2.unapply[String, Option[Int]](x1); - // if (o8.isEmpty) - // MatchingStrategy.OptionMatchingStrategy.zero - // else - // { - // val o7: Option[Some[Int]] = if (o8.get._2.isInstanceOf[Some[Int]]) - // MatchingStrategy.OptionMatchingStrategy.one[Some[Int]](o8.get._2.asInstanceOf[Some[Int]]) - // else - // MatchingStrategy.OptionMatchingStrategy.zero; - // if (o7.isEmpty) - // MatchingStrategy.OptionMatchingStrategy.zero - // else - // { - // val o6: Option[Int] = Some.unapply[Int](o7.get); - // if (o6.isEmpty) - // MatchingStrategy.OptionMatchingStrategy.zero - // else - // MatchingStrategy.OptionMatchingStrategy.one[(String, Int)]((o8.get._1, o6.get).asInstanceOf[(String, Int)]) - // } - // } - // }: Option[(String, Int)]); - // if (o9.isEmpty) - // (MatchingStrategy.OptionMatchingStrategy.zero: Option[(String, Int)]) - // else - // o9 - // }) - // - // def isDefinedAt(x_1: (String, Option[Int])): Boolean = MatchingStrategy.OptionMatchingStrategy.isSuccess[(String, Option[Int]), (String, Int)](x_1)( - // (x1: (String, Option[Int])) => { - // val o9: Option[(String, Int)] = ({ - // val o8: Option[(String, Option[Int])] = scala.Tuple2.unapply[String, Option[Int]](x1); - // if (o8.isEmpty) - // MatchingStrategy.OptionMatchingStrategy.zero - // else - // { - // val o7: Option[Some[Int]] = if (o8.get._2.isInstanceOf[Some[Int]]) - // MatchingStrategy.OptionMatchingStrategy.one[Some[Int]](o8.get._2.asInstanceOf[Some[Int]]) // XXX - // else - // MatchingStrategy.OptionMatchingStrategy.zero; - // if (o7.isEmpty) - // MatchingStrategy.OptionMatchingStrategy.zero - // else - // { - // val o6: Option[Int] = scala.Some.unapply[Int](o7.get); - // if (o6.isEmpty) - // MatchingStrategy.OptionMatchingStrategy.zero - // else - // MatchingStrategy.OptionMatchingStrategy.one[(String, Int)](null.asInstanceOf[(String, Int)]) - // } - // } - // }: Option[(String, Int)]); - // if (o9.isEmpty) - // (MatchingStrategy.OptionMatchingStrategy.zero: Option[(String, Int)]) - // else - // o9 - // }) - // } - // ) - - println(res) + object constCharEta { + final val GT = 'a' + final val GTGT = 'b' + final val GTGTGT = 'c' + final val GTEQ = 'd' + final val GTGTEQ = 'e' + final val GTGTGTEQ= 'f' + final val ASSIGN = 'g' + + def acceptClosingAngle(in: Char) { + val closers: PartialFunction[Char, Char] = x => x match { + case GTGTGTEQ => GTGTEQ + case GTGTGT => GTGT + case GTGTEQ => GTEQ + case GTGT => GT + case GTEQ => ASSIGN + } + if (closers isDefinedAt in) println(closers(in)) + else println("undefined") + } + + def test() = { + acceptClosingAngle(GTGT) + acceptClosingAngle(ASSIGN) + } + } + + object constChar { + final val GT = 'a' + final val GTGT = 'b' + final val GTGTGT = 'c' + final val GTEQ = 'd' + final val GTGTEQ = 'e' + final val GTGTGTEQ= 'f' + final val ASSIGN = 'g' + + def acceptClosingAngle(in: Char) { + val closers: PartialFunction[Char, Char] = { + case GTGTGTEQ => GTGTEQ + case GTGTGT => GTGT + case GTGTEQ => GTEQ + case GTGT => GT + case GTEQ => ASSIGN + } + if (closers isDefinedAt in) println(closers(in)) + else println("undefined") + } + + def test() = { + acceptClosingAngle(GTGT) + acceptClosingAngle(ASSIGN) + } + } + + object constIntEta { + final val GT = 1 + final val GTGT = 2 + final val GTGTGT = 3 + final val GTEQ = 4 + final val GTGTEQ = 5 + final val GTGTGTEQ = 6 + final val ASSIGN = 7 + + def acceptClosingAngle(in: Int) { + val closers: PartialFunction[Int, Int] = x => {println("hai!"); (x + 1)} match { + case GTGTGTEQ => GTGTEQ + case GTGTGT => GTGT + case GTGTEQ => GTEQ + case GTGT => GT + case GTEQ => ASSIGN + } + if (closers isDefinedAt in) println(closers(in)) + else println("undefined") + } + + def test() = { + acceptClosingAngle(GTGT) + acceptClosingAngle(ASSIGN) + } + } + + object constInt { + final val GT = 1 + final val GTGT = 2 + final val GTGTGT = 3 + final val GTEQ = 4 + final val GTGTEQ = 5 + final val GTGTGTEQ = 6 + final val ASSIGN = 7 + + def acceptClosingAngle(in: Int) { + val closers: PartialFunction[Int, Int] = { + case GTGTGTEQ => GTGTEQ + case GTGTGT => GTGT + case GTGTEQ => GTEQ + case GTGT => GT + case GTEQ => ASSIGN + } + if (closers isDefinedAt in) println(closers(in)) + else println("undefined") + } + + def test() = { + acceptClosingAngle(GTGT) + acceptClosingAngle(ASSIGN) + } + } + + println(res) // prints "Map(a -> 1)" + + nonConstCharEta.test() + nonConstChar.test() + constCharEta.test() + constChar.test() + constIntEta.test() + constInt.test() } diff --git a/test/pending/run/virtpatmat_anonfun_underscore.check b/test/pending/run/virtpatmat_anonfun_underscore.check new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/pending/run/virtpatmat_anonfun_underscore.flags b/test/pending/run/virtpatmat_anonfun_underscore.flags new file mode 100644 index 0000000000..23e3dc7d26 --- /dev/null +++ b/test/pending/run/virtpatmat_anonfun_underscore.flags @@ -0,0 +1 @@ +-Yvirtpatmat \ No newline at end of file diff --git a/test/pending/run/virtpatmat_anonfun_underscore.scala b/test/pending/run/virtpatmat_anonfun_underscore.scala new file mode 100644 index 0000000000..db6705d025 --- /dev/null +++ b/test/pending/run/virtpatmat_anonfun_underscore.scala @@ -0,0 +1,4 @@ +object Test extends App { + List(1,2,3) map (_ match { case x => x + 1} ) // `_ match` is redundant but shouldn't crash the compiler + List((1,2)) map (_ match { case (x, z) => x + z}) +} \ No newline at end of file -- cgit v1.2.3 From 9e513a6d29f2cb060caf58ff5568d7955b96305a Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Sat, 17 Mar 2012 12:15:37 +0100 Subject: [vpm] fix half of my existential troubles no need for the clunky repackExistential pack the type of each case instead, since the skolems we've created shouldn't last beyond the case anyway this way we don't end up with fresh, incompatible, skolems for every case, but a neatly packed existential --- src/compiler/scala/reflect/internal/Types.scala | 9 ------ .../tools/nsc/typechecker/PatMatVirtualiser.scala | 10 +++---- .../scala/tools/nsc/typechecker/Typers.scala | 6 +++- test/files/pos/virtpatmat_exist4.scala | 35 ++++++++++++++++++++++ 4 files changed, 45 insertions(+), 15 deletions(-) create mode 100644 test/files/pos/virtpatmat_exist4.scala (limited to 'src') diff --git a/src/compiler/scala/reflect/internal/Types.scala b/src/compiler/scala/reflect/internal/Types.scala index 549c9e4607..2bb19e2b65 100644 --- a/src/compiler/scala/reflect/internal/Types.scala +++ b/src/compiler/scala/reflect/internal/Types.scala @@ -2617,15 +2617,6 @@ trait Types extends api.Types { self: SymbolTable => } } - // TODO: I don't really know why this happens -- maybe because - // the owner hierarchy changes? the other workaround (besides - // repackExistential) is to explicitly pass expectedTp as the type - // argument for the call to guard, but repacking the existential - // somehow feels more robust - // - // TODO: check if optimization makes a difference, try something else - // if necessary (cache?) - /** Repack existential types, otherwise they sometimes get unpacked in the * wrong location (type inference comes up with an unexpected skolem) */ diff --git a/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala b/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala index 34fefd20fe..de7f03dc62 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala @@ -1037,7 +1037,7 @@ class Foo(x: Other) { x._1 } // no error in this order // assert(owner ne null); assert(owner ne NoSymbol) def freshSym(pos: Position, tp: Type = NoType, prefix: String = "x") = - NoSymbol.newTermSymbol(freshName(prefix), pos) setInfo repackExistential(tp) + NoSymbol.newTermSymbol(freshName(prefix), pos) setInfo /*repackExistential*/(tp) // codegen relevant to the structure of the translation (how extractors are combined) trait AbsCodegen { @@ -1079,18 +1079,18 @@ class Foo(x: Other) { x._1 } // no error in this order def and(a: Tree, b: Tree): Tree = a AND b // the force is needed mainly to deal with the GADT typing hack (we can't detect it otherwise as tp nor pt need contain an abstract type, we're just casting wildly) - def _asInstanceOf(t: Tree, tp: Type, force: Boolean = false): Tree = { val tpX = repackExistential(tp) + def _asInstanceOf(t: Tree, tp: Type, force: Boolean = false): Tree = { val tpX = /*repackExistential*/(tp) if (!force && (t.tpe ne NoType) && t.isTyped && typesConform(t.tpe, tpX)) t //{ println("warning: emitted redundant asInstanceOf: "+(t, t.tpe, tp)); t } //.setType(tpX) else gen.mkAsInstanceOf(t, tpX, true, false) } - def _isInstanceOf(b: Symbol, tp: Type): Tree = gen.mkIsInstanceOf(REF(b), repackExistential(tp), true, false) - // { val tpX = repackExistential(tp) + def _isInstanceOf(b: Symbol, tp: Type): Tree = gen.mkIsInstanceOf(REF(b), /*repackExistential*/(tp), true, false) + // { val tpX = /*repackExistential*/(tp) // if (typesConform(b.info, tpX)) { println("warning: emitted spurious isInstanceOf: "+(b, tp)); TRUE } // else gen.mkIsInstanceOf(REF(b), tpX, true, false) // } - def _asInstanceOf(b: Symbol, tp: Type): Tree = { val tpX = repackExistential(tp) + def _asInstanceOf(b: Symbol, tp: Type): Tree = { val tpX = /*repackExistential*/(tp) if (typesConform(b.info, tpX)) REF(b) //{ println("warning: emitted redundant asInstanceOf: "+(b, b.info, tp)); REF(b) } //.setType(tpX) else gen.mkAsInstanceOf(REF(b), tpX, true, false) } diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 506e347828..daf4ddd100 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -2140,7 +2140,11 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { def typedCases(cases: List[CaseDef], pattp: Type, pt: Type): List[CaseDef] = cases mapConserve { cdef => - newTyper(context.makeNewScope(cdef, context.owner)).typedCase(cdef, pattp, pt) + val caseTyped = newTyper(context.makeNewScope(cdef, context.owner)).typedCase(cdef, pattp, pt) + if (opt.virtPatmat) { + val tpPacked = packedType(caseTyped, context.owner) + caseTyped setType tpPacked + } else caseTyped } def adaptCase(cdef: CaseDef, mode: Int, tpe: Type): CaseDef = deriveCaseDef(cdef)(adapt(_, mode, tpe)) diff --git a/test/files/pos/virtpatmat_exist4.scala b/test/files/pos/virtpatmat_exist4.scala new file mode 100644 index 0000000000..a04d0e3229 --- /dev/null +++ b/test/files/pos/virtpatmat_exist4.scala @@ -0,0 +1,35 @@ +trait Global { + trait Tree + trait Symbol { def foo: Boolean } +} + +trait IMain { self: MemberHandlers => + val global: Global + def handlers: List[MemberHandler] +} + +trait MemberHandlers { + val intp: IMain + import intp.global._ + sealed abstract class MemberHandler(val member: Tree) { + def importedSymbols: List[Symbol] + } +} + +object Test { + var intp: IMain with MemberHandlers = null + + val handlers = intp.handlers + handlers.filterNot(_.importedSymbols.isEmpty).zipWithIndex foreach { + case (handler, idx) => + val (types, terms) = handler.importedSymbols partition (_.foo) + } +} + +object Test2 { + type JClass = java.lang.Class[_] + + def tvarString(bounds: List[AnyRef]) = { + bounds collect { case x: JClass => x } + } +} \ No newline at end of file -- cgit v1.2.3 From 8552740beda9bb07de1da329e9dc8c002090fe16 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Sat, 17 Mar 2012 12:18:43 +0100 Subject: [vpm] pack types of if branches before lubbing this way we don't end up with fresh, incompatible, skolems for every branch, but a neatly packed existential you get much nicer lubs this way -- threw in a fast path (I hope) that avoids lubbing when unnecessary (same types) since lub isn't robust enough to deal with the result of packedType, and we only really need the trivial case (S =:= T), special case it for now ... don't pack in ifs after typer, also only when virtPatmat --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index daf4ddd100..ad48712a32 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -3566,7 +3566,15 @@ trait Typers extends Modes with Adaptations with PatMatVirtualiser { } else { var thenp1 = typed(thenp, pt) var elsep1 = typed(elsep, pt) - val (owntype, needAdapt) = ptOrLub(List(thenp1.tpe, elsep1.tpe)) + + lazy val thenTp = packedType(thenp1, context.owner) + lazy val elseTp = packedType(elsep1, context.owner) + val (owntype, needAdapt) = + // virtpatmat needs more aggressive unification of skolemized types, but lub is not robust enough --> middle ground + if (opt.virtPatmat && !isPastTyper && thenTp =:= elseTp) (thenTp, true) // this breaks src/library/scala/collection/immutable/TrieIterator.scala + // TODO: skolemize (lub of packed types) when that no longer crashes on files/pos/t4070b.scala + else ptOrLub(List(thenp1.tpe, elsep1.tpe)) + if (needAdapt) { //isNumericValueType(owntype)) { thenp1 = adapt(thenp1, mode, owntype) elsep1 = adapt(elsep1, mode, owntype) -- cgit v1.2.3 From 66f0679169bd8d5dc749c2288777c5a217ae3d43 Mon Sep 17 00:00:00 2001 From: Vojin Jovanovic Date: Mon, 19 Mar 2012 22:25:26 +0100 Subject: Prepared actors hierarchy for migration. Internal nodes added so methods relevant to akka can be overridden. Review by: @phaller --- src/actors/scala/actors/Actor.scala | 484 +------------------- src/actors/scala/actors/ActorCanReply.scala | 2 +- src/actors/scala/actors/ActorTask.scala | 9 +- src/actors/scala/actors/Channel.scala | 2 +- src/actors/scala/actors/Combinators.scala | 2 +- src/actors/scala/actors/InternalActor.scala | 509 +++++++++++++++++++++ src/actors/scala/actors/InternalReplyReactor.scala | 161 +++++++ src/actors/scala/actors/OutputChannel.scala | 2 +- src/actors/scala/actors/ReactChannel.scala | 2 +- src/actors/scala/actors/Reactor.scala | 2 +- src/actors/scala/actors/ReactorCanReply.scala | 2 +- src/actors/scala/actors/ReplyReactor.scala | 165 +------ src/actors/scala/actors/ReplyReactorTask.scala | 4 +- src/actors/scala/actors/UncaughtException.scala | 2 +- 14 files changed, 702 insertions(+), 646 deletions(-) create mode 100644 src/actors/scala/actors/InternalActor.scala create mode 100644 src/actors/scala/actors/InternalReplyReactor.scala (limited to 'src') diff --git a/src/actors/scala/actors/Actor.scala b/src/actors/scala/actors/Actor.scala index b746f68268..aab533ae8d 100644 --- a/src/actors/scala/actors/Actor.scala +++ b/src/actors/scala/actors/Actor.scala @@ -46,7 +46,7 @@ object Actor extends Combinators { Terminated = Value } - private[actors] val tl = new ThreadLocal[ReplyReactor] + private[actors] val tl = new ThreadLocal[InternalReplyReactor] // timer thread runs as daemon private[actors] val timer = new Timer(true) @@ -59,15 +59,15 @@ object Actor extends Combinators { * * @return returns the currently executing actor. */ - def self: Actor = self(Scheduler) + def self: Actor = self(Scheduler).asInstanceOf[Actor] - private[actors] def self(sched: IScheduler): Actor = - rawSelf(sched).asInstanceOf[Actor] + private[actors] def self(sched: IScheduler): InternalActor = + rawSelf(sched).asInstanceOf[InternalActor] - private[actors] def rawSelf: ReplyReactor = + private[actors] def rawSelf: InternalReplyReactor = rawSelf(Scheduler) - private[actors] def rawSelf(sched: IScheduler): ReplyReactor = { + private[actors] def rawSelf(sched: IScheduler): InternalReplyReactor = { val s = tl.get if (s eq null) { val r = new ActorProxy(Thread.currentThread, sched) @@ -245,7 +245,7 @@ object Actor extends Combinators { def eventloop(f: PartialFunction[Any, Unit]): Nothing = rawSelf.react(new RecursiveProxyHandler(rawSelf, f)) - private class RecursiveProxyHandler(a: ReplyReactor, f: PartialFunction[Any, Unit]) + private class RecursiveProxyHandler(a: InternalReplyReactor, f: PartialFunction[Any, Unit]) extends scala.runtime.AbstractPartialFunction[Any, Unit] { def _isDefinedAt(m: Any): Boolean = true // events are immediately removed from the mailbox @@ -259,7 +259,7 @@ object Actor extends Combinators { * Returns the actor which sent the last received message. */ def sender: OutputChannel[Any] = - rawSelf.sender + rawSelf.internalSender /** * Sends `msg` to the actor waiting in a call to `!?`. @@ -302,7 +302,7 @@ object Actor extends Combinators { def andThen[b](other: => b): Unit } - implicit def mkBody[a](body: => a) = new Body[a] { + implicit def mkBody[a](body: => a) = new InternalActor.Body[a] { def andThen[b](other: => b): Unit = rawSelf.seq(body, other) } @@ -397,476 +397,12 @@ object Actor extends Combinators { * @define channel actor's mailbox */ @SerialVersionUID(-781154067877019505L) -trait Actor extends AbstractActor with ReplyReactor with ActorCanReply with InputChannel[Any] with Serializable { - - /* The following two fields are only used when the actor - * suspends by blocking its underlying thread, for example, - * when waiting in a receive or synchronous send. - */ - @volatile - private var isSuspended = false - - /* This field is used to communicate the received message from - * the invocation of send to the place where the thread of - * the receiving actor resumes inside receive/receiveWithin. - */ - @volatile - private var received: Option[Any] = None - - protected[actors] override def scheduler: IScheduler = Scheduler - - private[actors] override def startSearch(msg: Any, replyTo: OutputChannel[Any], handler: PartialFunction[Any, Any]) = - if (isSuspended) { - () => synchronized { - mailbox.append(msg, replyTo) - resumeActor() - } - } else super.startSearch(msg, replyTo, handler) - - // we override this method to check `shouldExit` before suspending - private[actors] override def searchMailbox(startMbox: MQueue[Any], - handler: PartialFunction[Any, Any], - resumeOnSameThread: Boolean) { - var tmpMbox = startMbox - var done = false - while (!done) { - val qel = tmpMbox.extractFirst((msg: Any, replyTo: OutputChannel[Any]) => { - senders = List(replyTo) - handler.isDefinedAt(msg) - }) - if (tmpMbox ne mailbox) - tmpMbox.foreach((m, s) => mailbox.append(m, s)) - if (null eq qel) { - synchronized { - // in mean time new stuff might have arrived - if (!sendBuffer.isEmpty) { - tmpMbox = new MQueue[Any]("Temp") - drainSendBuffer(tmpMbox) - // keep going - } else { - // very important to check for `shouldExit` at this point - // since linked actors might have set it after we checked - // last time (e.g., at the beginning of `react`) - if (shouldExit) exit() - waitingFor = handler - // see Reactor.searchMailbox - throw Actor.suspendException - } - } - } else { - resumeReceiver((qel.msg, qel.session), handler, resumeOnSameThread) - done = true - } - } - } - - private[actors] override def makeReaction(fun: () => Unit, handler: PartialFunction[Any, Any], msg: Any): Runnable = - new ActorTask(this, fun, handler, msg) - - /** See the companion object's `receive` method. */ - def receive[R](f: PartialFunction[Any, R]): R = { - assert(Actor.self(scheduler) == this, "receive from channel belonging to other actor") - - synchronized { - if (shouldExit) exit() // links - drainSendBuffer(mailbox) - } - - var done = false - while (!done) { - val qel = mailbox.extractFirst((m: Any, replyTo: OutputChannel[Any]) => { - senders = replyTo :: senders - val matches = f.isDefinedAt(m) - senders = senders.tail - matches - }) - if (null eq qel) { - synchronized { - // in mean time new stuff might have arrived - if (!sendBuffer.isEmpty) { - drainSendBuffer(mailbox) - // keep going - } else { - waitingFor = f - isSuspended = true - scheduler.managedBlock(blocker) - drainSendBuffer(mailbox) - // keep going - } - } - } else { - received = Some(qel.msg) - senders = qel.session :: senders - done = true - } - } - - val result = f(received.get) - received = None - senders = senders.tail - result - } - - /** See the companion object's `receiveWithin` method. */ - def receiveWithin[R](msec: Long)(f: PartialFunction[Any, R]): R = { - assert(Actor.self(scheduler) == this, "receive from channel belonging to other actor") - - synchronized { - if (shouldExit) exit() // links - drainSendBuffer(mailbox) - } - - // first, remove spurious TIMEOUT message from mailbox if any - mailbox.extractFirst((m: Any, replyTo: OutputChannel[Any]) => m == TIMEOUT) - - val receiveTimeout = () => { - if (f.isDefinedAt(TIMEOUT)) { - received = Some(TIMEOUT) - senders = this :: senders - } else - sys.error("unhandled timeout") - } - - var done = false - while (!done) { - val qel = mailbox.extractFirst((m: Any, replyTo: OutputChannel[Any]) => { - senders = replyTo :: senders - val matches = f.isDefinedAt(m) - senders = senders.tail - matches - }) - if (null eq qel) { - val todo = synchronized { - // in mean time new stuff might have arrived - if (!sendBuffer.isEmpty) { - drainSendBuffer(mailbox) - // keep going - () => {} - } else if (msec == 0L) { - done = true - receiveTimeout - } else { - if (onTimeout.isEmpty) { - if (!f.isDefinedAt(TIMEOUT)) - sys.error("unhandled timeout") - - val thisActor = this - onTimeout = Some(new TimerTask { - def run() { - thisActor.send(TIMEOUT, thisActor) - } - }) - Actor.timer.schedule(onTimeout.get, msec) - } - - // It is possible that !onTimeout.isEmpty, but TIMEOUT is not yet in mailbox - // See SI-4759 - waitingFor = f - received = None - isSuspended = true - scheduler.managedBlock(blocker) - drainSendBuffer(mailbox) - // keep going - () => {} - } - } - todo() - } else { - synchronized { - if (!onTimeout.isEmpty) { - onTimeout.get.cancel() - onTimeout = None - } - } - received = Some(qel.msg) - senders = qel.session :: senders - done = true - } - } - - val result = f(received.get) - received = None - senders = senders.tail - result - } - - /** See the companion object's `react` method. */ - override def react(handler: PartialFunction[Any, Unit]): Nothing = { - synchronized { - if (shouldExit) exit() - } - super.react(handler) - } - - /** See the companion object's `reactWithin` method. */ - override def reactWithin(msec: Long)(handler: PartialFunction[Any, Unit]): Nothing = { - synchronized { - if (shouldExit) exit() - } - super.reactWithin(msec)(handler) - } - - /** Receives the next message from the mailbox */ - def ? : Any = receive { - case x => x - } - - // guarded by lock of this - // never throws SuspendActorControl - private[actors] override def scheduleActor(f: PartialFunction[Any, Any], msg: Any) = - if (f eq null) { - // do nothing (timeout is handled instead) - } - else { - val task = new ActorTask(this, null, f, msg) - scheduler executeFromActor task - } - - /* Used for notifying scheduler when blocking inside receive/receiveWithin. */ - private object blocker extends scala.concurrent.ManagedBlocker { - def block() = { - Actor.this.suspendActor() - true - } - def isReleasable = - !Actor.this.isSuspended - } - - private def suspendActor() = synchronized { - while (isSuspended) { - try { - wait() - } catch { - case _: InterruptedException => - } - } - // links: check if we should exit - if (shouldExit) exit() - } - - private def resumeActor() { - isSuspended = false - notify() - } - - private[actors] override def exiting = synchronized { - _state == Actor.State.Terminated - } - - // guarded by this - private[actors] override def dostart() { - // Reset various flags. - // - // Note that we do *not* reset `trapExit`. The reason is that - // users should be able to set the field in the constructor - // and before `act` is called. - exitReason = 'normal - shouldExit = false - - super.dostart() - } +trait Actor extends InternalActor with ReplyReactor { override def start(): Actor = synchronized { super.start() this } - /** State of this actor */ - override def getState: Actor.State.Value = synchronized { - if (isSuspended) { - if (onTimeout.isEmpty) - Actor.State.Blocked - else - Actor.State.TimedBlocked - } else - super.getState - } - - // guarded by this - private[actors] var links: List[AbstractActor] = Nil - - /** - * Links `self` to actor `to`. - * - * @param to the actor to link to - * @return the parameter actor - */ - def link(to: AbstractActor): AbstractActor = { - assert(Actor.self(scheduler) == this, "link called on actor different from self") - this linkTo to - to linkTo this - to - } - - /** - * Links `self` to the actor defined by `body`. - * - * @param body the body of the actor to link to - * @return the parameter actor - */ - def link(body: => Unit): Actor = { - assert(Actor.self(scheduler) == this, "link called on actor different from self") - val a = new Actor { - def act() = body - override final val scheduler: IScheduler = Actor.this.scheduler - } - link(a) - a.start() - a - } - - private[actors] def linkTo(to: AbstractActor) = synchronized { - links = to :: links - } - - /** - * Unlinks `self` from actor `from`. - */ - def unlink(from: AbstractActor) { - assert(Actor.self(scheduler) == this, "unlink called on actor different from self") - this unlinkFrom from - from unlinkFrom this - } - - private[actors] def unlinkFrom(from: AbstractActor) = synchronized { - links = links.filterNot(from.==) - } - - @volatile - var trapExit = false - // guarded by this - private var exitReason: AnyRef = 'normal - // guarded by this - private[actors] var shouldExit = false - - /** - * Terminates execution of `self` with the following effect on - * linked actors: - * - * For each linked actor `a` with `trapExit` set to `'''true'''`, - * send message `Exit(self, reason)` to `a`. - * - * For each linked actor `a` with `trapExit` set to `'''false'''` - * (default), call `a.exit(reason)` if `reason != 'normal`. - */ - protected[actors] def exit(reason: AnyRef): Nothing = { - synchronized { - exitReason = reason - } - exit() - } - - /** - * Terminates with exit reason `'normal`. - */ - protected[actors] override def exit(): Nothing = { - val todo = synchronized { - if (!links.isEmpty) - exitLinked() - else - () => {} - } - todo() - super.exit() - } - - // Assume !links.isEmpty - // guarded by this - private[actors] def exitLinked(): () => Unit = { - _state = Actor.State.Terminated - // reset waitingFor, otherwise getState returns Suspended - waitingFor = Reactor.waitingForNone - // remove this from links - val mylinks = links.filterNot(this.==) - // unlink actors - mylinks.foreach(unlinkFrom(_)) - // return closure that locks linked actors - () => { - mylinks.foreach((linked: AbstractActor) => { - linked.synchronized { - if (!linked.exiting) { - linked.unlinkFrom(this) - linked.exit(this, exitReason) - } - } - }) - } - } - - // Assume !links.isEmpty - // guarded by this - private[actors] def exitLinked(reason: AnyRef): () => Unit = { - exitReason = reason - exitLinked() - } - - // Assume !this.exiting - private[actors] def exit(from: AbstractActor, reason: AnyRef) { - if (trapExit) { - this ! Exit(from, reason) - } - else if (reason != 'normal) - synchronized { - shouldExit = true - exitReason = reason - // resume this Actor in a way that - // causes it to exit - // (because shouldExit == true) - if (isSuspended) - resumeActor() - else if (waitingFor ne Reactor.waitingForNone) { - waitingFor = Reactor.waitingForNone - // it doesn't matter what partial function we are passing here - scheduleActor(waitingFor, null) - /* Here we should not throw a SuspendActorControl, - since the current method is called from an actor that - is in the process of exiting. - - Therefore, the contract for scheduleActor is that - it never throws a SuspendActorControl. - */ - } - } - } - - /** Requires qualified private, because `RemoteActor` must - * register a termination handler. - */ - private[actors] def onTerminate(f: => Unit) { - scheduler.onTerminate(this) { f } - } } - -/** - * Used as the timeout pattern in - * - * receiveWithin and - * - * reactWithin. - * - * @example {{{ - * receiveWithin(500) { - * case (x, y) => ... - * case TIMEOUT => ... - * } - * }}} - * - * @author Philipp Haller - */ -case object TIMEOUT - - -/** Sent to an actor with `trapExit` set to `'''true'''` whenever one of its - * linked actors terminates. - * - * @param from the actor that terminated - * @param reason the reason that caused the actor to terminate - */ -case class Exit(from: AbstractActor, reason: AnyRef) - -/** Manages control flow of actor executions. - * - * @author Philipp Haller - */ -private[actors] class SuspendActorControl extends ControlThrowable diff --git a/src/actors/scala/actors/ActorCanReply.scala b/src/actors/scala/actors/ActorCanReply.scala index b307aafa57..d92fb183c0 100644 --- a/src/actors/scala/actors/ActorCanReply.scala +++ b/src/actors/scala/actors/ActorCanReply.scala @@ -18,7 +18,7 @@ import scala.concurrent.SyncVar * @author Philipp Haller */ private[actors] trait ActorCanReply extends ReactorCanReply { - this: AbstractActor with ReplyReactor => + this: AbstractActor with InternalReplyReactor => override def !?(msg: Any): Any = { val replyCh = new Channel[Any](Actor.self(scheduler)) diff --git a/src/actors/scala/actors/ActorTask.scala b/src/actors/scala/actors/ActorTask.scala index 090d0448f0..bb04302238 100644 --- a/src/actors/scala/actors/ActorTask.scala +++ b/src/actors/scala/actors/ActorTask.scala @@ -17,7 +17,7 @@ package scala.actors * changes to the underlying var invisible.) I can't figure out what's supposed * to happen, so I renamed the constructor parameter to at least be less confusing. */ -private[actors] class ActorTask(actor: Actor, +private[actors] class ActorTask(actor: InternalActor, fun: () => Unit, handler: PartialFunction[Any, Any], initialMsg: Any) @@ -32,7 +32,7 @@ private[actors] class ActorTask(actor: Actor, } protected override def terminateExecution(e: Throwable) { - val senderInfo = try { Some(actor.sender) } catch { + val senderInfo = try { Some(actor.internalSender) } catch { case _: Exception => None } // !!! If this is supposed to be setting the current contents of the @@ -45,13 +45,16 @@ private[actors] class ActorTask(actor: Actor, e) val todo = actor.synchronized { - if (!actor.links.isEmpty) + val res = if (!actor.links.isEmpty) actor.exitLinked(uncaught) else { super.terminateExecution(e) () => {} } + actor.internalPostStop + res } + todo() } diff --git a/src/actors/scala/actors/Channel.scala b/src/actors/scala/actors/Channel.scala index 62331239e8..36cee66b42 100644 --- a/src/actors/scala/actors/Channel.scala +++ b/src/actors/scala/actors/Channel.scala @@ -34,7 +34,7 @@ case class ! [a](ch: Channel[a], msg: a) * @define actor channel * @define channel channel */ -class Channel[Msg](val receiver: Actor) extends InputChannel[Msg] with OutputChannel[Msg] with CanReply[Msg, Any] { +class Channel[Msg](val receiver: InternalActor) extends InputChannel[Msg] with OutputChannel[Msg] with CanReply[Msg, Any] { type Future[+P] = scala.actors.Future[P] diff --git a/src/actors/scala/actors/Combinators.scala b/src/actors/scala/actors/Combinators.scala index 5276c7843e..c1a9095614 100644 --- a/src/actors/scala/actors/Combinators.scala +++ b/src/actors/scala/actors/Combinators.scala @@ -16,7 +16,7 @@ private[actors] trait Combinators { * Enables the composition of suspendable closures using `andThen`, * `loop`, `loopWhile`, etc. */ - implicit def mkBody[a](body: => a): Actor.Body[a] + implicit def mkBody[a](body: => a): InternalActor.Body[a] /** * Repeatedly executes `body`. diff --git a/src/actors/scala/actors/InternalActor.scala b/src/actors/scala/actors/InternalActor.scala new file mode 100644 index 0000000000..c94da5b9fd --- /dev/null +++ b/src/actors/scala/actors/InternalActor.scala @@ -0,0 +1,509 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2005-2011, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ +package scala.actors +import java.util.TimerTask +import scala.util.control.ControlThrowable + +private[actors] object InternalActor { + private[actors] trait Body[a] { + def andThen[b](other: => b): Unit + } +} + +private[actors] trait InternalActor extends AbstractActor with InternalReplyReactor with ActorCanReply with InputChannel[Any] with Serializable { + + /* The following two fields are only used when the actor + * suspends by blocking its underlying thread, for example, + * when waiting in a receive or synchronous send. + */ + @volatile + private[actors] var isSuspended = false + + /* This field is used to communicate the received message from + * the invocation of send to the place where the thread of + * the receiving actor resumes inside receive/receiveWithin. + */ + @volatile + private var received: Option[Any] = None + + protected[actors] override def scheduler: IScheduler = Scheduler + + private[actors] override def startSearch(msg: Any, replyTo: OutputChannel[Any], handler: PartialFunction[Any, Any]) = + if (isSuspended) { + () => + synchronized { + mailbox.append(msg, replyTo) + resumeActor() + } + } else super.startSearch(msg, replyTo, handler) + + // we override this method to check `shouldExit` before suspending + private[actors] override def searchMailbox(startMbox: MQueue[Any], + handler: PartialFunction[Any, Any], + resumeOnSameThread: Boolean) { + var tmpMbox = startMbox + var done = false + while (!done) { + val qel = tmpMbox.extractFirst((msg: Any, replyTo: OutputChannel[Any]) => { + senders = List(replyTo) + handler.isDefinedAt(msg) + }) + if (tmpMbox ne mailbox) + tmpMbox.foreach((m, s) => mailbox.append(m, s)) + if (null eq qel) { + synchronized { + // in mean time new stuff might have arrived + if (!sendBuffer.isEmpty) { + tmpMbox = new MQueue[Any]("Temp") + drainSendBuffer(tmpMbox) + // keep going + } else { + // very important to check for `shouldExit` at this point + // since linked actors might have set it after we checked + // last time (e.g., at the beginning of `react`) + if (shouldExit) exit() + waitingFor = handler + // see Reactor.searchMailbox + throw Actor.suspendException + } + } + } else { + resumeReceiver((qel.msg, qel.session), handler, resumeOnSameThread) + done = true + } + } + } + + private[actors] override def makeReaction(fun: () => Unit, handler: PartialFunction[Any, Any], msg: Any): Runnable = + new ActorTask(this, fun, handler, msg) + + /** See the companion object's `receive` method. */ + def receive[R](f: PartialFunction[Any, R]): R = { + assert(Actor.self(scheduler) == this, "receive from channel belonging to other actor") + + synchronized { + if (shouldExit) exit() // links + drainSendBuffer(mailbox) + } + + var done = false + while (!done) { + val qel = mailbox.extractFirst((m: Any, replyTo: OutputChannel[Any]) => { + senders = replyTo :: senders + val matches = f.isDefinedAt(m) + senders = senders.tail + matches + }) + if (null eq qel) { + synchronized { + // in mean time new stuff might have arrived + if (!sendBuffer.isEmpty) { + drainSendBuffer(mailbox) + // keep going + } else { + waitingFor = f + isSuspended = true + scheduler.managedBlock(blocker) + drainSendBuffer(mailbox) + // keep going + } + } + } else { + received = Some(qel.msg) + senders = qel.session :: senders + done = true + } + } + + val result = f(received.get) + received = None + senders = senders.tail + result + } + + /** See the companion object's `receiveWithin` method. */ + def receiveWithin[R](msec: Long)(f: PartialFunction[Any, R]): R = { + assert(Actor.self(scheduler) == this, "receive from channel belonging to other actor") + + synchronized { + if (shouldExit) exit() // links + drainSendBuffer(mailbox) + } + + // first, remove spurious TIMEOUT message from mailbox if any + mailbox.extractFirst((m: Any, replyTo: OutputChannel[Any]) => m == TIMEOUT) + + val receiveTimeout = () => { + if (f.isDefinedAt(TIMEOUT)) { + received = Some(TIMEOUT) + senders = this :: senders + } else + sys.error("unhandled timeout") + } + + var done = false + while (!done) { + val qel = mailbox.extractFirst((m: Any, replyTo: OutputChannel[Any]) => { + senders = replyTo :: senders + val matches = f.isDefinedAt(m) + senders = senders.tail + matches + }) + if (null eq qel) { + val todo = synchronized { + // in mean time new stuff might have arrived + if (!sendBuffer.isEmpty) { + drainSendBuffer(mailbox) + // keep going + () => {} + } else if (msec == 0L) { + done = true + receiveTimeout + } else { + if (onTimeout.isEmpty) { + if (!f.isDefinedAt(TIMEOUT)) + sys.error("unhandled timeout") + + val thisActor = this + onTimeout = Some(new TimerTask { + def run() { + thisActor.send(TIMEOUT, thisActor) + } + }) + Actor.timer.schedule(onTimeout.get, msec) + } + + // It is possible that !onTimeout.isEmpty, but TIMEOUT is not yet in mailbox + // See SI-4759 + waitingFor = f + received = None + isSuspended = true + scheduler.managedBlock(blocker) + drainSendBuffer(mailbox) + // keep going + () => {} + } + } + todo() + } else { + synchronized { + if (!onTimeout.isEmpty) { + onTimeout.get.cancel() + onTimeout = None + } + } + received = Some(qel.msg) + senders = qel.session :: senders + done = true + } + } + + val result = f(received.get) + received = None + senders = senders.tail + result + } + + /** See the companion object's `react` method. */ + override def react(handler: PartialFunction[Any, Unit]): Nothing = { + synchronized { + if (shouldExit) exit() + } + super.react(handler) + } + + /** See the companion object's `reactWithin` method. */ + override def reactWithin(msec: Long)(handler: PartialFunction[Any, Unit]): Nothing = { + synchronized { + if (shouldExit) exit() + } + super.reactWithin(msec)(handler) + } + + /** Receives the next message from the mailbox */ + def ? : Any = receive { + case x => x + } + + // guarded by lock of this + // never throws SuspendActorControl + private[actors] override def scheduleActor(f: PartialFunction[Any, Any], msg: Any) = + if (f eq null) { + // do nothing (timeout is handled instead) + } else { + val task = new ActorTask(this, null, f, msg) + scheduler executeFromActor task + } + + /* Used for notifying scheduler when blocking inside receive/receiveWithin. */ + private object blocker extends scala.concurrent.ManagedBlocker { + def block() = { + InternalActor.this.suspendActor() + true + } + def isReleasable = + !InternalActor.this.isSuspended + } + + private def suspendActor() = synchronized { + while (isSuspended) { + try { + wait() + } catch { + case _: InterruptedException => + } + } + // links: check if we should exit + if (shouldExit) exit() + } + + private def resumeActor() { + isSuspended = false + notify() + } + + private[actors] override def exiting = synchronized { + _state == Actor.State.Terminated + } + + // guarded by this + private[actors] override def dostart() { + // Reset various flags. + // + // Note that we do *not* reset `trapExit`. The reason is that + // users should be able to set the field in the constructor + // and before `act` is called. + exitReason = 'normal + shouldExit = false + + super.dostart() + } + + override def start(): InternalActor = synchronized { + super.start() + this + } + + /** State of this actor */ + override def getState: Actor.State.Value = synchronized { + if (isSuspended) { + if (onTimeout.isEmpty) + Actor.State.Blocked + else + Actor.State.TimedBlocked + } else + super.getState + } + + // guarded by this + private[actors] var links: List[AbstractActor] = Nil + + /** + * Links self to actor to. + * + * @param to the actor to link to + * @return the parameter actor + */ + def link(to: AbstractActor): AbstractActor = { + assert(Actor.self(scheduler) == this, "link called on actor different from self") + this linkTo to + to linkTo this + to + } + + /** + * Links self to the actor defined by body. + * + * @param body the body of the actor to link to + * @return the parameter actor + */ + def link(body: => Unit): Actor = { + assert(Actor.self(scheduler) == this, "link called on actor different from self") + val a = new Actor { + def act() = body + override final val scheduler: IScheduler = InternalActor.this.scheduler + } + link(a) + a.start() + a + } + + private[actors] def linkTo(to: AbstractActor) = synchronized { + links = to :: links + } + + /** + * Unlinks self from actor from. + */ + def unlink(from: AbstractActor) { + assert(Actor.self(scheduler) == this, "unlink called on actor different from self") + this unlinkFrom from + from unlinkFrom this + } + + private[actors] def unlinkFrom(from: AbstractActor) = synchronized { + links = links.filterNot(from.==) + } + + @volatile + private[actors] var _trapExit = false + + def trapExit = _trapExit + + def trapExit_=(value: Boolean) = _trapExit = value + + // guarded by this + private var exitReason: AnyRef = 'normal + // guarded by this + private[actors] var shouldExit = false + + /** + *

+ * Terminates execution of self with the following + * effect on linked actors: + *

+ *

+ * For each linked actor a with + * trapExit set to true, send message + * Exit(self, reason) to a. + *

+ *

+ * For each linked actor a with + * trapExit set to false (default), + * call a.exit(reason) if + * reason != 'normal. + *

+ */ + protected[actors] def exit(reason: AnyRef): Nothing = { + synchronized { + exitReason = reason + } + exit() + } + + /** + * Terminates with exit reason 'normal. + */ + protected[actors] override def exit(): Nothing = { + val todo = synchronized { + if (!links.isEmpty) + exitLinked() + else + () => {} + } + todo() + super.exit() + } + + // Assume !links.isEmpty + // guarded by this + private[actors] def exitLinked(): () => Unit = { + _state = Actor.State.Terminated + // reset waitingFor, otherwise getState returns Suspended + waitingFor = Reactor.waitingForNone + // remove this from links + val mylinks = links.filterNot(this.==) + // unlink actors + mylinks.foreach(unlinkFrom(_)) + // return closure that locks linked actors + () => { + mylinks.foreach((linked: AbstractActor) => { + linked.synchronized { + if (!linked.exiting) { + linked.unlinkFrom(this) + linked.exit(this, exitReason) + } + } + }) + } + } + + // Assume !links.isEmpty + // guarded by this + private[actors] def exitLinked(reason: AnyRef): () => Unit = { + exitReason = reason + exitLinked() + } + + // Assume !this.exiting + private[actors] def exit(from: AbstractActor, reason: AnyRef) { + if (trapExit) { + this ! Exit(from, reason) + } else if (reason != 'normal) + stop(reason) + } + + /* Requires qualified private, because RemoteActor must + * register a termination handler. + */ + private[actors] def onTerminate(f: => Unit) { + scheduler.onTerminate(this) { f } + } + + private[actors] def internalPostStop() = {} + + private[actors] def stop(reason: AnyRef): Unit = { + synchronized { + shouldExit = true + exitReason = reason + // resume this Actor in a way that + // causes it to exit + // (because shouldExit == true) + if (isSuspended) + resumeActor() + else if (waitingFor ne Reactor.waitingForNone) { + waitingFor = Reactor.waitingForNone + // it doesn't matter what partial function we are passing here + val task = new ActorTask(this, null, waitingFor, null) + scheduler execute task + /* Here we should not throw a SuspendActorControl, + since the current method is called from an actor that + is in the process of exiting. + + Therefore, the contract for scheduleActor is that + it never throws a SuspendActorControl. + */ + } + } + } +} + +/** + * Used as the timeout pattern in + * + * receiveWithin and + * + * reactWithin. + * + * @example {{{ + * receiveWithin(500) { + * case (x, y) => ... + * case TIMEOUT => ... + * } + * }}} + * + * @author Philipp Haller + */ +case object TIMEOUT + +/** + * Sent to an actor + * with `trapExit` set to `true` whenever one of its linked actors + * terminates. + * + * @param from the actor that terminated + * @param reason the reason that caused the actor to terminate + */ +case class Exit(from: AbstractActor, reason: AnyRef) + +/** + * Manages control flow of actor executions. + * + * @author Philipp Haller + */ +private[actors] class SuspendActorControl extends ControlThrowable diff --git a/src/actors/scala/actors/InternalReplyReactor.scala b/src/actors/scala/actors/InternalReplyReactor.scala new file mode 100644 index 0000000000..38295138d4 --- /dev/null +++ b/src/actors/scala/actors/InternalReplyReactor.scala @@ -0,0 +1,161 @@ +package scala.actors + +import java.util.{TimerTask} + +/** + * Extends the [[scala.actors.Reactor]] + * trait with methods to reply to the sender of a message. + * Sending a message to a ReplyReactor implicitly + * passes a reference to the sender together with the message. + * + * @author Philipp Haller + * + * @define actor `ReplyReactor` + */ +trait InternalReplyReactor extends Reactor[Any] with ReactorCanReply { + + /* A list of the current senders. The head of the list is + * the sender of the message that was received last. + */ + @volatile + private[actors] var senders: List[OutputChannel[Any]] = List() + + /* This option holds a TimerTask when the actor waits in a + * reactWithin. The TimerTask is cancelled when the actor + * resumes. + * + * guarded by this + */ + private[actors] var onTimeout: Option[TimerTask] = None + + /** + * Returns the $actor which sent the last received message. + */ + protected[actors] def internalSender: OutputChannel[Any] = senders.head + + /** + * Replies with msg to the sender. + */ + protected[actors] def reply(msg: Any) { + internalSender ! msg + } + + override def !(msg: Any) { + send(msg, Actor.rawSelf(scheduler)) + } + + override def forward(msg: Any) { + send(msg, Actor.sender) + } + + private[actors] override def resumeReceiver(item: (Any, OutputChannel[Any]), handler: PartialFunction[Any, Any], onSameThread: Boolean) { + synchronized { + if (!onTimeout.isEmpty) { + onTimeout.get.cancel() + onTimeout = None + } + } + senders = List(item._2) + super.resumeReceiver(item, handler, onSameThread) + } + + private[actors] override def searchMailbox(startMbox: MQueue[Any], + handler: PartialFunction[Any, Any], + resumeOnSameThread: Boolean) { + var tmpMbox = startMbox + var done = false + while (!done) { + val qel = tmpMbox.extractFirst((msg: Any, replyTo: OutputChannel[Any]) => { + senders = List(replyTo) + handler.isDefinedAt(msg) + }) + if (tmpMbox ne mailbox) + tmpMbox.foreach((m, s) => mailbox.append(m, s)) + if (null eq qel) { + synchronized { + // in mean time new stuff might have arrived + if (!sendBuffer.isEmpty) { + tmpMbox = new MQueue[Any]("Temp") + drainSendBuffer(tmpMbox) + // keep going + } else { + waitingFor = handler + // see Reactor.searchMailbox + throw Actor.suspendException + } + } + } else { + resumeReceiver((qel.msg, qel.session), handler, resumeOnSameThread) + done = true + } + } + } + + private[actors] override def makeReaction(fun: () => Unit, handler: PartialFunction[Any, Any], msg: Any): Runnable = + new ReplyReactorTask(this, fun, handler, msg) + + protected[actors] override def react(handler: PartialFunction[Any, Unit]): Nothing = { + assert(Actor.rawSelf(scheduler) == this, "react on channel belonging to other actor") + super.react(handler) + } + + + /** + * Receives a message from this $actor's mailbox within a certain + * time span. + * + * This method never returns. Therefore, the rest of the computation + * has to be contained in the actions of the partial function. + * + * @param msec the time span before timeout + * @param handler a partial function with message patterns and actions + */ + protected[actors] def reactWithin(msec: Long)(handler: PartialFunction[Any, Unit]): Nothing = { + assert(Actor.rawSelf(scheduler) == this, "react on channel belonging to other actor") + + synchronized { drainSendBuffer(mailbox) } + + // first, remove spurious TIMEOUT message from mailbox if any + mailbox.extractFirst((m: Any, replyTo: OutputChannel[Any]) => m == TIMEOUT) + + while (true) { + val qel = mailbox.extractFirst((m: Any, replyTo: OutputChannel[Any]) => { + senders = List(replyTo) + handler isDefinedAt m + }) + if (null eq qel) { + synchronized { + // in mean time new messages might have arrived + if (!sendBuffer.isEmpty) { + drainSendBuffer(mailbox) + // keep going + } else if (msec == 0L) { + // throws Actor.suspendException + resumeReceiver((TIMEOUT, this), handler, false) + } else { + waitingFor = handler + val thisActor = this + onTimeout = Some(new TimerTask { + def run() { thisActor.send(TIMEOUT, thisActor) } + }) + Actor.timer.schedule(onTimeout.get, msec) + throw Actor.suspendException + } + } + } else + resumeReceiver((qel.msg, qel.session), handler, false) + } + throw Actor.suspendException + } + + override def getState: Actor.State.Value = synchronized { + if (waitingFor ne Reactor.waitingForNone) { + if (onTimeout.isEmpty) + Actor.State.Suspended + else + Actor.State.TimedSuspended + } else + _state + } + +} diff --git a/src/actors/scala/actors/OutputChannel.scala b/src/actors/scala/actors/OutputChannel.scala index 089b3d0981..1fba684975 100644 --- a/src/actors/scala/actors/OutputChannel.scala +++ b/src/actors/scala/actors/OutputChannel.scala @@ -43,5 +43,5 @@ trait OutputChannel[-Msg] { /** * Returns the `Actor` that is receiving from this $actor. */ - def receiver: Actor + def receiver: InternalActor } diff --git a/src/actors/scala/actors/ReactChannel.scala b/src/actors/scala/actors/ReactChannel.scala index fccde34272..81a166c1a4 100644 --- a/src/actors/scala/actors/ReactChannel.scala +++ b/src/actors/scala/actors/ReactChannel.scala @@ -12,7 +12,7 @@ package scala.actors /** * @author Philipp Haller */ -private[actors] class ReactChannel[Msg](receiver: ReplyReactor) extends InputChannel[Msg] { +private[actors] class ReactChannel[Msg](receiver: InternalReplyReactor) extends InputChannel[Msg] { private case class SendToReactor(channel: ReactChannel[Msg], msg: Msg) diff --git a/src/actors/scala/actors/Reactor.scala b/src/actors/scala/actors/Reactor.scala index 7d21e9f91e..8fc7578344 100644 --- a/src/actors/scala/actors/Reactor.scala +++ b/src/actors/scala/actors/Reactor.scala @@ -253,7 +253,7 @@ trait Reactor[Msg >: Null] extends OutputChannel[Msg] with Combinators { _state } - implicit def mkBody[A](body: => A) = new Actor.Body[A] { + implicit def mkBody[A](body: => A) = new InternalActor.Body[A] { def andThen[B](other: => B): Unit = Reactor.this.seq(body, other) } diff --git a/src/actors/scala/actors/ReactorCanReply.scala b/src/actors/scala/actors/ReactorCanReply.scala index 68f9999776..dabd0832f0 100644 --- a/src/actors/scala/actors/ReactorCanReply.scala +++ b/src/actors/scala/actors/ReactorCanReply.scala @@ -16,7 +16,7 @@ package scala.actors * @author Philipp Haller */ private[actors] trait ReactorCanReply extends CanReply[Any, Any] { - _: ReplyReactor => + _: InternalReplyReactor => type Future[+P] = scala.actors.Future[P] diff --git a/src/actors/scala/actors/ReplyReactor.scala b/src/actors/scala/actors/ReplyReactor.scala index 0e5ce00c91..0ffbbd3cce 100644 --- a/src/actors/scala/actors/ReplyReactor.scala +++ b/src/actors/scala/actors/ReplyReactor.scala @@ -5,165 +5,12 @@ ** /____/\___/_/ |_/____/_/ | | ** ** |/ ** \* */ - package scala.actors -import java.util.{Timer, TimerTask} - -/** - * Extends the [[scala.actors.Reactor]] trait with methods to reply to the - * sender of a message. - * - * Sending a message to a `ReplyReactor` implicitly passes a reference to - * the sender together with the message. - * - * @author Philipp Haller - * - * @define actor `ReplyReactor` - */ -trait ReplyReactor extends Reactor[Any] with ReactorCanReply { - - /* A list of the current senders. The head of the list is - * the sender of the message that was received last. - */ - @volatile - private[actors] var senders: List[OutputChannel[Any]] = List() - - /* This option holds a TimerTask when the actor waits in a - * reactWithin. The TimerTask is cancelled when the actor - * resumes. - * - * guarded by this - */ - private[actors] var onTimeout: Option[TimerTask] = None - - /** - * Returns the $actor which sent the last received message. - */ - protected[actors] def sender: OutputChannel[Any] = senders.head - - /** - * Replies with `msg` to the sender. - */ - protected[actors] def reply(msg: Any) { - sender ! msg - } - - override def !(msg: Any) { - send(msg, Actor.rawSelf(scheduler)) - } - - override def forward(msg: Any) { - send(msg, Actor.sender) - } - - private[actors] override def resumeReceiver(item: (Any, OutputChannel[Any]), handler: PartialFunction[Any, Any], onSameThread: Boolean) { - synchronized { - if (!onTimeout.isEmpty) { - onTimeout.get.cancel() - onTimeout = None - } - } - senders = List(item._2) - super.resumeReceiver(item, handler, onSameThread) - } - - private[actors] override def searchMailbox(startMbox: MQueue[Any], - handler: PartialFunction[Any, Any], - resumeOnSameThread: Boolean) { - var tmpMbox = startMbox - var done = false - while (!done) { - val qel = tmpMbox.extractFirst((msg: Any, replyTo: OutputChannel[Any]) => { - senders = List(replyTo) - handler.isDefinedAt(msg) - }) - if (tmpMbox ne mailbox) - tmpMbox.foreach((m, s) => mailbox.append(m, s)) - if (null eq qel) { - synchronized { - // in mean time new stuff might have arrived - if (!sendBuffer.isEmpty) { - tmpMbox = new MQueue[Any]("Temp") - drainSendBuffer(tmpMbox) - // keep going - } else { - waitingFor = handler - // see Reactor.searchMailbox - throw Actor.suspendException - } - } - } else { - resumeReceiver((qel.msg, qel.session), handler, resumeOnSameThread) - done = true - } - } - } - - private[actors] override def makeReaction(fun: () => Unit, handler: PartialFunction[Any, Any], msg: Any): Runnable = - new ReplyReactorTask(this, fun, handler, msg) - - protected[actors] override def react(handler: PartialFunction[Any, Unit]): Nothing = { - assert(Actor.rawSelf(scheduler) == this, "react on channel belonging to other actor") - super.react(handler) - } - - /** - * Receives a message from this $actor's mailbox within a certain - * time span. - * - * This method never returns. Therefore, the rest of the computation - * has to be contained in the actions of the partial function. - * - * @param msec the time span before timeout - * @param handler a partial function with message patterns and actions - */ - protected[actors] def reactWithin(msec: Long)(handler: PartialFunction[Any, Unit]): Nothing = { - assert(Actor.rawSelf(scheduler) == this, "react on channel belonging to other actor") - - synchronized { drainSendBuffer(mailbox) } - - // first, remove spurious TIMEOUT message from mailbox if any - mailbox.extractFirst((m: Any, replyTo: OutputChannel[Any]) => m == TIMEOUT) - - while (true) { - val qel = mailbox.extractFirst((m: Any, replyTo: OutputChannel[Any]) => { - senders = List(replyTo) - handler isDefinedAt m - }) - if (null eq qel) { - synchronized { - // in mean time new messages might have arrived - if (!sendBuffer.isEmpty) { - drainSendBuffer(mailbox) - // keep going - } else if (msec == 0L) { - // throws Actor.suspendException - resumeReceiver((TIMEOUT, this), handler, false) - } else { - waitingFor = handler - val thisActor = this - onTimeout = Some(new TimerTask { - def run() { thisActor.send(TIMEOUT, thisActor) } - }) - Actor.timer.schedule(onTimeout.get, msec) - throw Actor.suspendException - } - } - } else - resumeReceiver((qel.msg, qel.session), handler, false) - } - throw Actor.suspendException - } - - override def getState: Actor.State.Value = synchronized { - if (waitingFor ne Reactor.waitingForNone) { - if (onTimeout.isEmpty) - Actor.State.Suspended - else - Actor.State.TimedSuspended - } else - _state - } - +@deprecated("Scala Actors are beeing removed from the standard library. Please refer to the migration guide.", "2.10") +trait ReplyReactor extends InternalReplyReactor { + + protected[actors] def sender: OutputChannel[Any] = super.internalSender + } + diff --git a/src/actors/scala/actors/ReplyReactorTask.scala b/src/actors/scala/actors/ReplyReactorTask.scala index cb63d7e000..d38eb50381 100644 --- a/src/actors/scala/actors/ReplyReactorTask.scala +++ b/src/actors/scala/actors/ReplyReactorTask.scala @@ -17,13 +17,13 @@ package scala.actors * changes to the underlying var invisible.) I can't figure out what's supposed * to happen, so I renamed the constructor parameter to at least be less confusing. */ -private[actors] class ReplyReactorTask(replyReactor: ReplyReactor, +private[actors] class ReplyReactorTask(replyReactor: InternalReplyReactor, fun: () => Unit, handler: PartialFunction[Any, Any], msg: Any) extends ReactorTask(replyReactor, fun, handler, msg) { - var saved: ReplyReactor = _ + var saved: InternalReplyReactor = _ protected override def beginExecution() { saved = Actor.tl.get diff --git a/src/actors/scala/actors/UncaughtException.scala b/src/actors/scala/actors/UncaughtException.scala index 3e6efe3b7c..a3e7f795f1 100644 --- a/src/actors/scala/actors/UncaughtException.scala +++ b/src/actors/scala/actors/UncaughtException.scala @@ -20,7 +20,7 @@ package scala.actors * @author Philipp Haller * @author Erik Engbrecht */ -case class UncaughtException(actor: Actor, +case class UncaughtException(actor: InternalActor, message: Option[Any], sender: Option[OutputChannel[Any]], thread: Thread, -- cgit v1.2.3