From 1a73aa087e9ccd7584d91c653a8b61905da7c904 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 12 Jul 2012 13:09:21 +0200 Subject: Attempt #1 to optimize findMember Keeps fingerprints in scopes which are bitsets telling you what the last 6 bits of each hashcode of the names stored in the scope are. findMember will avoid looking in a scope if inferprints do not match. --- src/reflect/scala/reflect/internal/Scopes.scala | 15 ++++++++++++--- src/reflect/scala/reflect/internal/Types.scala | 5 ++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/reflect/scala/reflect/internal/Scopes.scala b/src/reflect/scala/reflect/internal/Scopes.scala index ceacd2afb0..939cd556a6 100644 --- a/src/reflect/scala/reflect/internal/Scopes.scala +++ b/src/reflect/scala/reflect/internal/Scopes.scala @@ -42,6 +42,11 @@ trait Scopes extends api.Scopes { self: SymbolTable => * SynchronizedScope as mixin. */ class Scope protected[Scopes] (initElems: ScopeEntry = null) extends Iterable[Symbol] { + + /** A bitset containing the last 6 bits of the start value of every name + * stored in this scope. + */ + var fingerPrints: Long = 0L protected[Scopes] def this(base: Scope) = { this(base.elems) @@ -95,7 +100,7 @@ trait Scopes extends api.Scopes { self: SymbolTable => * * @param e ... */ - protected def enter(e: ScopeEntry) { + protected def enterEntry(e: ScopeEntry) { elemsCache = null if (hashtable ne null) enterInHash(e) @@ -113,7 +118,11 @@ trait Scopes extends api.Scopes { self: SymbolTable => * * @param sym ... */ - def enter[T <: Symbol](sym: T): T = { enter(newScopeEntry(sym, this)); sym } + def enter[T <: Symbol](sym: T): T = { + fingerPrints |= (1L << sym.name.start) + enterEntry(newScopeEntry(sym, this)) + sym + } /** enter a symbol, asserting that no symbol with same name exists in scope * @@ -344,7 +353,7 @@ trait Scopes extends api.Scopes { self: SymbolTable => /** The empty scope (immutable). */ object EmptyScope extends Scope { - override def enter(e: ScopeEntry) { + override def enterEntry(e: ScopeEntry) { abort("EmptyScope.enter") } } diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 56cc265e48..191981efef 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -1045,6 +1045,7 @@ trait Types extends api.Types { self: SymbolTable => var continue = true var self: Type = null var membertpe: Type = null + val fingerPrint: Long = (1L << name.start) while (continue) { continue = false val bcs0 = baseClasses @@ -1052,7 +1053,9 @@ trait Types extends api.Types { self: SymbolTable => while (!bcs.isEmpty) { val decls = bcs.head.info.decls var entry = - if (name == nme.ANYNAME) decls.elems else decls.lookupEntry(name) + if (name == nme.ANYNAME) decls.elems + else if ((fingerPrint & decls.fingerPrints) == 0) null + else decls.lookupEntry(name) while (entry ne null) { val sym = entry.sym if (sym hasAllFlags requiredFlags) { -- cgit v1.2.3 From 41f4497cc57f5e010df10a9f5a98c9815c1c36fd Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 12 Jul 2012 15:36:28 +0200 Subject: Attempt #2 to optimize findMember --- src/compiler/scala/tools/nsc/Driver.scala | 4 +++- src/compiler/scala/tools/nsc/Main.scala | 6 ++---- src/reflect/scala/reflect/internal/Types.scala | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/compiler/scala/tools/nsc/Driver.scala b/src/compiler/scala/tools/nsc/Driver.scala index 0051c3bdec..15e2929ff1 100644 --- a/src/compiler/scala/tools/nsc/Driver.scala +++ b/src/compiler/scala/tools/nsc/Driver.scala @@ -1,10 +1,12 @@ package scala.tools.nsc import scala.tools.nsc.reporters.{Reporter, ConsoleReporter} -import Properties.{ versionString, copyrightString } +import Properties.{ versionString, copyrightString, residentPromptString } import scala.reflect.internal.util.{ BatchSourceFile, FakePos } abstract class Driver { + + val prompt = residentPromptString val versionMsg = "Scala compiler " + versionString + " -- " + diff --git a/src/compiler/scala/tools/nsc/Main.scala b/src/compiler/scala/tools/nsc/Main.scala index 19c872b6d3..8b7e76e994 100644 --- a/src/compiler/scala/tools/nsc/Main.scala +++ b/src/compiler/scala/tools/nsc/Main.scala @@ -12,15 +12,13 @@ import scala.tools.nsc.interactive.{ RefinedBuildManager, SimpleBuildManager } import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.reporters.{Reporter, ConsoleReporter} import scala.reflect.internal.util.{ BatchSourceFile, FakePos } //{Position} -import Properties.{ versionString, copyrightString, residentPromptString, msilLibPath } +import Properties.msilLibPath /** The main class for NSC, a compiler for the programming - * language Scala. + * language Scala. */ object Main extends Driver with EvalLoop { - val prompt = residentPromptString - def resident(compiler: Global) { loop { line => val args = line.split(' ').toList diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 191981efef..8477bdaf01 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -1053,7 +1053,7 @@ trait Types extends api.Types { self: SymbolTable => while (!bcs.isEmpty) { val decls = bcs.head.info.decls var entry = - if (name == nme.ANYNAME) decls.elems + if (name eq nme.ANYNAME) decls.elems else if ((fingerPrint & decls.fingerPrints) == 0) null else decls.lookupEntry(name) while (entry ne null) { @@ -1069,12 +1069,12 @@ trait Types extends api.Types { self: SymbolTable => Statistics.popTimer(typeOpsStack, start) if (suspension ne null) suspension foreach (_.suspended = false) return sym - } else if (member == NoSymbol) { + } else if (member eq NoSymbol) { member = sym } else if (members eq null) { - if (member.name != sym.name || - !(member == sym || - member.owner != sym.owner && + if ((member.name ne sym.name) || + !((member eq sym) || + (member.owner ne sym.owner) && !sym.isPrivate && { if (self eq null) self = this.narrow if (membertpe eq null) membertpe = self.memberType(member) @@ -1088,8 +1088,8 @@ trait Types extends api.Types { self: SymbolTable => var prevEntry = members.lookupEntry(sym.name) var symtpe: Type = null while ((prevEntry ne null) && - !(prevEntry.sym == sym || - prevEntry.sym.owner != sym.owner && + !((prevEntry.sym eq sym) || + (prevEntry.sym.owner ne sym.owner) && !sym.hasFlag(PRIVATE) && { if (self eq null) self = this.narrow if (symtpe eq null) symtpe = self.memberType(sym) -- cgit v1.2.3 From 0e3c70f2ab7f8b03cd157749f50cc301971cef03 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 12 Jul 2012 17:25:17 +0200 Subject: Attempt #3 to optimize findMember Fixed fingerPrinting scheme to work with rehashes, also added finger prints to typedIdent searches. --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 7 ++++++- src/reflect/scala/reflect/internal/Names.scala | 3 +++ src/reflect/scala/reflect/internal/Scopes.scala | 9 +++++---- src/reflect/scala/reflect/internal/Types.scala | 12 +++++++++--- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 6aa93f9cec..2f9474a6ad 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -4510,6 +4510,8 @@ trait Typers extends Modes with Adaptations with Tags { assert(errorContainer == null, "Cannot set ambiguous error twice for identifier") errorContainer = tree } + + val fingerPrint: Long = name.fingerPrint var defSym: Symbol = tree.symbol // the directly found symbol var pre: Type = NoPrefix // the prefix type of defSym, if a class member @@ -4548,7 +4550,10 @@ trait Typers extends Modes with Adaptations with Tags { var cx = startingIdentContext while (defSym == NoSymbol && cx != NoContext && (cx.scope ne null)) { // cx.scope eq null arises during FixInvalidSyms in Duplicators pre = cx.enclClass.prefix - defEntry = cx.scope.lookupEntry(name) + defEntry = { + val scope = cx.scope + if ((fingerPrint & scope.fingerPrints) != 0) scope.lookupEntry(name) else null + } if ((defEntry ne null) && qualifies(defEntry.sym)) { // Right here is where SI-1987, overloading in package objects, can be // seen to go wrong. There is an overloaded symbol, but when referring diff --git a/src/reflect/scala/reflect/internal/Names.scala b/src/reflect/scala/reflect/internal/Names.scala index 18671871ae..ae79bd0fc4 100644 --- a/src/reflect/scala/reflect/internal/Names.scala +++ b/src/reflect/scala/reflect/internal/Names.scala @@ -414,6 +414,9 @@ trait Names extends api.Names { } else toString } + + @inline + final def fingerPrint: Long = (1L << start) /** TODO - find some efficiency. */ def append(ch: Char) = newName("" + this + ch) diff --git a/src/reflect/scala/reflect/internal/Scopes.scala b/src/reflect/scala/reflect/internal/Scopes.scala index 939cd556a6..89e3c52de6 100644 --- a/src/reflect/scala/reflect/internal/Scopes.scala +++ b/src/reflect/scala/reflect/internal/Scopes.scala @@ -41,15 +41,15 @@ trait Scopes extends api.Scopes { self: SymbolTable => * This is necessary because when run from reflection every scope needs to have a * SynchronizedScope as mixin. */ - class Scope protected[Scopes] (initElems: ScopeEntry = null) extends Iterable[Symbol] { + class Scope protected[Scopes] (initElems: ScopeEntry = null, initFingerPrints: Long = 0L) extends Iterable[Symbol] { /** A bitset containing the last 6 bits of the start value of every name * stored in this scope. */ - var fingerPrints: Long = 0L + var fingerPrints: Long = initFingerPrints protected[Scopes] def this(base: Scope) = { - this(base.elems) + this(base.elems, base.fingerPrints) nestinglevel = base.nestinglevel + 1 } @@ -119,7 +119,7 @@ trait Scopes extends api.Scopes { self: SymbolTable => * @param sym ... */ def enter[T <: Symbol](sym: T): T = { - fingerPrints |= (1L << sym.name.start) + fingerPrints |= sym.name.fingerPrint enterEntry(newScopeEntry(sym, this)) sym } @@ -156,6 +156,7 @@ trait Scopes extends api.Scopes { self: SymbolTable => } def rehash(sym: Symbol, newname: Name) { + fingerPrints |= newname.fingerPrint if (hashtable ne null) { val index = sym.name.start & HASHMASK var e1 = hashtable(index) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 8477bdaf01..41d1d6e8f6 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -1045,7 +1045,7 @@ trait Types extends api.Types { self: SymbolTable => var continue = true var self: Type = null var membertpe: Type = null - val fingerPrint: Long = (1L << name.start) + val fingerPrint: Long = name.fingerPrint while (continue) { continue = false val bcs0 = baseClasses @@ -1612,8 +1612,13 @@ trait Types extends api.Types { self: SymbolTable => if (period != currentPeriod) { tpe.baseClassesPeriod = currentPeriod if (!isValidForBaseClasses(period)) { - tpe.baseClassesCache = null - tpe.baseClassesCache = tpe.memo(computeBaseClasses)(tpe.typeSymbol :: _.baseClasses.tail) + val start = Statistics.pushTimer(typeOpsStack, baseClassesNanos) + try { + tpe.baseClassesCache = null + tpe.baseClassesCache = tpe.memo(computeBaseClasses)(tpe.typeSymbol :: _.baseClasses.tail) + } finally { + Statistics.popTimer(typeOpsStack, start) + } } } if (tpe.baseClassesCache eq null) @@ -6909,6 +6914,7 @@ object TypesStats { val findMemberNanos = Statistics.newStackableTimer("time spent in findmember", typerNanos) val asSeenFromNanos = Statistics.newStackableTimer("time spent in asSeenFrom", typerNanos) val baseTypeSeqNanos = Statistics.newStackableTimer("time spent in baseTypeSeq", typerNanos) + val baseClassesNanos = Statistics.newStackableTimer("time spent in baseClasses", typerNanos) val compoundBaseTypeSeqCount = Statistics.newSubCounter(" of which for compound types", baseTypeSeqCount) val typerefBaseTypeSeqCount = Statistics.newSubCounter(" of which for typerefs", baseTypeSeqCount) val singletonBaseTypeSeqCount = Statistics.newSubCounter(" of which for singletons", baseTypeSeqCount) -- cgit v1.2.3 From 04f0b659efe8565c162b5933ace76e2adf00a16e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 12 Jul 2012 18:00:13 +0200 Subject: Attempt #4 to optimize findMember broke out findMembers as a separate operation to streamline the code. findMember itself still needs to be optimized. --- src/reflect/scala/reflect/internal/Types.scala | 71 +++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 41d1d6e8f6..afedf3460e 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -668,7 +668,8 @@ trait Types extends api.Types { self: SymbolTable => * Note: unfortunately it doesn't work to exclude DEFERRED this way. */ def membersBasedOnFlags(excludedFlags: Long, requiredFlags: Long): List[Symbol] = - findMember(nme.ANYNAME, excludedFlags, requiredFlags, false).alternatives + findMembers(excludedFlags, requiredFlags) +// findMember(nme.ANYNAME, excludedFlags, requiredFlags, false).alternatives def memberBasedOnName(name: Name, excludedFlags: Long): Symbol = findMember(name, excludedFlags, 0, false) @@ -1016,6 +1017,71 @@ trait Types extends api.Types { self: SymbolTable => if (alts.isEmpty) sym else (baseClasses.head.newOverloaded(this, alts)) } + + def findMembers(excludedFlags: Long, requiredFlags: Long): List[Symbol] = { + // if this type contains type variables, put them to sleep for a while -- don't just wipe them out by + // replacing them by the corresponding type parameter, as that messes up (e.g.) type variables in type refinements + // without this, the matchesType call would lead to type variables on both sides + // of a subtyping/equality judgement, which can lead to recursive types being constructed. + // See (t0851) for a situation where this happens. + val suspension: List[TypeVar] = if (this.isGround) null else suspendTypeVarsInType(this) + + Statistics.incCounter(findMembersCount) + val start = Statistics.pushTimer(typeOpsStack, findMembersNanos) + + //Console.println("find member " + name.decode + " in " + this + ":" + this.baseClasses)//DEBUG + var members: Scope = null + var excluded = excludedFlags | DEFERRED + var continue = true + var self: Type = null + var membertpe: Type = null + while (continue) { + continue = false + val bcs0 = baseClasses + var bcs = bcs0 + while (!bcs.isEmpty) { + val decls = bcs.head.info.decls + var entry = decls.elems + while (entry ne null) { + val sym = entry.sym + if (sym hasAllFlags requiredFlags) { + val excl = sym.getFlag(excluded) + if (excl == 0L && + (// omit PRIVATE LOCALS unless selector class is contained in class owning the def. + (bcs eq bcs0) || + !sym.isPrivateLocal || + (bcs0.head.hasTransOwner(bcs.head)))) { + if (members eq null) members = newScope + var prevEntry = members.lookupEntry(sym.name) + var symtpe: Type = null + while ((prevEntry ne null) && + !((prevEntry.sym eq sym) || + (prevEntry.sym.owner ne sym.owner) && + !sym.hasFlag(PRIVATE) && { + if (self eq null) self = this.narrow + if (symtpe eq null) symtpe = self.memberType(sym) + self.memberType(prevEntry.sym) matches symtpe + })) { + prevEntry = members lookupNextEntry prevEntry + } + if (prevEntry eq null) { + members enter sym + } + } else if (excl == DEFERRED) { + continue = true + } + } + entry = entry.next + } // while (entry ne null) + // excluded = excluded | LOCAL + bcs = bcs.tail + } // while (!bcs.isEmpty) + excluded = excludedFlags + } // while (continue) + Statistics.popTimer(typeOpsStack, start) + if (suspension ne null) suspension foreach (_.suspended = false) + if (members eq null) Nil else members.toList + } /** * Find member(s) in this type. If several members matching criteria are found, they are @@ -1122,6 +1188,7 @@ trait Types extends api.Types { self: SymbolTable => baseClasses.head.newOverloaded(this, members.toList) } } + /** The (existential or otherwise) skolems and existentially quantified variables which are free in this type */ def skolemsExceptMethodTypeParams: List[Symbol] = { var boundSyms: List[Symbol] = List() @@ -6906,12 +6973,14 @@ object TypesStats { val lubCount = Statistics.newCounter ("#toplevel lubs/glbs") val nestedLubCount = Statistics.newCounter ("#all lubs/glbs") val findMemberCount = Statistics.newCounter ("#findMember ops") + val findMembersCount = Statistics.newCounter ("#findMembers ops") val noMemberCount = Statistics.newSubCounter(" of which not found", findMemberCount) val multMemberCount = Statistics.newSubCounter(" of which multiple overloaded", findMemberCount) val typerNanos = Statistics.newTimer ("time spent typechecking", "typer") val lubNanos = Statistics.newStackableTimer("time spent in lubs", typerNanos) val subtypeNanos = Statistics.newStackableTimer("time spent in <:<", typerNanos) val findMemberNanos = Statistics.newStackableTimer("time spent in findmember", typerNanos) + val findMembersNanos = Statistics.newStackableTimer("time spent in findmembers", typerNanos) val asSeenFromNanos = Statistics.newStackableTimer("time spent in asSeenFrom", typerNanos) val baseTypeSeqNanos = Statistics.newStackableTimer("time spent in baseTypeSeq", typerNanos) val baseClassesNanos = Statistics.newStackableTimer("time spent in baseClasses", typerNanos) -- cgit v1.2.3 From 73e61b8b0b9b8137af8ec9c6a101e991552a375e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 12 Jul 2012 22:47:55 +0200 Subject: Attempt #5 to optimize findMember. Specific optimizations to findMember that have become possible because findMembers is its own function now. --- src/reflect/scala/reflect/internal/Types.scala | 105 ++++++++++++------------- 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index afedf3460e..14011f3109 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -1034,7 +1034,6 @@ trait Types extends api.Types { self: SymbolTable => var excluded = excludedFlags | DEFERRED var continue = true var self: Type = null - var membertpe: Type = null while (continue) { continue = false val bcs0 = baseClasses @@ -1044,12 +1043,13 @@ trait Types extends api.Types { self: SymbolTable => var entry = decls.elems while (entry ne null) { val sym = entry.sym - if (sym hasAllFlags requiredFlags) { - val excl = sym.getFlag(excluded) + val flags = sym.flags + if ((flags & requiredFlags) == requiredFlags) { + val excl = flags & excluded if (excl == 0L && (// omit PRIVATE LOCALS unless selector class is contained in class owning the def. (bcs eq bcs0) || - !sym.isPrivateLocal || + (flags & PrivateLocal) != PrivateLocal || (bcs0.head.hasTransOwner(bcs.head)))) { if (members eq null) members = newScope var prevEntry = members.lookupEntry(sym.name) @@ -1118,61 +1118,60 @@ trait Types extends api.Types { self: SymbolTable => var bcs = bcs0 while (!bcs.isEmpty) { val decls = bcs.head.info.decls - var entry = - if (name eq nme.ANYNAME) decls.elems - else if ((fingerPrint & decls.fingerPrints) == 0) null - else decls.lookupEntry(name) - while (entry ne null) { - val sym = entry.sym - if (sym hasAllFlags requiredFlags) { - val excl = sym.getFlag(excluded) - if (excl == 0L && - (// omit PRIVATE LOCALS unless selector class is contained in class owning the def. - (bcs eq bcs0) || - !sym.isPrivateLocal || - (bcs0.head.hasTransOwner(bcs.head)))) { - if (name.isTypeName || stableOnly && sym.isStable) { - Statistics.popTimer(typeOpsStack, start) - if (suspension ne null) suspension foreach (_.suspended = false) - return sym - } else if (member eq NoSymbol) { - member = sym - } else if (members eq null) { - if ((member.name ne sym.name) || - !((member eq sym) || - (member.owner ne sym.owner) && - !sym.isPrivate && { - if (self eq null) self = this.narrow - if (membertpe eq null) membertpe = self.memberType(member) - (membertpe matches self.memberType(sym)) - })) { + if ((fingerPrint & decls.fingerPrints) != 0) { + var entry = decls.lookupEntry(name) + while (entry ne null) { + val sym = entry.sym + val flags = sym.flags + if ((flags & requiredFlags) == requiredFlags) { + val excl = flags & excluded + if (excl == 0L && + (// omit PRIVATE LOCALS unless selector class is contained in class owning the def. + (bcs eq bcs0) || + (flags & PrivateLocal) != PrivateLocal || + (bcs0.head.hasTransOwner(bcs.head)))) { + if (name.isTypeName || stableOnly && sym.isStable) { + Statistics.popTimer(typeOpsStack, start) + if (suspension ne null) suspension foreach (_.suspended = false) + return sym + } else if (member eq NoSymbol) { + member = sym + } else if (members eq null) { + if (!((member eq sym) || + (member.owner ne sym.owner) && + (flags & PRIVATE) == 0 && { + if (self eq null) self = this.narrow + if (membertpe eq null) membertpe = self.memberType(member) + (membertpe matches self.memberType(sym)) + })) { members = newScope members enter member members enter sym + } + } else { + var prevEntry = members.lookupEntry(sym.name) + var symtpe: Type = null + while ((prevEntry ne null) && + !((prevEntry.sym eq sym) || + (prevEntry.sym.owner ne sym.owner) && + (flags & PRIVATE) == 0 && { + if (self eq null) self = this.narrow + if (symtpe eq null) symtpe = self.memberType(sym) + self.memberType(prevEntry.sym) matches symtpe + })) { + prevEntry = members lookupNextEntry prevEntry + } + if (prevEntry eq null) { + members enter sym + } } - } else { - var prevEntry = members.lookupEntry(sym.name) - var symtpe: Type = null - while ((prevEntry ne null) && - !((prevEntry.sym eq sym) || - (prevEntry.sym.owner ne sym.owner) && - !sym.hasFlag(PRIVATE) && { - if (self eq null) self = this.narrow - if (symtpe eq null) symtpe = self.memberType(sym) - self.memberType(prevEntry.sym) matches symtpe - })) { - prevEntry = members lookupNextEntry prevEntry - } - if (prevEntry eq null) { - members enter sym - } + } else if (excl == DEFERRED) { + continue = true } - } else if (excl == DEFERRED.toLong) { - continue = true } - } - entry = if (name == nme.ANYNAME) entry.next else decls lookupNextEntry entry - } // while (entry ne null) + entry = decls lookupNextEntry entry + } // while (entry ne null) + } // if (fingerPrint matches) // excluded = excluded | LOCAL bcs = if (name == nme.CONSTRUCTOR) Nil else bcs.tail } // while (!bcs.isEmpty) -- cgit v1.2.3 From e94252ea30032fa4e2f1f341c8db7fb38977618e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 12 Jul 2012 23:18:20 +0200 Subject: Attemmpt #6 to optimize findMember Replace scope by ::-list, where new elements are added at the end. --- src/reflect/scala/reflect/internal/Types.scala | 35 ++++++++++++++------------ 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 14011f3109..f8bb543cff 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -1017,7 +1017,7 @@ trait Types extends api.Types { self: SymbolTable => if (alts.isEmpty) sym else (baseClasses.head.newOverloaded(this, alts)) } - + def findMembers(excludedFlags: Long, requiredFlags: Long): List[Symbol] = { // if this type contains type variables, put them to sleep for a while -- don't just wipe them out by // replacing them by the corresponding type parameter, as that messes up (e.g.) type variables in type refinements @@ -1040,7 +1040,7 @@ trait Types extends api.Types { self: SymbolTable => var bcs = bcs0 while (!bcs.isEmpty) { val decls = bcs.head.info.decls - var entry = decls.elems + var entry = decls.elems while (entry ne null) { val sym = entry.sym val flags = sym.flags @@ -1105,7 +1105,8 @@ trait Types extends api.Types { self: SymbolTable => val start = Statistics.pushTimer(typeOpsStack, findMemberNanos) //Console.println("find member " + name.decode + " in " + this + ":" + this.baseClasses)//DEBUG - var members: Scope = null + var members: List[Symbol] = null + var lastM: ::[Symbol] = null var member: Symbol = NoSymbol var excluded = excludedFlags | DEFERRED var continue = true @@ -1144,25 +1145,26 @@ trait Types extends api.Types { self: SymbolTable => if (membertpe eq null) membertpe = self.memberType(member) (membertpe matches self.memberType(sym)) })) { - members = newScope - members enter member - members enter sym + lastM = new ::(sym, null) + members = member :: lastM } } else { - var prevEntry = members.lookupEntry(sym.name) + var others = members var symtpe: Type = null - while ((prevEntry ne null) && - !((prevEntry.sym eq sym) || - (prevEntry.sym.owner ne sym.owner) && + while ((others ne null) && + !((others.head eq sym) || + (others.head.owner ne sym.owner) && (flags & PRIVATE) == 0 && { if (self eq null) self = this.narrow if (symtpe eq null) symtpe = self.memberType(sym) - self.memberType(prevEntry.sym) matches symtpe + self.memberType(others.head) matches symtpe })) { - prevEntry = members lookupNextEntry prevEntry + others = others.tail } - if (prevEntry eq null) { - members enter sym + if (others eq null) { + val lastM1 = new ::(sym, null) + lastM.tl = lastM1 + lastM = lastM1 } } } else if (excl == DEFERRED) { @@ -1184,10 +1186,11 @@ trait Types extends api.Types { self: SymbolTable => member } else { Statistics.incCounter(multMemberCount) - baseClasses.head.newOverloaded(this, members.toList) + lastM.tl = Nil + baseClasses.head.newOverloaded(this, members) } } - + /** The (existential or otherwise) skolems and existentially quantified variables which are free in this type */ def skolemsExceptMethodTypeParams: List[Symbol] = { var boundSyms: List[Symbol] = List() -- cgit v1.2.3 From 275115e81e92fe1f0d2ae135f6736f2cad3426a6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 13 Jul 2012 00:18:07 +0200 Subject: Fixing problem that caused fingerprints to fail in reflection. Also fixed test case that failed when moving to findMembers. Avoids similar problems in the future by renaming nme.ANYNAME --- src/compiler/scala/tools/nsc/typechecker/RefChecks.scala | 2 +- src/reflect/scala/reflect/api/StandardNames.scala | 1 - src/reflect/scala/reflect/internal/StdNames.scala | 2 +- src/reflect/scala/reflect/runtime/SymbolLoaders.scala | 4 +++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala index 7318538de7..3518316fbb 100644 --- a/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala +++ b/src/compiler/scala/tools/nsc/typechecker/RefChecks.scala @@ -119,7 +119,7 @@ abstract class RefChecks extends InfoTransform with reflect.internal.transform.R // those with the DEFAULTPARAM flag, and infer the methods. Looking for the methods // directly requires inspecting the parameter list of every one. That modification // shaved 95% off the time spent in this method. - val defaultGetters = clazz.info.findMember(nme.ANYNAME, 0L, DEFAULTPARAM, false).alternatives + val defaultGetters = clazz.info.findMembers(0L, DEFAULTPARAM) val defaultMethodNames = defaultGetters map (sym => nme.defaultGetterToMethod(sym.name)) defaultMethodNames.distinct foreach { name => diff --git a/src/reflect/scala/reflect/api/StandardNames.scala b/src/reflect/scala/reflect/api/StandardNames.scala index 9ec66b8531..eb1ecda900 100644 --- a/src/reflect/scala/reflect/api/StandardNames.scala +++ b/src/reflect/scala/reflect/api/StandardNames.scala @@ -43,7 +43,6 @@ trait StandardNames extends base.StandardNames { val SUPER_PREFIX_STRING: String val TRAIT_SETTER_SEPARATOR_STRING: String - val ANYNAME: TermName val FAKE_LOCAL_THIS: TermName val INITIALIZER: TermName val LAZY_LOCAL: TermName diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 72a99589d5..b9230dc97b 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -294,7 +294,7 @@ trait StdNames { val WHILE_PREFIX = "while$" // Compiler internal names - val ANYNAME: NameType = "" + val ANYname: NameType = "" val CONSTRUCTOR: NameType = "" val EQEQ_LOCAL_VAR: NameType = "eqEqTemp$" val FAKE_LOCAL_THIS: NameType = "this$" diff --git a/src/reflect/scala/reflect/runtime/SymbolLoaders.scala b/src/reflect/scala/reflect/runtime/SymbolLoaders.scala index c1cd5d2911..eb48e9dc79 100644 --- a/src/reflect/scala/reflect/runtime/SymbolLoaders.scala +++ b/src/reflect/scala/reflect/runtime/SymbolLoaders.scala @@ -99,8 +99,10 @@ trait SymbolLoaders { self: SymbolTable => 0 < dp && dp < (name.length - 1) } - class PackageScope(pkgClass: Symbol) extends Scope() with SynchronizedScope { + class PackageScope(pkgClass: Symbol) extends Scope(initFingerPrints = -1L) // disable fingerprinting as we do not know entries beforehand + with SynchronizedScope { assert(pkgClass.isType) + // disable fingerprinting as we do not know entries beforehand private val negatives = mutable.Set[Name]() // Syncnote: Performance only, so need not be protected. override def lookupEntry(name: Name): ScopeEntry = { val e = super.lookupEntry(name) -- cgit v1.2.3 From 77e56927603ba7f155c65f89135ac357c9ec3d35 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 13 Jul 2012 14:13:42 +0200 Subject: Attempty #7 to optimize findMember memberType operations are not cached, so that at most one memberType is taken per found symbol. --- src/reflect/scala/reflect/internal/Types.scala | 63 ++++++++++++++++++-------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index f8bb543cff..3f27dbba76 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -1105,14 +1105,32 @@ trait Types extends api.Types { self: SymbolTable => val start = Statistics.pushTimer(typeOpsStack, findMemberNanos) //Console.println("find member " + name.decode + " in " + this + ":" + this.baseClasses)//DEBUG + var member: Symbol = NoSymbol var members: List[Symbol] = null var lastM: ::[Symbol] = null - var member: Symbol = NoSymbol + var membertpe: Type = null + var membertpes: Array[Type] = null var excluded = excludedFlags | DEFERRED var continue = true var self: Type = null - var membertpe: Type = null val fingerPrint: Long = name.fingerPrint + + def getMtpe(sym: Symbol, idx: Int): Type = { + var result = membertpes(idx) + if (result eq null) { result = self memberType sym; membertpes(idx) = result } + result + } + + def addMtpe(xs: Array[Type], tpe: Type, idx: Int): Array[Type] = + if (idx < xs.length ) { + xs(idx) = tpe + xs + } else { + val ys = new Array[Type](xs.length * 2) + Array.copy(xs, 0, ys, 0, xs.length) + addMtpe(ys, tpe, idx) + } + while (continue) { continue = false val bcs0 = baseClasses @@ -1138,33 +1156,42 @@ trait Types extends api.Types { self: SymbolTable => } else if (member eq NoSymbol) { member = sym } else if (members eq null) { - if (!((member eq sym) || - (member.owner ne sym.owner) && - (flags & PRIVATE) == 0 && { - if (self eq null) self = this.narrow - if (membertpe eq null) membertpe = self.memberType(member) - (membertpe matches self.memberType(sym)) - })) { + var symtpe: Type = null + if ((member ne sym) && + ((member.owner eq sym.owner) || + (flags & PRIVATE) != 0 || { + if (self eq null) self = this.narrow + if (membertpe eq null) membertpe = self.memberType(member) + symtpe = self.memberType(sym) + !(membertpe matches symtpe) + })) { lastM = new ::(sym, null) members = member :: lastM + membertpes = new Array[Type](8) + membertpes(0) = membertpe + membertpes(1) = symtpe } } else { var others = members + var idx = 0 var symtpe: Type = null - while ((others ne null) && - !((others.head eq sym) || - (others.head.owner ne sym.owner) && - (flags & PRIVATE) == 0 && { - if (self eq null) self = this.narrow - if (symtpe eq null) symtpe = self.memberType(sym) - self.memberType(others.head) matches symtpe - })) { + while ((others ne null) && { + val other = others.head + (other ne sym) && + ((other.owner eq sym.owner) || + (flags & PRIVATE) != 0 || { + if (self eq null) self = this.narrow + if (symtpe eq null) symtpe = self.memberType(sym) + !(getMtpe(other, idx) matches symtpe) + })}) { others = others.tail + idx += 1 } if (others eq null) { val lastM1 = new ::(sym, null) lastM.tl = lastM1 lastM = lastM1 + membertpes = addMtpe(membertpes, symtpe, idx) } } } else if (excl == DEFERRED) { @@ -5132,7 +5159,7 @@ trait Types extends api.Types { self: SymbolTable => */ def needsOuterTest(patType: Type, selType: Type, currentOwner: Symbol) = { def createDummyClone(pre: Type): Type = { - val dummy = currentOwner.enclClass.newValue(nme.ANYNAME).setInfo(pre.widen) + val dummy = currentOwner.enclClass.newValue(nme.ANYname).setInfo(pre.widen) singleType(ThisType(currentOwner.enclClass), dummy) } def maybeCreateDummyClone(pre: Type, sym: Symbol): Type = pre match { -- cgit v1.2.3 From 71d2ceb15dd39db6e031c33ed5d1be0182cc5a7f Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 13 Jul 2012 14:44:14 +0200 Subject: Attempt #8 to opimize findMember. Only consider DEFERRED members in 2nd pass. --- src/reflect/scala/reflect/internal/Types.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 3f27dbba76..3cddec5ace 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -1110,6 +1110,7 @@ trait Types extends api.Types { self: SymbolTable => var lastM: ::[Symbol] = null var membertpe: Type = null var membertpes: Array[Type] = null + var required = requiredFlags var excluded = excludedFlags | DEFERRED var continue = true var self: Type = null @@ -1142,7 +1143,7 @@ trait Types extends api.Types { self: SymbolTable => while (entry ne null) { val sym = entry.sym val flags = sym.flags - if ((flags & requiredFlags) == requiredFlags) { + if ((flags & required) == required) { val excl = flags & excluded if (excl == 0L && (// omit PRIVATE LOCALS unless selector class is contained in class owning the def. @@ -1205,6 +1206,7 @@ trait Types extends api.Types { self: SymbolTable => bcs = if (name == nme.CONSTRUCTOR) Nil else bcs.tail } // while (!bcs.isEmpty) excluded = excludedFlags + required |= DEFERRED } // while (continue) Statistics.popTimer(typeOpsStack, start) if (suspension ne null) suspension foreach (_.suspended = false) -- cgit v1.2.3 From fcb0c011c6e42f6d9fee68767c6fb31eec7926ad Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 13 Jul 2012 15:46:54 +0200 Subject: Attempt #9 to opimize findMember. Also avoid recomputation of memberType in findMembers --- src/reflect/scala/reflect/internal/Types.scala | 67 +++++++++++++++++--------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index 3cddec5ace..baa5a97f83 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -14,6 +14,7 @@ import Flags._ import scala.util.control.ControlThrowable import scala.annotation.tailrec import util.Statistics +import scala.runtime.ObjectRef /* A standard type pattern match: case ErrorType => @@ -1031,9 +1032,21 @@ trait Types extends api.Types { self: SymbolTable => //Console.println("find member " + name.decode + " in " + this + ":" + this.baseClasses)//DEBUG var members: Scope = null + var membertpes: mutable.Map[Symbol, Type] = null + var required = requiredFlags var excluded = excludedFlags | DEFERRED var continue = true var self: Type = null + + def getMtpe(cache: mutable.Map[Symbol, Type], sym: Symbol): Type = cache get sym match { + case Some(tpe) if tpe ne null => + tpe + case _ => + val result = self memberType sym + cache(sym) = result + result + } + while (continue) { continue = false val bcs0 = baseClasses @@ -1044,29 +1057,31 @@ trait Types extends api.Types { self: SymbolTable => while (entry ne null) { val sym = entry.sym val flags = sym.flags - if ((flags & requiredFlags) == requiredFlags) { + if ((flags & required) == required) { val excl = flags & excluded if (excl == 0L && (// omit PRIVATE LOCALS unless selector class is contained in class owning the def. (bcs eq bcs0) || (flags & PrivateLocal) != PrivateLocal || (bcs0.head.hasTransOwner(bcs.head)))) { - if (members eq null) members = newScope - var prevEntry = members.lookupEntry(sym.name) - var symtpe: Type = null - while ((prevEntry ne null) && - !((prevEntry.sym eq sym) || - (prevEntry.sym.owner ne sym.owner) && - !sym.hasFlag(PRIVATE) && { - if (self eq null) self = this.narrow - if (symtpe eq null) symtpe = self.memberType(sym) - self.memberType(prevEntry.sym) matches symtpe - })) { - prevEntry = members lookupNextEntry prevEntry + if (members eq null) { + members = newScope + membertpes = new mutable.HashMap } - if (prevEntry eq null) { - members enter sym + var others: ScopeEntry = members.lookupEntry(sym.name) + var symtpe: Type = null + while ((others ne null) && { + val other = others.sym + (other ne sym) && + ((other.owner eq sym.owner) || + (flags & PRIVATE) != 0 || { + if (self eq null) self = this.narrow + if (symtpe eq null) symtpe = self.memberType(sym) + !(getMtpe(membertpes, other) matches symtpe) + })}) { + others = members lookupNextEntry others } + if (others eq null) members enter sym } else if (excl == DEFERRED) { continue = true } @@ -1076,6 +1091,7 @@ trait Types extends api.Types { self: SymbolTable => // excluded = excluded | LOCAL bcs = bcs.tail } // while (!bcs.isEmpty) + required |= DEFERRED excluded = excludedFlags } // while (continue) Statistics.popTimer(typeOpsStack, start) @@ -1116,20 +1132,23 @@ trait Types extends api.Types { self: SymbolTable => var self: Type = null val fingerPrint: Long = name.fingerPrint - def getMtpe(sym: Symbol, idx: Int): Type = { + def getMtpe(idx: Int, sym: Symbol): Type = { var result = membertpes(idx) - if (result eq null) { result = self memberType sym; membertpes(idx) = result } + if (result eq null) { + result = self memberType sym + membertpes(idx) = result + } result } - def addMtpe(xs: Array[Type], tpe: Type, idx: Int): Array[Type] = - if (idx < xs.length ) { + def addMtpe(xs: Array[Type], idx: Int, tpe: Type): Array[Type] = + if (idx < xs.length) { xs(idx) = tpe xs } else { val ys = new Array[Type](xs.length * 2) Array.copy(xs, 0, ys, 0, xs.length) - addMtpe(ys, tpe, idx) + addMtpe(ys, idx, tpe) } while (continue) { @@ -1173,7 +1192,7 @@ trait Types extends api.Types { self: SymbolTable => membertpes(1) = symtpe } } else { - var others = members + var others: List[Symbol] = members var idx = 0 var symtpe: Type = null while ((others ne null) && { @@ -1183,7 +1202,7 @@ trait Types extends api.Types { self: SymbolTable => (flags & PRIVATE) != 0 || { if (self eq null) self = this.narrow if (symtpe eq null) symtpe = self.memberType(sym) - !(getMtpe(other, idx) matches symtpe) + !(getMtpe(idx, other) matches symtpe) })}) { others = others.tail idx += 1 @@ -1192,7 +1211,7 @@ trait Types extends api.Types { self: SymbolTable => val lastM1 = new ::(sym, null) lastM.tl = lastM1 lastM = lastM1 - membertpes = addMtpe(membertpes, symtpe, idx) + membertpes = addMtpe(membertpes, idx, symtpe) } } } else if (excl == DEFERRED) { @@ -1205,8 +1224,8 @@ trait Types extends api.Types { self: SymbolTable => // excluded = excluded | LOCAL bcs = if (name == nme.CONSTRUCTOR) Nil else bcs.tail } // while (!bcs.isEmpty) - excluded = excludedFlags required |= DEFERRED + excluded = excludedFlags } // while (continue) Statistics.popTimer(typeOpsStack, start) if (suspension ne null) suspension foreach (_.suspended = false) -- cgit v1.2.3 From d905558749cceff50e9872efb490450c54b6bd81 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 13 Jul 2012 18:24:37 +0200 Subject: Variation #10 to optimze findMember Undoing the memberType caching. This seems to make it slower rather than faster. Also, comparing new vs old statistics shows that only very few asSeenFrom operations are saved by the caching. --- src/compiler/scala/tools/nsc/MainBench.scala | 48 ++++++++++++++++++++ src/reflect/scala/reflect/internal/StdNames.scala | 2 +- src/reflect/scala/reflect/internal/Types.scala | 54 +++-------------------- 3 files changed, 55 insertions(+), 49 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/MainBench.scala diff --git a/src/compiler/scala/tools/nsc/MainBench.scala b/src/compiler/scala/tools/nsc/MainBench.scala new file mode 100644 index 0000000000..0037de7b94 --- /dev/null +++ b/src/compiler/scala/tools/nsc/MainBench.scala @@ -0,0 +1,48 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc + +import java.io.File +import File.pathSeparator + +import scala.tools.nsc.interactive.{ RefinedBuildManager, SimpleBuildManager } +import scala.tools.nsc.io.AbstractFile +import scala.tools.nsc.reporters.{Reporter, ConsoleReporter} +import scala.reflect.internal.util.{ BatchSourceFile, FakePos } //{Position} +import Properties.{ versionString, copyrightString, residentPromptString, msilLibPath } +import scala.reflect.internal.util.Statistics + +/** The main class for NSC, a compiler for the programming + * language Scala. + */ +object MainBench extends Driver with EvalLoop { + + lazy val theCompiler = Global(settings, reporter) + + override def newCompiler() = theCompiler + + val NIter = 50 + val NBest = 10 + + override def main(args: Array[String]) = { + val times = new Array[Long](NIter) + var start = System.nanoTime() + for (i <- 0 until NIter) { + if (i == NIter-1) { + theCompiler.settings.Ystatistics.value = true + Statistics.enabled = true + } + process(args) + val end = System.nanoTime() + val duration = (end-start)/1000000 + println(s"${duration}ms") + times(i) = duration + start = end + } + val avg = times.sorted.take(NBest).sum / NBest + println(s"avg shortest $NBest times ${avg}ms") + } +} diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index b9230dc97b..376abd6a38 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -294,7 +294,7 @@ trait StdNames { val WHILE_PREFIX = "while$" // Compiler internal names - val ANYname: NameType = "" + val ANYname: NameType = "" val CONSTRUCTOR: NameType = "" val EQEQ_LOCAL_VAR: NameType = "eqEqTemp$" val FAKE_LOCAL_THIS: NameType = "this$" diff --git a/src/reflect/scala/reflect/internal/Types.scala b/src/reflect/scala/reflect/internal/Types.scala index baa5a97f83..399b1a039a 100644 --- a/src/reflect/scala/reflect/internal/Types.scala +++ b/src/reflect/scala/reflect/internal/Types.scala @@ -1032,21 +1032,10 @@ trait Types extends api.Types { self: SymbolTable => //Console.println("find member " + name.decode + " in " + this + ":" + this.baseClasses)//DEBUG var members: Scope = null - var membertpes: mutable.Map[Symbol, Type] = null var required = requiredFlags var excluded = excludedFlags | DEFERRED var continue = true var self: Type = null - - def getMtpe(cache: mutable.Map[Symbol, Type], sym: Symbol): Type = cache get sym match { - case Some(tpe) if tpe ne null => - tpe - case _ => - val result = self memberType sym - cache(sym) = result - result - } - while (continue) { continue = false val bcs0 = baseClasses @@ -1064,10 +1053,7 @@ trait Types extends api.Types { self: SymbolTable => (bcs eq bcs0) || (flags & PrivateLocal) != PrivateLocal || (bcs0.head.hasTransOwner(bcs.head)))) { - if (members eq null) { - members = newScope - membertpes = new mutable.HashMap - } + if (members eq null) members = newScope var others: ScopeEntry = members.lookupEntry(sym.name) var symtpe: Type = null while ((others ne null) && { @@ -1077,7 +1063,7 @@ trait Types extends api.Types { self: SymbolTable => (flags & PRIVATE) != 0 || { if (self eq null) self = this.narrow if (symtpe eq null) symtpe = self.memberType(sym) - !(getMtpe(membertpes, other) matches symtpe) + !(self.memberType(other) matches symtpe) })}) { others = members lookupNextEntry others } @@ -1092,7 +1078,7 @@ trait Types extends api.Types { self: SymbolTable => bcs = bcs.tail } // while (!bcs.isEmpty) required |= DEFERRED - excluded = excludedFlags + excluded &= ~(DEFERRED.toLong) } // while (continue) Statistics.popTimer(typeOpsStack, start) if (suspension ne null) suspension foreach (_.suspended = false) @@ -1125,32 +1111,12 @@ trait Types extends api.Types { self: SymbolTable => var members: List[Symbol] = null var lastM: ::[Symbol] = null var membertpe: Type = null - var membertpes: Array[Type] = null var required = requiredFlags var excluded = excludedFlags | DEFERRED var continue = true var self: Type = null val fingerPrint: Long = name.fingerPrint - def getMtpe(idx: Int, sym: Symbol): Type = { - var result = membertpes(idx) - if (result eq null) { - result = self memberType sym - membertpes(idx) = result - } - result - } - - def addMtpe(xs: Array[Type], idx: Int, tpe: Type): Array[Type] = - if (idx < xs.length) { - xs(idx) = tpe - xs - } else { - val ys = new Array[Type](xs.length * 2) - Array.copy(xs, 0, ys, 0, xs.length) - addMtpe(ys, idx, tpe) - } - while (continue) { continue = false val bcs0 = baseClasses @@ -1176,24 +1142,18 @@ trait Types extends api.Types { self: SymbolTable => } else if (member eq NoSymbol) { member = sym } else if (members eq null) { - var symtpe: Type = null if ((member ne sym) && ((member.owner eq sym.owner) || (flags & PRIVATE) != 0 || { if (self eq null) self = this.narrow if (membertpe eq null) membertpe = self.memberType(member) - symtpe = self.memberType(sym) - !(membertpe matches symtpe) + !(membertpe matches self.memberType(sym)) })) { lastM = new ::(sym, null) members = member :: lastM - membertpes = new Array[Type](8) - membertpes(0) = membertpe - membertpes(1) = symtpe } } else { var others: List[Symbol] = members - var idx = 0 var symtpe: Type = null while ((others ne null) && { val other = others.head @@ -1202,16 +1162,14 @@ trait Types extends api.Types { self: SymbolTable => (flags & PRIVATE) != 0 || { if (self eq null) self = this.narrow if (symtpe eq null) symtpe = self.memberType(sym) - !(getMtpe(idx, other) matches symtpe) + !(self.memberType(other) matches symtpe) })}) { others = others.tail - idx += 1 } if (others eq null) { val lastM1 = new ::(sym, null) lastM.tl = lastM1 lastM = lastM1 - membertpes = addMtpe(membertpes, idx, symtpe) } } } else if (excl == DEFERRED) { @@ -1225,7 +1183,7 @@ trait Types extends api.Types { self: SymbolTable => bcs = if (name == nme.CONSTRUCTOR) Nil else bcs.tail } // while (!bcs.isEmpty) required |= DEFERRED - excluded = excludedFlags + excluded &= ~(DEFERRED.toLong) } // while (continue) Statistics.popTimer(typeOpsStack, start) if (suspension ne null) suspension foreach (_.suspended = false) -- cgit v1.2.3 From fcbdc1725c6fcd65a071709408ef75097f487cb7 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Thu, 28 Jun 2012 15:54:08 +0200 Subject: SI-5235 Correct usecase variable expansion The bug is related to a couple of other annoyances, also fixed: - usecases without type params were crashing scaladoc due to a change in the PolyTypes class (not allowing empty tparams list) - properly getting rid of backticks (even if the link is not valid) - correct linking for usecases with $Coll = `immutable.Seq` (the symbol searching algorithm was too of restrictive, now we search the entire ownerchain - and the empty package at the end) - give a warning if the type lookup fails - finally, added a $Coll variable to List, for some reason it wasn't there and we were getting immutable.Seq as the result of use cases. --- src/compiler/scala/tools/nsc/ast/DocComments.scala | 49 +++++++----- .../scala/tools/nsc/typechecker/Typers.scala | 5 +- src/library/scala/collection/immutable/List.scala | 3 +- .../scala/collection/immutable/StringOps.scala | 2 +- .../scala/collection/mutable/ArrayOps.scala | 2 +- .../scala/tools/partest/ScaladocModelTest.scala | 10 +++ test/scaladoc/run/SI-5235.check | 4 + test/scaladoc/run/SI-5235.scala | 87 ++++++++++++++++++++++ 8 files changed, 140 insertions(+), 22 deletions(-) create mode 100644 test/scaladoc/run/SI-5235.check create mode 100644 test/scaladoc/run/SI-5235.scala diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala index b545140c4a..b2d6800ebb 100755 --- a/src/compiler/scala/tools/nsc/ast/DocComments.scala +++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala @@ -457,22 +457,16 @@ trait DocComments { self: Global => case List() => NoType case site :: sites1 => select(site.thisType, name, findIn(sites1)) } - val (classes, pkgs) = site.ownerChain.span(!_.isPackageClass) - findIn(classes ::: List(pkgs.head, rootMirror.RootClass)) + // Previously, searching was taking place *only* in the current package and in the root package + // now we're looking for it everywhere in the hierarchy, so we'll be able to link variable expansions like + // immutable.Seq in package immutable + //val (classes, pkgs) = site.ownerChain.span(!_.isPackageClass) + //val sites = (classes ::: List(pkgs.head, rootMirror.RootClass))) + //findIn(sites) + findIn(site.ownerChain ::: List(definitions.EmptyPackage)) } - def getType(_str: String, variable: String): Type = { - /* - * work around the backticks issue suggested by Simon in - * https://groups.google.com/forum/?hl=en&fromgroups#!topic/scala-internals/z7s1CCRCz74 - * ideally, we'd have a removeWikiSyntax method in the CommentFactory to completely eliminate the wiki markup - */ - val str = - if (_str.length >= 2 && _str.startsWith("`") && _str.endsWith("`")) - _str.substring(1, _str.length - 2) - else - _str - + def getType(str: String, variable: String): Type = { def getParts(start: Int): List[String] = { val end = skipIdent(str, start) if (end == start) List() @@ -484,7 +478,7 @@ trait DocComments { self: Global => val parts = getParts(0) if (parts.isEmpty) { reporter.error(comment.codePos, "Incorrect variable expansion for " + variable + " in use case. Does the " + - "variable expand to wiki syntax when documenting " + site + "?") + "variable expand to wiki syntax when documenting " + site + "?") return ErrorType } val partnames = (parts.init map newTermName) :+ newTypeName(parts.last) @@ -498,17 +492,36 @@ trait DocComments { self: Global => case _ => (getSite(partnames.head), partnames.tail) } - (start /: rest)(select(_, _, NoType)) + val result = (start /: rest)(select(_, _, NoType)) + if (result == NoType) + reporter.warning(comment.codePos, "Could not find the type " + variable + " points to while expanding it " + + "for the usecase signature of " + sym + " in " + site + "." + + "In this context, " + variable + " = \"" + str + "\".") + result + } + + /** + * work around the backticks issue suggested by Simon in + * https://groups.google.com/forum/?hl=en&fromgroups#!topic/scala-internals/z7s1CCRCz74 + * ideally, we'd have a removeWikiSyntax method in the CommentFactory to completely eliminate the wiki markup + */ + def cleanupVariable(str: String) = { + val tstr = str.trim + if (tstr.length >= 2 && tstr.startsWith("`") && tstr.endsWith("`")) + tstr.substring(1, tstr.length - 1) + else + tstr } val aliasExpansions: List[Type] = for (alias <- aliases) yield lookupVariable(alias.name.toString.substring(1), site) match { case Some(repl) => - val tpe = getType(repl.trim, alias.name.toString) + val repl2 = cleanupVariable(repl) + val tpe = getType(repl2, alias.name.toString) if (tpe != NoType) tpe else { - val alias1 = alias.cloneSymbol(rootMirror.RootClass, alias.rawflags, newTypeName(repl)) + val alias1 = alias.cloneSymbol(rootMirror.RootClass, alias.rawflags, newTypeName(repl2)) typeRef(NoPrefix, alias1, Nil) } case None => diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index a570cd74d6..2d277603ee 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1972,7 +1972,10 @@ trait Typers extends Modes with Adaptations with Tags { case SilentResultValue(tpt) => val alias = enclClass.newAliasType(name.toTypeName, useCase.pos) val tparams = cloneSymbolsAtOwner(tpt.tpe.typeSymbol.typeParams, alias) - alias setInfo typeFun(tparams, appliedType(tpt.tpe, tparams map (_.tpe))) + /* Unless we treat no-tparams usecases differently they blow up in typeFun + * def typeFun = PolyType(tparams, tpe) // <- which asserts (!tparams.isEmpty) */ + val newInfo = if (tparams.isEmpty) tpt.tpe else typeFun(tparams, appliedType(tpt.tpe, tparams map (_.tpe))) + alias setInfo newInfo context.scope.enter(alias) case _ => } diff --git a/src/library/scala/collection/immutable/List.scala b/src/library/scala/collection/immutable/List.scala index 6fd8d143ee..74dc385f99 100644 --- a/src/library/scala/collection/immutable/List.scala +++ b/src/library/scala/collection/immutable/List.scala @@ -62,6 +62,7 @@ import java.io._ * section on `Lists` for more information. * * @define coll list + * @define Coll `List` * @define thatinfo the class of the returned collection. In the standard library configuration, * `That` is always `List[B]` because an implicit of type `CanBuildFrom[List, B, That]` * is defined in object `List`. @@ -96,7 +97,7 @@ sealed abstract class List[+A] extends AbstractSeq[A] * * @usecase def ::(x: A): List[A] * @inheritdoc - * + * * Example: * {{{1 :: List(2, 3) = List(2, 3).::(1) = List(1, 2, 3)}}} */ diff --git a/src/library/scala/collection/immutable/StringOps.scala b/src/library/scala/collection/immutable/StringOps.scala index 633821ecea..7e60cc7195 100644 --- a/src/library/scala/collection/immutable/StringOps.scala +++ b/src/library/scala/collection/immutable/StringOps.scala @@ -25,7 +25,7 @@ import mutable.StringBuilder * @param repr the actual representation of this string operations object. * * @since 2.8 - * @define Coll `StringOps` + * @define Coll `String` * @define coll string */ final class StringOps(override val repr: String) extends AnyVal with StringLike[String] { diff --git a/src/library/scala/collection/mutable/ArrayOps.scala b/src/library/scala/collection/mutable/ArrayOps.scala index 7a595f211d..21c2aaaec7 100644 --- a/src/library/scala/collection/mutable/ArrayOps.scala +++ b/src/library/scala/collection/mutable/ArrayOps.scala @@ -30,7 +30,7 @@ import parallel.mutable.ParArray * * @tparam T type of the elements contained in this array. * - * @define Coll `ArrayOps` + * @define Coll `Array` * @define orderDependent * @define orderDependentFold * @define mayNotTerminateInf diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala index de5354d4a0..be70a91e14 100644 --- a/src/partest/scala/tools/partest/ScaladocModelTest.scala +++ b/src/partest/scala/tools/partest/ScaladocModelTest.scala @@ -12,6 +12,7 @@ import scala.tools.nsc.util.CommandLineParser import scala.tools.nsc.doc.{Settings, DocFactory, Universe} import scala.tools.nsc.doc.model._ import scala.tools.nsc.reporters.ConsoleReporter +import scala.tools.nsc.doc.model.comment.Comment /** A class for testing scaladoc model generation * - you need to specify the code in the `code` method @@ -142,5 +143,14 @@ abstract class ScaladocModelTest extends DirectTest { case _ => sys.error("Error getting " + expl + ": " + list.length + " elements with this name. " + "All elements in list: [" + list.mkString(", ") + "]") } + + def extractCommentText(c: Comment) = { + def extractText(body: Any): String = body match { + case s: String => s + case p: Product => p.productIterator.toList.map(extractText(_)).mkString + case _ => "" + } + extractText(c.body) + } } } diff --git a/test/scaladoc/run/SI-5235.check b/test/scaladoc/run/SI-5235.check new file mode 100644 index 0000000000..d9acfd063b --- /dev/null +++ b/test/scaladoc/run/SI-5235.check @@ -0,0 +1,4 @@ +newSource:10: warning: Could not find the type $Coll points to while expanding it for the usecase signature of method reverse in trait SpecificColl.In this context, $Coll = "BullSh". + * @usecase def reverse(): $Coll + ^ +Done. diff --git a/test/scaladoc/run/SI-5235.scala b/test/scaladoc/run/SI-5235.scala new file mode 100644 index 0000000000..cae70fd0a5 --- /dev/null +++ b/test/scaladoc/run/SI-5235.scala @@ -0,0 +1,87 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.nsc.doc.model.diagram._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def code = """ + package scala.test.scaladoc.SI5235 { + trait Builder[From, To] + + /** + * @define Coll `GenericColl` + */ + class GenericColl { + /** + * @usecase def reverse(): $Coll + * Returns the reversed $Coll. + */ + def reverse[T](implicit something: Builder[GenericColl, T]): T + def foo1: GenericColl = ??? + } + + /** Nooo, don't point to this */ + trait MyCollection + + package specific { + /** + * @define Coll `BullSh` + */ + trait SpecificColl extends GenericColl { + def foo2: SpecificColl = ??? + } + } + + package mycoll { + /** + * @define Coll `mycoll.MyCollection` + */ + class MyCollection extends specific.SpecificColl { + def foo3: MyCollection = ??? + } + } + } + """ + + // diagrams must be started. In case there's an error with dot, it should not report anything + def scaladocSettings = "" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("SI5235") + + val GenericColl = base._class("GenericColl") + val SpecificColl = base._package("specific")._trait("SpecificColl") + val MyCollection = base._package("mycoll")._class("MyCollection") + + // check comment text + val gcComment = extractCommentText(GenericColl._method("reverse").comment.get) + val scComment = extractCommentText(SpecificColl._method("reverse").comment.get) + val mcComment = extractCommentText(MyCollection._method("reverse").comment.get) + assert(gcComment.contains("Returns the reversed GenericColl."), + gcComment + ".contains(\"Returns the reversed GenericColl.\")") + assert(scComment.contains("Returns the reversed BullSh."), + scComment + ".contains(\"Returns the reversed BullSh.\")") + assert(mcComment.contains("Returns the reversed mycoll.MyCollection."), + mcComment + ".contains(\"Returns the reversed mycoll.MyCollection.\")") + + // check signatures + val gcReverse = GenericColl._method("reverse") + val scReverse = SpecificColl._method("reverse") + val mcReverse = MyCollection._method("reverse") + val gcReverseType = gcReverse.resultType + val scReverseType = scReverse.resultType + val mcReverseType = mcReverse.resultType + assert(gcReverseType.name == "GenericColl", gcReverseType.name + " == GenericColl") + assert(scReverseType.name == "BullSh", scReverseType.name + " == BullSh") + assert(mcReverseType.name == "MyCollection",mcReverseType.name + " == MyCollection") + assert(gcReverseType.refEntity(0)._1 == GenericColl, + gcReverse.qualifiedName + "'s return type has a link to " + GenericColl.qualifiedName) + assert(scReverseType.refEntity.isEmpty, + scReverse.qualifiedName + "'s return type does not have links") + assert(mcReverseType.refEntity(0)._1 == MyCollection, + mcReverse.qualifiedName + "'s return type has a link to " + MyCollection.qualifiedName) + } +} \ No newline at end of file -- cgit v1.2.3 From f916434c119289773e5aad88c633c30f68a12e14 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Sat, 16 Jun 2012 04:23:43 +0200 Subject: SI-3314 SI-4888 Scaladoc: Relative type prefixes And adds support for linking to class members, only usable from the model factory now, so no links to members from the doc comment yet, sorry. But it fixes the Enumeration problem once and for all! Also corrected the inTpl for members obtained by implicit conversions, so they're in the correct template and the comment variable expansion is done from the correct (but different) template. Review by @kzys. --- .../scala/tools/nsc/doc/html/HtmlFactory.scala | 2 +- .../scala/tools/nsc/doc/html/HtmlPage.scala | 10 +- src/compiler/scala/tools/nsc/doc/html/Page.scala | 2 +- .../scala/tools/nsc/doc/html/page/Index.scala | 4 +- .../tools/nsc/doc/html/page/IndexScript.scala | 4 +- .../scala/tools/nsc/doc/html/page/Template.scala | 42 +----- .../html/page/diagram/DotDiagramGenerator.scala | 2 +- .../scala/tools/nsc/doc/model/Entity.scala | 17 ++- .../scala/tools/nsc/doc/model/ModelFactory.scala | 150 ++++++++++++++++----- .../doc/model/ModelFactoryImplicitSupport.scala | 27 ++-- .../scala/tools/nsc/doc/model/TypeEntity.scala | 5 +- .../nsc/doc/model/diagram/DiagramFactory.scala | 2 +- .../scala/tools/partest/ScaladocModelTest.scala | 26 +++- test/scaladoc/resources/SI-3314.scala | 70 ++++++++++ test/scaladoc/resources/SI_4676.scala | 4 - test/scaladoc/resources/Trac3484.scala | 27 ---- test/scaladoc/run/SI-3314.check | 1 + test/scaladoc/run/SI-3314.scala | 74 ++++++++++ test/scaladoc/run/SI-3484.check | 1 + test/scaladoc/run/SI-3484.scala | 52 +++++++ test/scaladoc/run/SI-4676.check | 1 + test/scaladoc/run/SI-4676.scala | 26 ++++ test/scaladoc/run/SI-5235.scala | 6 +- test/scaladoc/run/implicits-var-exp.scala | 35 +++-- test/scaladoc/scalacheck/HtmlFactoryTest.scala | 73 +++------- 25 files changed, 459 insertions(+), 204 deletions(-) create mode 100644 test/scaladoc/resources/SI-3314.scala delete mode 100644 test/scaladoc/resources/SI_4676.scala delete mode 100644 test/scaladoc/resources/Trac3484.scala create mode 100644 test/scaladoc/run/SI-3314.check create mode 100644 test/scaladoc/run/SI-3314.scala create mode 100644 test/scaladoc/run/SI-3484.check create mode 100644 test/scaladoc/run/SI-3484.scala create mode 100644 test/scaladoc/run/SI-4676.check create mode 100644 test/scaladoc/run/SI-4676.scala diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala index 51c5793d46..18cc65092b 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala @@ -138,7 +138,7 @@ class HtmlFactory(val universe: doc.Universe, index: doc.Index) { if (!(written contains tpl)) { writeForThis(new page.Template(universe, diagramGenerator, tpl)) written += tpl - tpl.templates map writeTemplate + tpl.templates collect { case d: DocTemplateEntity => d } map writeTemplate } } diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala index 4a1a8cf898..af5e90083e 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala @@ -155,12 +155,18 @@ abstract class HtmlPage extends Page { thisPage => def toLinksIn(inPos: Int, starts: List[Int]): NodeSeq = { val (tpl, width) = tpe.refEntity(inPos) (tpl match { - case dtpl:DocTemplateEntity if hasLinks => + case LinkToTpl(dtpl:DocTemplateEntity) if hasLinks => { string.slice(inPos, inPos + width) } - case tpl => + case LinkToTpl(tpl) => { string.slice(inPos, inPos + width) } + case LinkToMember(mbr, inTpl) if hasLinks => + { + string.slice(inPos, inPos + width) + } + case LinkToMember(mbr, inTpl) => + { string.slice(inPos, inPos + width) } }) ++ toLinksOut(inPos + width, starts.tail) } if (hasLinks) diff --git a/src/compiler/scala/tools/nsc/doc/html/Page.scala b/src/compiler/scala/tools/nsc/doc/html/Page.scala index 5e3ab87ccd..08df26e745 100644 --- a/src/compiler/scala/tools/nsc/doc/html/Page.scala +++ b/src/compiler/scala/tools/nsc/doc/html/Page.scala @@ -48,7 +48,7 @@ abstract class Page { * @param generator The generator that is writing this page. */ def writeFor(site: HtmlFactory): Unit - def docEntityKindToString(ety: DocTemplateEntity) = + def docEntityKindToString(ety: TemplateEntity) = if (ety.isTrait) "trait" else if (ety.isCaseClass) "case class" else if (ety.isClass) "class" diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala index 0e894a03bf..ba48bf3f9b 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala @@ -61,7 +61,9 @@ class Index(universe: doc.Universe, index: doc.Index) extends HtmlPage { }
    { val tpls: Map[String, Seq[DocTemplateEntity]] = - (pack.templates filter (t => !t.isPackage && !universe.settings.hardcoded.isExcluded(t.qualifiedName) )) groupBy (_.name) + (pack.templates collect { + case t: DocTemplateEntity if !t.isPackage && !universe.settings.hardcoded.isExcluded(t.qualifiedName) => t + }) groupBy (_.name) val placeholderSeq: NodeSeq =
    diff --git a/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala b/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala index 2b68ac2937..e1ab479f9d 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala @@ -62,7 +62,9 @@ class IndexScript(universe: doc.Universe, index: doc.Index) extends Page { def allPackagesWithTemplates = { Map(allPackages.map((key) => { - key -> key.templates.filter(t => !t.isPackage && !universe.settings.hardcoded.isExcluded(t.qualifiedName)) + key -> key.templates.collect { + case t: DocTemplateEntity if !t.isPackage && !universe.settings.hardcoded.isExcluded(t.qualifiedName) => t + } }) : _*) } } diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index 0d0410c7e2..47834e542c 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -238,44 +238,12 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp } - def boundsToString(hi: Option[TypeEntity], lo: Option[TypeEntity]): String = { - def bound0(bnd: Option[TypeEntity], pre: String): String = bnd match { - case None => "" - case Some(tpe) => pre ++ tpe.toString - } - bound0(hi, "<:") ++ bound0(lo, ">:") - } - - def tparamsToString(tpss: List[TypeParam]): String = { - if (tpss.isEmpty) "" else { - def tparam0(tp: TypeParam): String = - tp.variance + tp.name + boundsToString(tp.hi, tp.lo) - def tparams0(tpss: List[TypeParam]): String = (tpss: @unchecked) match { - case tp :: Nil => tparam0(tp) - case tp :: tps => tparam0(tp) ++ ", " ++ tparams0(tps) - } - "[" + tparams0(tpss) + "]" - } - } - - def defParamsToString(d: MemberEntity with Def): String = { - val paramLists: List[String] = - if (d.valueParams.isEmpty) Nil - else d.valueParams map (ps => ps map (_.resultType.name) mkString ("(",",",")")) - - tparamsToString(d.typeParams) + paramLists.mkString - } - def memberToHtml(mbr: MemberEntity, inTpl: DocTemplateEntity): NodeSeq = { - val defParamsString = mbr match { - case d:MemberEntity with Def => defParamsToString(d) - case _ => "" - } val memberComment = memberToCommentHtml(mbr, inTpl, false)
  1. - + { signature(mbr, false) } { memberComment }
  2. @@ -650,6 +618,7 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp def kindToString(mbr: MemberEntity): String = { mbr match { case tpl: DocTemplateEntity => docEntityKindToString(tpl) + case tpl: NoDocTemplateMemberEntity => docEntityKindToString(tpl) case ctor: Constructor => "new" case tme: MemberEntity => ( if (tme.isDef) "def" @@ -850,18 +819,13 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp val link = relativeLinkTo(mbr) myXml ++= {str.substring(from, to)} case mbr: MemberEntity => - val anchor = "#" + mbr.name + defParamsString(mbr) + ":" + mbr.resultType.name + val anchor = "#" + mbr.signature val link = relativeLinkTo(mbr.inTemplate) myXml ++= {str.substring(from, to)} } index = to } } - // function used in the MemberEntity case above - def defParamsString(mbr: Entity):String = mbr match { - case d:MemberEntity with Def => defParamsToString(d) - case _ => "" - } if (index <= length-1) myXml ++= codeStringToXml(str.substring(index, length )) diff --git a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala index dc6f941c30..8648fdb0a1 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala @@ -74,7 +74,7 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { def textTypeEntity(text: String) = new TypeEntity { val name = text - def refEntity: SortedMap[Int, (TemplateEntity, Int)] = SortedMap() + def refEntity: SortedMap[Int, (LinkTo, Int)] = SortedMap() } // it seems dot chokes on node names over 8000 chars, so let's limit the size of the string diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index 2901daafd6..4ab77b356b 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -53,6 +53,9 @@ trait Entity { /** The kind of the entity */ def kind: String + + /** Whether or not the template was defined in a package object */ + def inPackageObject: Boolean } object Entity { @@ -91,9 +94,6 @@ trait TemplateEntity extends Entity { /** Whether this template is a case class. */ def isCaseClass: Boolean - /** Whether or not the template was defined in a package object */ - def inPackageObject: Boolean - /** The self-type of this template, if it differs from the template type. */ def selfType : Option[TypeEntity] @@ -183,7 +183,11 @@ trait MemberEntity extends Entity { /** If this member originates from an implicit conversion, we set the implicit information to the correct origin */ def byConversion: Option[ImplicitConversion] + + /** The identity of this member, used for linking */ + def signature: String } + object MemberEntity { // Oh contravariance, contravariance, wherefore art thou contravariance? // Note: the above works for both the commonly misunderstood meaning of the line and the real one. @@ -205,7 +209,10 @@ trait NoDocTemplate extends TemplateEntity { def kind = "" } -/** TODO: Document */ +/** An inherited template that was not documented in its original owner - example: + * in classpath: trait T { class C } -- T (and implicitly C) are not documented + * in the source: trait U extends T -- C appears in U as a NoDocTemplateMemberImpl + * -- that is, U has a member for it but C doesn't get its own page */ trait NoDocTemplateMemberEntity extends TemplateEntity with MemberEntity { def kind = "" } @@ -253,7 +260,7 @@ trait DocTemplateEntity extends TemplateEntity with MemberEntity { /** All templates that are members of this template. If this template is a package, only templates for which * documentation is available in the universe (`DocTemplateEntity`) are listed. */ - def templates: List[DocTemplateEntity] + def templates: List[TemplateEntity with MemberEntity] /** All methods that are members of this template. */ def methods: List[Def] diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 9fa6619e9f..2efbfbe43c 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -83,6 +83,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def toRoot: List[EntityImpl] = this :: inTpl.toRoot def qualifiedName = name def annotations = sym.annotations.map(makeAnnotation) + def inPackageObject: Boolean = sym.owner.isModuleClass && sym.owner.sourceModule.isPackageObject } trait TemplateImpl extends EntityImpl with TemplateEntity { @@ -96,11 +97,25 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def isRootPackage = false def ownType = makeType(sym.tpe, this) def selfType = if (sym.thisSym eq sym) None else Some(makeType(sym.thisSym.typeOfThis, this)) - def inPackageObject: Boolean = sym.owner.isModuleClass && sym.owner.sourceModule.isPackageObject } abstract class MemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl) extends EntityImpl(sym, inTpl) with MemberEntity { - lazy val comment = if (inTpl != null) thisFactory.comment(sym, inTpl) else None + lazy val comment = { + val commentTpl = + /* Variable precendence order for implicitly added members: Take the variable defifinitions from ... + * 1. the target of the implicit conversion + * 2. the definition template (owner) + * 3. the current template + */ + if (implConv != null) findTemplateMaybe(implConv.toType.typeSymbol) match { + case Some(d) if d != makeRootPackage => d //in case of NoSymbol, it will give us the root package + case _ => findTemplateMaybe(sym.owner) match { + case Some(d) if d != makeRootPackage => d //in case of NoSymbol, it will give us the root package + case _ => inTpl + } + } else inTpl + if (commentTpl != null) thisFactory.comment(sym, commentTpl) else None + } override def inTemplate = inTpl override def toRoot: List[MemberImpl] = this :: inTpl.toRoot def inDefinitionTemplates = this match { @@ -189,6 +204,30 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { sym.isAbstractClass || sym.isAbstractType) && !sym.isSynthetic def isTemplate = false def byConversion = if (implConv ne null) Some(implConv) else None + lazy val signature = { + + val defParamsString = this match { + case d: MemberEntity with Def => + val paramLists: List[String] = + if (d.valueParams.isEmpty) Nil + else d.valueParams map (ps => ps map (_.resultType.name) mkString ("(",",",")")) + + val tParams = if (d.typeParams.isEmpty) "" else { + def boundsToString(hi: Option[TypeEntity], lo: Option[TypeEntity]): String = { + def bound0(bnd: Option[TypeEntity], pre: String): String = bnd match { + case None => "" + case Some(tpe) => pre ++ tpe.toString + } + bound0(hi, "<:") ++ bound0(lo, ">:") + } + "[" + d.typeParams.map(tp => tp.variance + tp.name + boundsToString(tp.hi, tp.lo)).mkString(", ") + "]" + } + + tParams + paramLists.mkString + case _ => "" + } + name + defParamsString +":"+ resultType.name + } } /** A template that is not documented at all. The class is instantiated during lookups, to indicate that the class @@ -198,7 +237,6 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { assert(modelFinished) assert(!(noDocTemplatesCache isDefinedAt sym)) noDocTemplatesCache += (sym -> this) - def isDocTemplate = false } @@ -209,7 +247,8 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { */ class NoDocTemplateMemberImpl(sym: Symbol, inTpl: DocTemplateImpl) extends MemberImpl(sym, null, inTpl) with TemplateImpl with HigherKindedImpl with NoDocTemplateMemberEntity { assert(modelFinished) - + // no templates cache for this class, each owner gets its own instance + override def isTemplate = true def isDocTemplate = false lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name) } @@ -330,7 +369,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // all the members that are documentented PLUS the members inherited by implicit conversions var members: List[MemberImpl] = ownMembers - def templates = members collect { case c: DocTemplateEntity => c } + def templates = members collect { case c: TemplateEntity with MemberEntity => c } def methods = members collect { case d: Def => d } def values = members collect { case v: Val => v } def abstractTypes = members collect { case t: AbstractType => t } @@ -348,7 +387,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { case _ => } - members :::= memberSymsLazy.map(modelCreation.createLazyTemplateMember(_, inTpl)) + members :::= memberSymsLazy.map(modelCreation.createLazyTemplateMember(_, this)) // compute linearization to register subclasses linearization @@ -411,9 +450,14 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { abstract class NonTemplateMemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl) extends MemberImpl(sym, implConv, inTpl) with NonTemplateMemberEntity { override def qualifiedName = optimize(inTemplate.qualifiedName + "#" + name) - lazy val definitionName = - if (implConv == null) optimize(inDefinitionTemplates.head.qualifiedName + "#" + name) - else optimize(implConv.conversionQualifiedName + "#" + name) + lazy val definitionName = { + // this contrived name is here just to satisfy some older tests -- if you decide to remove it, be my guest, and + // also remove property("package object") from test/scaladoc/scalacheck/HtmlFactoryTest.scala so you don't break + // the test suite... + val packageObject = if (inPackageObject) ".package" else "" + if (implConv == null) optimize(inDefinitionTemplates.head.qualifiedName + packageObject + "#" + name) + else optimize(implConv.conversionQualifiedName + packageObject + "#" + name) + } def isUseCase = sym.isSynthetic def isBridge = sym.isBridge } @@ -461,7 +505,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { case ObjectClass => normalizeTemplate(AnyRefClass) case _ if aSym.isPackageObject => - aSym + normalizeTemplate(aSym.owner) case _ if aSym.isModuleClass => normalizeTemplate(aSym.sourceModule) case _ => @@ -501,6 +545,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } def createDocTemplate(bSym: Symbol, inTpl: DocTemplateImpl): DocTemplateImpl = { + assert(!modelFinished) // only created BEFORE the model is finished if (bSym.isModule || (bSym.isAliasType && bSym.tpe.typeSymbol.isModule)) new DocTemplateImpl(bSym, inTpl) with Object else if (bSym.isTrait || (bSym.isAliasType && bSym.tpe.typeSymbol.isTrait)) @@ -566,6 +611,13 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { * - NoDocTemplateEntity (created in makeTemplate) */ def createLazyTemplateMember(aSym: Symbol, inTpl: DocTemplateImpl): MemberImpl = { + + // Code is duplicate because the anonymous classes are created statically + def createNoDocMemberTemplate(bSym: Symbol, inTpl: DocTemplateImpl): NoDocTemplateMemberImpl = { + assert(modelFinished) // only created AFTER the model is finished + new NoDocTemplateMemberImpl(bSym, inTpl) + } + assert(modelFinished) val bSym = normalizeTemplate(aSym) @@ -579,7 +631,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { mbrs.head case _ => // move the class completely to the new location - new NoDocTemplateMemberImpl(aSym, inTpl) + createNoDocMemberTemplate(bSym, inTpl) } } } @@ -690,7 +742,9 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { docTemplatesCache.get(normalizeTemplate(aSym)) } - def makeTemplate(aSym: Symbol): TemplateImpl = { + def makeTemplate(aSym: Symbol): TemplateImpl = makeTemplate(aSym, None) + + def makeTemplate(aSym: Symbol, inTpl: Option[TemplateImpl]): TemplateImpl = { assert(modelFinished) def makeNoDocTemplate(aSym: Symbol, inTpl: TemplateImpl): NoDocTemplateImpl = { @@ -706,7 +760,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { dtpl case None => val bSym = normalizeTemplate(aSym) - makeNoDocTemplate(bSym, makeTemplate(bSym.owner)) + makeNoDocTemplate(bSym, if (inTpl.isDefined) inTpl.get else makeTemplate(bSym.owner)) } } @@ -797,10 +851,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { /** Get the types of the parents of the current class, ignoring the refinements */ def makeParentTypes(aType: Type, tpl: Option[DocTemplateImpl], inTpl: TemplateImpl): List[(TemplateEntity, TypeEntity)] = aType match { case RefinedType(parents, defs) => - val ignoreParents = Set[Symbol](AnyRefClass, ObjectClass) + val ignoreParents = Set[Symbol](AnyClass, AnyRefClass, ObjectClass) val filtParents = // we don't want to expose too many links to AnyRef, that will just be redundant information - if (tpl.isDefined && (!tpl.get.isObject && parents.length < 2)) + if (tpl.isDefined && { val sym = tpl.get.sym; (!sym.isModule && parents.length < 2) || (sym == AnyValClass) || (sym == AnyRefClass) || (sym == AnyClass) }) parents else parents.filterNot((p: Type) => ignoreParents(p.typeSymbol)) @@ -819,7 +873,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def createTypeEntity = new TypeEntity { private val nameBuffer = new StringBuilder - private var refBuffer = new immutable.TreeMap[Int, (TemplateEntity, Int)] + private var refBuffer = new immutable.TreeMap[Int, (LinkTo, Int)] private def appendTypes0(types: List[Type], sep: String): Unit = types match { case Nil => case tp :: Nil => @@ -862,15 +916,56 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // if (!preSym.printWithoutPrefix) { // nameBuffer append stripPrefixes.foldLeft(pre.prefixString)(_ stripPrefix _) // } + + // SI-3314/SI-4888: Classes, Traits and Types can be inherited from a template to another: + // class Enum { abstract class Value } + // class Day extends Enum { object Mon extends Value /*...*/ } + // ===> in such cases we have two options: + // (0) if there's no inheritance taking place (Enum#Value) we can link to the template directly + // (1) if we generate the doc template for Day, we can link to the correct member + // (2) if we don't generate the doc template, we should at least indicate the correct prefix in the tooltip val bSym = normalizeTemplate(aSym) - if (bSym.isNonClassType && bSym != AnyRefClass) { - nameBuffer append bSym.decodedName - } else { - val tpl = makeTemplate(bSym) - val pos0 = nameBuffer.length - refBuffer += pos0 -> (tpl, tpl.name.length) - nameBuffer append tpl.name - } + val owner = + if ((preSym != NoSymbol) && /* it needs a prefix */ + (preSym != bSym.owner) && /* prefix is different from owner */ + // ((preSym != inTpl.sym) && /* prevent prefixes from being shown inside the defining class */ + // (preSym != inTpl.sym.moduleClass)) && /* or object */ + (aSym == bSym)) /* normalization doesn't play tricks on us */ + preSym + else + bSym.owner + + val bTpl = findTemplateMaybe(bSym) + val link = + if (owner == bSym.owner && bTpl.isDefined) + // (0) the owner's class is linked AND has a template - lovely + LinkToTpl(bTpl.get) + else { + val oTpl = findTemplateMaybe(owner) + val bMbr = oTpl.map(findMember(bSym, _)) + if (oTpl.isDefined && bMbr.isDefined && bMbr.get.isDefined) + // (1) the owner's class + LinkToMember(bMbr.get.get, oTpl.get) //ugh + else + // (2) if we still couldn't find the owner, make a noDocTemplate for everything (in the correct owner!) + LinkToTpl(makeTemplate(bSym, Some(makeTemplate(owner)))) + } + + // TODO: The name might include a prefix, take care of that! + val name = bSym.nameString + val pos0 = nameBuffer.length + refBuffer += pos0 -> ((link, name.length)) + nameBuffer append name + + // if (bSym.isNonClassType && bSym != AnyRefClass) { + // nameBuffer append bSym.decodedName + // } else { + // val tpl = makeTemplate(bSym) + // val pos0 = nameBuffer.length + // refBuffer += pos0 -> ((LinkToTpl(tpl), tpl.name.length)) + // nameBuffer append tpl.name + // } + if (!targs.isEmpty) { nameBuffer append '[' appendTypes0(targs, ", ") @@ -949,12 +1044,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { * (1) sourceModule * (2) you get out of owners with .owner */ - normalizeTemplate(aSym) match { - case bSym if bSym.isPackageObject => - normalizeOwner(bSym.owner) - case bSym => - bSym - } + normalizeTemplate(aSym) def inOriginalOnwer(aSym: Symbol, inTpl: TemplateImpl): Boolean = normalizeOwner(aSym.owner) == normalizeOwner(inTpl.sym) diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index 8cbf2ac1b6..493ad3d831 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -408,25 +408,14 @@ trait ModelFactoryImplicitSupport { debug("") memberSyms.flatMap({ aSym => - makeTemplate(aSym.owner) match { - case d: DocTemplateImpl => - // we can't just pick up nodes from the previous template, although that would be very convenient: - // they need the byConversion field to be attached to themselves -- this is design decision I should - // revisit soon - // - // d.ownMembers.collect({ - // // it's either a member or has a couple of usecases it's hidden behind - // case m: MemberImpl if m.sym == aSym => - // m // the member itself - // case m: MemberImpl if m.useCaseOf.isDefined && m.useCaseOf.get.asInstanceOf[MemberImpl].sym == aSym => - // m.useCaseOf.get.asInstanceOf[MemberImpl] // the usecase - // }) - makeMember(aSym, this, d) - case _ => - // should only happen if the code for this template is not part of the scaladoc run => - // members won't have any comments - makeMember(aSym, this, inTpl) - } + // we can't just pick up nodes from the original template, although that would be very convenient: + // they need the byConversion field to be attached to themselves and the types to be transformed by + // asSeenFrom + + // at the same time, the member itself is in the inTpl, not in the new template -- but should pick up + // variables from the old template. Ugly huh? We'll always create the member inTpl, but it will change + // the template when expanding variables in the comment :) + makeMember(aSym, this, inTpl) }) } diff --git a/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala b/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala index 67e955f613..a16e99bf06 100644 --- a/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala @@ -9,6 +9,9 @@ package model import scala.collection._ +abstract sealed class LinkTo +case class LinkToTpl(tpl: TemplateEntity) extends LinkTo +case class LinkToMember(mbr: MemberEntity, inTpl: DocTemplateEntity) extends LinkTo /** A type. Note that types and templates contain the same information only for the simplest types. For example, a type * defines how a template's type parameters are instantiated (as in `List[Cow]`), what the template's prefix is @@ -21,7 +24,7 @@ abstract class TypeEntity { /** Maps which parts of this type's name reference entities. The map is indexed by the position of the first * character that reference some entity, and contains the entity and the position of the last referenced * character. The referenced character ranges do not to overlap or nest. The map is sorted by position. */ - def refEntity: SortedMap[Int, (TemplateEntity, Int)] + def refEntity: SortedMap[Int, (LinkTo, Int)] /** The human-readable representation of this type. */ override def toString = name diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala index 1a8ad193aa..4b05da98cd 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala @@ -32,7 +32,7 @@ trait DiagramFactory extends DiagramDirectiveParser { def normalNode(sym: Symbol) = NormalNode(makeTemplate(sym).ownType, Some(makeTemplate(sym))) def aggregationNode(text: String) = - NormalNode(new TypeEntity { val name = text; val refEntity = SortedMap[Int, (TemplateEntity, Int)]() }, None) + NormalNode(new TypeEntity { val name = text; val refEntity = SortedMap[Int, (LinkTo, Int)]() }, None) /** Create the inheritance diagram for this template */ def makeInheritanceDiagram(tpl: DocTemplateImpl): Option[Diagram] = { diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala index be70a91e14..c89dd2cb8f 100644 --- a/src/partest/scala/tools/partest/ScaladocModelTest.scala +++ b/src/partest/scala/tools/partest/ScaladocModelTest.scala @@ -103,13 +103,22 @@ abstract class ScaladocModelTest extends DirectTest { class TemplateAccess(tpl: DocTemplateEntity) { def _class(name: String): DocTemplateEntity = getTheFirst(_classes(name), tpl.qualifiedName + ".class(" + name + ")") - def _classes(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case c: Class => c}) + def _classes(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case c: DocTemplateEntity with Class => c}) + + def _classMbr(name: String): NoDocTemplateMemberEntity = getTheFirst(_classesMbr(name), tpl.qualifiedName + ".classMember(" + name + ")") + def _classesMbr(name: String): List[NoDocTemplateMemberEntity] = tpl.templates.filter(_.name == name).collect({ case c: NoDocTemplateMemberEntity if c.isClass => c}) def _trait(name: String): DocTemplateEntity = getTheFirst(_traits(name), tpl.qualifiedName + ".trait(" + name + ")") - def _traits(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case t: Trait => t}) + def _traits(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case t: DocTemplateEntity with Trait => t}) + + def _traitMbr(name: String): NoDocTemplateMemberEntity = getTheFirst(_traitsMbr(name), tpl.qualifiedName + ".traitMember(" + name + ")") + def _traitsMbr(name: String): List[NoDocTemplateMemberEntity] = tpl.templates.filter(_.name == name).collect({ case t: NoDocTemplateMemberEntity if t.isTrait => t}) def _object(name: String): DocTemplateEntity = getTheFirst(_objects(name), tpl.qualifiedName + ".object(" + name + ")") - def _objects(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case o: Object => o}) + def _objects(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case o: DocTemplateEntity with Object => o}) + + def _objectMbr(name: String): NoDocTemplateMemberEntity = getTheFirst(_objectsMbr(name), tpl.qualifiedName + ".objectMember(" + name + ")") + def _objectsMbr(name: String): List[NoDocTemplateMemberEntity] = tpl.templates.filter(_.name == name).collect({ case o: NoDocTemplateMemberEntity if o.isObject => o}) def _method(name: String): Def = getTheFirst(_methods(name), tpl.qualifiedName + ".method(" + name + ")") def _methods(name: String): List[Def] = tpl.methods.filter(_.name == name) @@ -119,6 +128,12 @@ abstract class ScaladocModelTest extends DirectTest { def _conversion(name: String): ImplicitConversion = getTheFirst(_conversions(name), tpl.qualifiedName + ".conversion(" + name + ")") def _conversions(name: String): List[ImplicitConversion] = tpl.conversions.filter(_.conversionQualifiedName == name) + + def _absType(name: String): MemberEntity = getTheFirst(_methods(name), tpl.qualifiedName + ".abstractType(" + name + ")") + def _absTypes(name: String): List[MemberEntity] = tpl.members.filter(mbr => mbr.name == name && mbr.isAbstractType) + + def _aliasType(name: String): MemberEntity = getTheFirst(_methods(name), tpl.qualifiedName + ".aliasType(" + name + ")") + def _aliasTypes(name: String): List[MemberEntity] = tpl.members.filter(mbr => mbr.name == name && mbr.isAliasType) } class PackageAccess(pack: Package) extends TemplateAccess(pack) { @@ -141,7 +156,10 @@ abstract class ScaladocModelTest extends DirectTest { case 1 => list.head case 0 => sys.error("Error getting " + expl + ": No such element.") case _ => sys.error("Error getting " + expl + ": " + list.length + " elements with this name. " + - "All elements in list: [" + list.mkString(", ") + "]") + "All elements in list: [" + list.map({ + case ent: Entity => ent.kind + " " + ent.qualifiedName + case other => other.toString + }).mkString(", ") + "]") } def extractCommentText(c: Comment) = { diff --git a/test/scaladoc/resources/SI-3314.scala b/test/scaladoc/resources/SI-3314.scala new file mode 100644 index 0000000000..e5773a4970 --- /dev/null +++ b/test/scaladoc/resources/SI-3314.scala @@ -0,0 +1,70 @@ +package scala.test.scaladoc { + + package test1 { + class Enum { + abstract class Value + class Val extends Value + def Value(): Value = new Val + } + + object Constants extends Enum { + def a = Value + } + } + + package test2 { + trait WeekDayTrait extends Enumeration { + type WeekDay = Value + val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value + } + + class WeekDayClass extends Enumeration { + type WeekDay = Value + val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value + } + + object WeekDayObject extends Enumeration { + type WeekDay = Value + val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value + } + + object UserObject { + def isWorkingDay1(d: scala.test.scaladoc.test2.WeekDayClass#Value) = false + def isWorkingDay2(d: scala.test.scaladoc.test2.WeekDayClass#WeekDay) = false + def isWorkingDay3(d: scala.test.scaladoc.test2.WeekDayTrait#Value) = false + def isWorkingDay4(d: scala.test.scaladoc.test2.WeekDayTrait#WeekDay) = false + def isWorkingDay5(d: scala.test.scaladoc.test2.WeekDayObject.Value) = false + def isWorkingDay6(d: scala.test.scaladoc.test2.WeekDayObject.WeekDay) = false + import WeekDayObject._ + def isWorkingDay7(d: Value) = ! (d == Sat || d == Sun) + def isWorkingDay8(d: WeekDay) = ! (d == Sat || d == Sun) + def isWorkingDay9(d: WeekDayObject.Value) = ! (d == Sat || d == Sun) + } + + class UserClass { + def isWorkingDay1(d: scala.test.scaladoc.test2.WeekDayClass#Value) = false + def isWorkingDay2(d: scala.test.scaladoc.test2.WeekDayClass#WeekDay) = false + def isWorkingDay3(d: scala.test.scaladoc.test2.WeekDayTrait#Value) = false + def isWorkingDay4(d: scala.test.scaladoc.test2.WeekDayTrait#WeekDay) = false + def isWorkingDay5(d: scala.test.scaladoc.test2.WeekDayObject.Value) = false + def isWorkingDay6(d: scala.test.scaladoc.test2.WeekDayObject.WeekDay) = false + import WeekDayObject._ + def isWorkingDay7(d: Value) = ! (d == Sat || d == Sun) + def isWorkingDay8(d: WeekDay) = ! (d == Sat || d == Sun) + def isWorkingDay9(d: WeekDayObject.Value) = ! (d == Sat || d == Sun) + } + + trait UserTrait { + def isWorkingDay1(d: scala.test.scaladoc.test2.WeekDayClass#Value) = false + def isWorkingDay2(d: scala.test.scaladoc.test2.WeekDayClass#WeekDay) = false + def isWorkingDay3(d: scala.test.scaladoc.test2.WeekDayTrait#Value) = false + def isWorkingDay4(d: scala.test.scaladoc.test2.WeekDayTrait#WeekDay) = false + def isWorkingDay5(d: scala.test.scaladoc.test2.WeekDayObject.Value) = false + def isWorkingDay6(d: scala.test.scaladoc.test2.WeekDayObject.WeekDay) = false + import WeekDayObject._ + def isWorkingDay7(d: Value) = ! (d == Sat || d == Sun) + def isWorkingDay8(d: WeekDay) = ! (d == Sat || d == Sun) + def isWorkingDay9(d: WeekDayObject.Value) = ! (d == Sat || d == Sun) + } + } +} diff --git a/test/scaladoc/resources/SI_4676.scala b/test/scaladoc/resources/SI_4676.scala deleted file mode 100644 index 00c0fc7ea9..0000000000 --- a/test/scaladoc/resources/SI_4676.scala +++ /dev/null @@ -1,4 +0,0 @@ -class SI_4676 { - type SS = (String,String) - def x(ss: SS): Int = 3 -} diff --git a/test/scaladoc/resources/Trac3484.scala b/test/scaladoc/resources/Trac3484.scala deleted file mode 100644 index 9656ec268d..0000000000 --- a/test/scaladoc/resources/Trac3484.scala +++ /dev/null @@ -1,27 +0,0 @@ -class cbf[A, B, C] - -/** - * @define Coll Traversable - * @define bfreturn $Coll - */ -class Collection[A] { - /** What map does... - * - * $bfreturn - * @usecase def map[B](f: A => B): $bfreturn[B] - * - */ - def map[B, That](f: A => B)(implicit fact: cbf[Collection[A], B, That]) = - null -} - -/** - * @define b John - * @define a Mister $b - */ -class SR704 { - /** - * Hello $a. - */ - def foo = 123 -} diff --git a/test/scaladoc/run/SI-3314.check b/test/scaladoc/run/SI-3314.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/SI-3314.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/SI-3314.scala b/test/scaladoc/run/SI-3314.scala new file mode 100644 index 0000000000..665223098a --- /dev/null +++ b/test/scaladoc/run/SI-3314.scala @@ -0,0 +1,74 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def resourceFile = "SI-3314.scala" + + // no need for special settings + def scaladocSettings = "" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + // just need to check the member exists, access methods will throw an error if there's a problem + val base = rootPackage._package("scala")._package("test")._package("scaladoc") + + val test1 = base._package("test1") + val test1Value = test1._class("Enum")._method("Value").resultType + assert(test1Value.name == "Value", test1Value.name + " == Value") + assert(test1Value.refEntity.size == 1, test1Value.refEntity.size + " == 1") + + val test1Constants = test1._object("Constants")._method("a").resultType + assert(test1Constants.name == "Value", test1Constants.name + " == Value") + assert(test1Constants.refEntity.size == 1, test1Constants.refEntity.size + " == 1") + assert(test1Constants.refEntity(0)._1 == LinkToMember(test1._object("Constants")._class("Value"), test1._object("Constants")), + test1Constants.refEntity(0)._1 + " == LinkToMember(test1.Enum.Value)") + + val test2 = base._package("test2") + def testDefinition(doc: DocTemplateEntity) = { + for (day <- List("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")) { + assert(doc._value(day).resultType.name == "Value", + doc._value(day).resultType.name + " == Value") + assert(doc._value(day).resultType.refEntity.size == 1, + doc._value(day).resultType.refEntity.size + " == 1") + assert(doc._value(day).resultType.refEntity(0)._1 == LinkToMember(doc._classMbr("Value"), doc), + doc._value(day).resultType.refEntity(0)._1 + " == LinkToMember(" + doc.qualifiedName + ".Value)") + } + } + testDefinition(test2._trait("WeekDayTrait")) + testDefinition(test2._class("WeekDayClass")) + testDefinition(test2._object("WeekDayObject")) + + def testUsage(doc: DocTemplateEntity) = { + val ValueInClass = test2._class("WeekDayClass")._classMbr("Value") + val ValueInTrait = test2._trait("WeekDayTrait")._classMbr("Value") + val ValueInObject = test2._object("WeekDayObject")._classMbr("Value") + val WeekDayInObject = test2._object("WeekDayObject")._member("WeekDay") + + val expected = List( + ("isWorkingDay1", "Value", ValueInClass), + ("isWorkingDay2", "Value", ValueInClass), + ("isWorkingDay3", "Value", ValueInTrait), + ("isWorkingDay4", "Value", ValueInTrait), + ("isWorkingDay5", "Value", ValueInObject), + ("isWorkingDay6", "WeekDay", WeekDayInObject), + ("isWorkingDay7", "Value", ValueInObject), + ("isWorkingDay8", "WeekDay", WeekDayInObject), + ("isWorkingDay9", "Value", ValueInObject)) + + for ((method, name, ref) <- expected) { + assert(doc._method(method).valueParams(0)(0).resultType.name == name, + doc._method(method).valueParams(0)(0).resultType.name + " == " + name + " (in " + doc + "." + method + ")") + assert(doc._method(method).valueParams(0)(0).resultType.refEntity.size == 1, + doc._method(method).valueParams(0)(0).resultType.refEntity.size + " == " + 1 + " (in " + doc + "." + method + ")") + assert(doc._method(method).valueParams(0)(0).resultType.refEntity(0)._1 == LinkToMember(ref, ref.inTemplate), + doc._method(method).valueParams(0)(0).resultType.refEntity(0)._1 + " == LinkToMember(" + ref.qualifiedName + ") (in " + doc + "." + method + ")") + } + } + testUsage(test2._object("UserObject")) + testUsage(test2._class("UserClass")) + testUsage(test2._trait("UserTrait")) + } +} \ No newline at end of file diff --git a/test/scaladoc/run/SI-3484.check b/test/scaladoc/run/SI-3484.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/SI-3484.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/SI-3484.scala b/test/scaladoc/run/SI-3484.scala new file mode 100644 index 0000000000..297aebee8f --- /dev/null +++ b/test/scaladoc/run/SI-3484.scala @@ -0,0 +1,52 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.nsc.doc.model.diagram._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def code = """ + class cbf[A, B, C] + + /** + * @define Coll Collection + * @define bfreturn $Coll + */ + class Collection[A] { + /** What map does... + * + * $bfreturn + * @usecase def map[B](f: A => B): $bfreturn[B] + * + */ + def map[B, That](f: A => B)(implicit fact: cbf[Collection[A], B, That]) = + null + } + + /** + * @define b John + * @define a Mister $b + */ + class SR704 { + /** + * Hello $a. + */ + def foo = 123 + } + """ + + // diagrams must be started. In case there's an error with dot, it should not report anything + def scaladocSettings = "" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + // check correct expansion of the use case signature + val map = rootPackage._class("Collection")._method("map") + assert(map.resultType.name == "Collection[B]", map.resultType.name + " == Traversable[B]") + + val foo = rootPackage._class("SR704")._method("foo") + assert(extractCommentText(foo.comment.get).contains("Hello Mister John."), + extractCommentText(foo.comment.get) + ".contains(Hello Mister John.)") + } +} \ No newline at end of file diff --git a/test/scaladoc/run/SI-4676.check b/test/scaladoc/run/SI-4676.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/SI-4676.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/SI-4676.scala b/test/scaladoc/run/SI-4676.scala new file mode 100644 index 0000000000..b83a59a472 --- /dev/null +++ b/test/scaladoc/run/SI-4676.scala @@ -0,0 +1,26 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.nsc.doc.model.diagram._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def code = """ + class SI_4676 { + type SS = (String,String) + def x(ss: SS): Int = 3 + } + class cbf[A, B, C] + """ + + // diagrams must be started. In case there's an error with dot, it should not report anything + def scaladocSettings = "" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + // check correct expansion of the use case signature + val x = rootPackage._class("SI_4676")._method("x") + assert(x.valueParams(0)(0).resultType.name == "(String, String)", "parameter ss of method x has type (String, String") + } +} \ No newline at end of file diff --git a/test/scaladoc/run/SI-5235.scala b/test/scaladoc/run/SI-5235.scala index cae70fd0a5..f0c6e1cf17 100644 --- a/test/scaladoc/run/SI-5235.scala +++ b/test/scaladoc/run/SI-5235.scala @@ -77,11 +77,11 @@ object Test extends ScaladocModelTest { assert(gcReverseType.name == "GenericColl", gcReverseType.name + " == GenericColl") assert(scReverseType.name == "BullSh", scReverseType.name + " == BullSh") assert(mcReverseType.name == "MyCollection",mcReverseType.name + " == MyCollection") - assert(gcReverseType.refEntity(0)._1 == GenericColl, + assert(gcReverseType.refEntity(0)._1 == LinkToTpl(GenericColl), gcReverse.qualifiedName + "'s return type has a link to " + GenericColl.qualifiedName) - assert(scReverseType.refEntity.isEmpty, + assert(!scReverseType.refEntity(0)._1.asInstanceOf[LinkToTpl].tpl.isDocTemplate, scReverse.qualifiedName + "'s return type does not have links") - assert(mcReverseType.refEntity(0)._1 == MyCollection, + assert(mcReverseType.refEntity(0)._1 == LinkToTpl(MyCollection), mcReverse.qualifiedName + "'s return type has a link to " + MyCollection.qualifiedName) } } \ No newline at end of file diff --git a/test/scaladoc/run/implicits-var-exp.scala b/test/scaladoc/run/implicits-var-exp.scala index 16569fe3c2..94d2990d29 100644 --- a/test/scaladoc/run/implicits-var-exp.scala +++ b/test/scaladoc/run/implicits-var-exp.scala @@ -6,25 +6,36 @@ object Test extends ScaladocModelTest { override def code = """ package scala.test.scaladoc.variable.expansion { - /** - * Blah blah blah - */ + /** @define coll WROOOONG-A */ class A object A { import language.implicitConversions - implicit def aToB(a: A) = new B + implicit def aToC(a: A) = new C + implicit def aToE(a: A) = new E with F } - /** - * @define coll collection - */ + /** @define coll WROOOONG-B */ class B { - /** - * foo returns a $coll - */ + /** foo returns a $coll */ def foo: Nothing = ??? } + + /** @define coll collection */ + class C extends B + + /** @define coll WROOOONG-D */ + trait D { + /** bar returns a $coll */ + def bar: Nothing = ??? + } + + /** @define coll result */ + //trait E { self: D => override def bar: Nothing = ??? } + trait E extends D { override def bar: Nothing = ??? } + + /** @define coll WROOOONG-F */ + trait F } """ @@ -37,7 +48,9 @@ object Test extends ScaladocModelTest { val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("variable")._package("expansion") val foo = base._class("A")._method("foo") - assert(foo.comment.get.body.toString.contains("foo returns a collection"), "\"" + foo.comment.get.body.toString + "\".contains(\"foo returns a collection\")") + + val bar = base._class("A")._method("bar") + assert(bar.comment.get.body.toString.contains("bar returns a result"), "\"" + bar.comment.get.body.toString + "\".contains(\"bar returns a result\")") } } \ No newline at end of file diff --git a/test/scaladoc/scalacheck/HtmlFactoryTest.scala b/test/scaladoc/scalacheck/HtmlFactoryTest.scala index 5b6f75426e..13eacf79a5 100644 --- a/test/scaladoc/scalacheck/HtmlFactoryTest.scala +++ b/test/scaladoc/scalacheck/HtmlFactoryTest.scala @@ -235,30 +235,6 @@ object Test extends Properties("HtmlFactory") { } } - property("Trac #3484") = { - val files = createTemplates("Trac3484.scala") - - files("Collection.html") match { - case node: scala.xml.Node => { - val s = node.toString - s.contains(""": Traversable[B]""") - } - case _ => false - } - } - - property("Trac #3484 - SR704") = { - val files = createTemplates("Trac3484.scala") - - files("SR704.html") match { - case node: scala.xml.Node => { - val s = node.toString - s.contains("Hello Mister John.") - } - case _ => false - } - } - property("Trac #4325 - files") = { val files = createTemplates("Trac4325.scala") @@ -303,7 +279,7 @@ object Test extends Properties("HtmlFactory") { case _ => false } } - // + // // property("Trac #484 - refinements and existentials") = { // val files = createTemplates("Trac484.scala") // val lines = """ @@ -315,7 +291,7 @@ object Test extends Properties("HtmlFactory") { // |def j(x: Int): Bar // |def k(): AnyRef { type Dingus <: T forSome { type T <: String } } // """.stripMargin.trim.lines map (_.trim) - // + // // files("RefinementAndExistentials.html") match { // case node: scala.xml.Node => { // val s = node.text.replaceAll("\\s+", " ") @@ -397,26 +373,17 @@ object Test extends Properties("HtmlFactory") { } } - property("Should decode symbolic type alias name.") = { + property("SI-4714: Should decode symbolic type alias name.") = { createTemplate("SI_4715.scala") match { case node: scala.xml.Node => { val html = node.toString - html.contains(">: :+:[<") - } - case _ => false - } - } - - property("Shouldn't drop type arguments to aliased tuple.") = { - createTemplate("SI_4676.scala") match { - case node: scala.xml.Node => { - node.toString.contains(">ss: (String, String)<") + html.contains(">:+:<") } case _ => false } } - property("Default arguments of synthesized constructor") = { + property("SI-4287: Default arguments of synthesized constructor") = { val files = createTemplates("SI_4287.scala") files("ClassWithSugar.html") match { @@ -427,7 +394,7 @@ object Test extends Properties("HtmlFactory") { } } - property("Default arguments of synthesized constructor") = { + property("SI-4507: Default arguments of synthesized constructor") = { createTemplate("SI_4507.scala") match { case node: scala.xml.Node => ! node.toString.contains("
  3. returns silently when evaluating true and true
  4. ") @@ -435,40 +402,40 @@ object Test extends Properties("HtmlFactory") { } } - property("Use cases and links should not crash scaladoc") = { + property("SI-4898: Use cases and links should not crash scaladoc") = { createTemplate("SI_4898.scala") true } - property("Use cases should override their original members") = + property("SI-5054: Use cases should override their original members") = checkText("SI_5054_q1.scala")( (None,"""def test(): Int""", true) //Disabled because the full signature is now displayed //(None,"""def test(implicit lost: Int): Int""", false) ) - property("Use cases should keep their flags - final should not be lost") = + property("SI-5054: Use cases should keep their flags - final should not be lost") = checkText("SI_5054_q2.scala")((None, """final def test(): Int""", true)) - property("Use cases should keep their flags - implicit should not be lost") = + property("SI-5054: Use cases should keep their flags - implicit should not be lost") = checkText("SI_5054_q3.scala")((None, """implicit def test(): Int""", true)) - property("Use cases should keep their flags - real abstract should not be lost") = + property("SI-5054: Use cases should keep their flags - real abstract should not be lost") = checkText("SI_5054_q4.scala")((None, """abstract def test(): Int""", true)) - property("Use cases should keep their flags - traits should not be affected") = + property("SI-5054: Use cases should keep their flags - traits should not be affected") = checkText("SI_5054_q5.scala")((None, """def test(): Int""", true)) - property("Use cases should keep their flags - traits should not be affected") = + property("SI-5054: Use cases should keep their flags - traits should not be affected") = checkText("SI_5054_q6.scala")((None, """abstract def test(): Int""", true)) - property("Use case individual signature test") = + property("SI-5054: Use case individual signature test") = checkText("SI_5054_q7.scala")( (None, """abstract def test2(explicit: Int): Int [use case] This takes the explicit value passed.""", true), (None, """abstract def test1(): Int [use case] This takes the implicit value in scope.""", true) ) - property("Display correct \"Definition classes\"") = + property("SI-5287: Display correct \"Definition classes\"") = checkText("SI_5287.scala")( (None, """def method(): Int @@ -477,7 +444,7 @@ object Test extends Properties("HtmlFactory") { Definition Classes SI_5287 SI_5287_B SI_5287_A""", true) ) // the explanation appears twice, as small comment and full comment - property("Correct comment inheritance for overriding") = + property("Comment inheritance: Correct comment inheritance for overriding") = checkText("implicit-inheritance-override.scala")( (Some("Base"), """def function[T](arg1: T, arg2: String): Double @@ -521,7 +488,7 @@ object Test extends Properties("HtmlFactory") { ) for (useCaseFile <- List("UseCaseInheritance", "UseCaseOverrideInheritance")) { - property("Correct comment inheritance for usecases") = + property("Comment inheritance: Correct comment inheritance for usecases") = checkText("implicit-inheritance-usecase.scala")( (Some(useCaseFile), """def missing_arg[T](arg1: T): Double @@ -588,7 +555,7 @@ object Test extends Properties("HtmlFactory") { ) } - property("Correct explicit inheritance for override") = + property("Comment inheritance: Correct explicit inheritance for override") = checkText("explicit-inheritance-override.scala")( (Some("InheritDocDerived"), """def function[T](arg1: T, arg2: String): Double @@ -614,7 +581,7 @@ object Test extends Properties("HtmlFactory") { See also StartSee The Manual EndSee """, true)) - property("Correct explicit inheritance for usecase") = + property("Comment inheritance: Correct explicit inheritance for usecase") = checkText("explicit-inheritance-usecase.scala")( (Some("UseCaseInheritDoc"), """def function[T](arg1: T, arg2: String): Double @@ -639,7 +606,7 @@ object Test extends Properties("HtmlFactory") { See also StartSee The Manual EndSee """, true)) - property("Correct explicit inheritance in corner cases") = + property("Comment inheritance: Correct explicit inheritance in corner cases") = checkText("inheritdoc-corner-cases.scala")( (Some("D"), """def hello1: Int -- cgit v1.2.3 From f881249ba19084b194c0db07fd36ab2a68af5228 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Thu, 12 Jul 2012 14:08:34 +0200 Subject: Scaladoc: Inherited templates in diagrams Related to SI-3314, where we started showing inherited templates that were normally not documented. This patch corrects a problem in parentTypes that was preventing inherited templates from being displayed in diagrams. Also renamed: PackageDiagram => ContentDiagram ClassDiagram => InheritanceDiagram which should have been done much earlier --- .../scala/tools/nsc/doc/html/page/Template.scala | 2 +- .../html/page/diagram/DotDiagramGenerator.scala | 12 ++-- .../scala/tools/nsc/doc/model/ModelFactory.scala | 22 +++++- .../tools/nsc/doc/model/diagram/Diagram.scala | 16 ++--- .../nsc/doc/model/diagram/DiagramFactory.scala | 16 ++--- test/scaladoc/resources/SI-3314-diagrams.scala | 78 ++++++++++++++++++++++ test/scaladoc/run/SI-3314-diagrams.check | 1 + test/scaladoc/run/SI-3314-diagrams.scala | 37 ++++++++++ test/scaladoc/run/diagrams-base.scala | 6 +- 9 files changed, 163 insertions(+), 27 deletions(-) create mode 100644 test/scaladoc/resources/SI-3314-diagrams.scala create mode 100644 test/scaladoc/run/SI-3314-diagrams.check create mode 100644 test/scaladoc/run/SI-3314-diagrams.scala diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index 47834e542c..bba838ddcf 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -769,7 +769,7 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp mbr match { case dte: DocTemplateEntity if !isSelf => -

    { inside(hasLinks = false, nameLink = relativeLinkTo(dte)) }

    +

    { inside(hasLinks = true, nameLink = relativeLinkTo(dte)) }

    case _ if isSelf =>

    { inside(hasLinks = true) }

    case _ => diff --git a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala index 8648fdb0a1..59560befc9 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala @@ -25,7 +25,7 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { // maps an index to its corresponding node private var index2Node: Map[Int, Node] = null // true if the current diagram is a class diagram - private var isClassDiagram = false + private var isInheritanceDiagram = false // incoming implicit nodes (needed for determining the CSS class of a node) private var incomingImplicitNodes: List[Node] = List() // the suffix used when there are two many classes to show @@ -66,10 +66,10 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { var superClasses = List[Node]() var incomingImplicits = List[Node]() var outgoingImplicits = List[Node]() - isClassDiagram = false + isInheritanceDiagram = false d match { - case ClassDiagram(_thisNode, _superClasses, _subClasses, _incomingImplicits, _outgoingImplicits) => + case InheritanceDiagram(_thisNode, _superClasses, _subClasses, _incomingImplicits, _outgoingImplicits) => def textTypeEntity(text: String) = new TypeEntity { @@ -108,7 +108,7 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { nodes = List() edges = (thisNode -> superClasses) :: subClasses.map(_ -> List(thisNode)) node2Index = (thisNode::subClasses:::superClasses:::incomingImplicits:::outgoingImplicits).zipWithIndex.toMap - isClassDiagram = true + isInheritanceDiagram = true incomingImplicitNodes = incomingImplicits case _ => nodes = d.nodes @@ -119,7 +119,7 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { index2Node = node2Index map {_.swap} val implicitsDot = { - if (!isClassDiagram) "" + if (!isInheritanceDiagram) "" else { // dot cluster containing thisNode val thisCluster = "subgraph clusterThis {\n" + @@ -360,7 +360,7 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { private def transform(e:scala.xml.Node): scala.xml.Node = e match { // add an id and class attribute to the SVG element case Elem(prefix, "svg", attribs, scope, child @ _*) => { - val klass = if (isClassDiagram) "class-diagram" else "package-diagram" + val klass = if (isInheritanceDiagram) "class-diagram" else "package-diagram" Elem(prefix, "svg", attribs, scope, child map(x => transform(x)) : _*) % new UnprefixedAttribute("id", "graph" + counter, Null) % new UnprefixedAttribute("class", klass, Null) diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 2efbfbe43c..61b4267f3c 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -858,8 +858,28 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { parents else parents.filterNot((p: Type) => ignoreParents(p.typeSymbol)) + + /** Returns: + * - a DocTemplate if the type's symbol is documented + * - a NoDocTemplateMember if the type's symbol is not documented in its parent but in another template + * - a NoDocTemplate if the type's symbol is not documented at all */ + def makeTemplateOrMemberTemplate(parent: Type): TemplateImpl = { + def noDocTemplate = makeTemplate(parent.typeSymbol) + findTemplateMaybe(parent.typeSymbol) match { + case Some(tpl) => tpl + case None => parent match { + case TypeRef(pre, sym, args) => + findTemplateMaybe(pre.typeSymbol) match { + case Some(tpl) => findMember(parent.typeSymbol, tpl).collect({case t: TemplateImpl => t}).getOrElse(noDocTemplate) + case None => noDocTemplate + } + case _ => noDocTemplate + } + } + } + filtParents.map(parent => { - val templateEntity = makeTemplate(parent.typeSymbol) + val templateEntity = makeTemplateOrMemberTemplate(parent) val typeEntity = makeType(parent, inTpl) (templateEntity, typeEntity) }) diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala index 8527ca4039..2b804ca10f 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala @@ -13,18 +13,18 @@ import model._ abstract class Diagram { def nodes: List[Node] def edges: List[(Node, List[Node])] - def isPackageDiagram = false - def isClassDiagram = false + def isContentDiagram = false // Implemented by ContentDiagram + def isInheritanceDiagram = false // Implemented by InheritanceDiagram def depthInfo: DepthInfo } -case class PackageDiagram(nodes:List[/*Class*/Node], edges:List[(Node, List[Node])]) extends Diagram { - override def isPackageDiagram = true - lazy val depthInfo = new PackageDiagramDepth(this) +case class ContentDiagram(nodes:List[/*Class*/Node], edges:List[(Node, List[Node])]) extends Diagram { + override def isContentDiagram = true + lazy val depthInfo = new ContentDiagramDepth(this) } /** A class diagram */ -case class ClassDiagram(thisNode: ThisNode, +case class InheritanceDiagram(thisNode: ThisNode, superClasses: List[/*Class*/Node], subClasses: List[/*Class*/Node], incomingImplicits: List[ImplicitNode], @@ -33,7 +33,7 @@ case class ClassDiagram(thisNode: ThisNode, def edges = (thisNode -> (superClasses ::: outgoingImplicits)) :: (subClasses ::: incomingImplicits).map(_ -> List(thisNode)) - override def isClassDiagram = true + override def isInheritanceDiagram = true lazy val depthInfo = new DepthInfo { def maxDepth = 3 def nodeDepth(node: Node) = @@ -115,7 +115,7 @@ case class OutsideNode(tpe: TypeEntity, tpl: Option[TemplateEntity], tooltip: Op // Computing and offering node depth information -class PackageDiagramDepth(pack: PackageDiagram) extends DepthInfo { +class ContentDiagramDepth(pack: ContentDiagram) extends DepthInfo { private[this] var _maxDepth = 0 private[this] var _nodeDepth = Map[Node, Int]() private[this] var seedNodes = Set[Node]() diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala index 4b05da98cd..731801b143 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala @@ -93,7 +93,7 @@ trait DiagramFactory extends DiagramDirectiveParser { val filteredImplicitOutgoingNodes = if (diagramFilter.hideOutgoingImplicits) Nil else outgoingImplicitNodes // final diagram filter - filterDiagram(ClassDiagram(thisNode, filteredSuperclasses.reverse, filteredSubclasses.reverse, filteredIncomingImplicits, filteredImplicitOutgoingNodes), diagramFilter) + filterDiagram(InheritanceDiagram(thisNode, filteredSuperclasses.reverse, filteredSubclasses.reverse, filteredIncomingImplicits, filteredImplicitOutgoingNodes), diagramFilter) } tModel += System.currentTimeMillis @@ -173,9 +173,9 @@ trait DiagramFactory extends DiagramDirectiveParser { val anyRefSubtypes = Nil val allAnyRefTypes = aggregationNode("All AnyRef subtypes") val nullTemplate = makeTemplate(NullClass) - PackageDiagram(allAnyRefTypes::nodes, (mapNodes(nullTemplate), allAnyRefTypes::anyRefSubtypes)::edges.filterNot(_._1.tpl == Some(nullTemplate))) + ContentDiagram(allAnyRefTypes::nodes, (mapNodes(nullTemplate), allAnyRefTypes::anyRefSubtypes)::edges.filterNot(_._1.tpl == Some(nullTemplate))) } else - PackageDiagram(nodes, edges) + ContentDiagram(nodes, edges) filterDiagram(diagram, diagramFilter) } @@ -200,10 +200,10 @@ trait DiagramFactory extends DiagramDirectiveParser { else { // Final diagram, with the filtered nodes and edges diagram match { - case ClassDiagram(thisNode, _, _, _, _) if diagramFilter.hideNode(thisNode) => + case InheritanceDiagram(thisNode, _, _, _, _) if diagramFilter.hideNode(thisNode) => None - case ClassDiagram(thisNode, superClasses, subClasses, incomingImplicits, outgoingImplicits) => + case InheritanceDiagram(thisNode, superClasses, subClasses, incomingImplicits, outgoingImplicits) => def hideIncoming(node: Node): Boolean = diagramFilter.hideNode(node) || diagramFilter.hideEdge(node, thisNode) @@ -214,13 +214,13 @@ trait DiagramFactory extends DiagramDirectiveParser { // println(thisNode) // println(superClasses.map(cl => "super: " + cl + " " + hideOutgoing(cl)).mkString("\n")) // println(subClasses.map(cl => "sub: " + cl + " " + hideIncoming(cl)).mkString("\n")) - Some(ClassDiagram(thisNode, + Some(InheritanceDiagram(thisNode, superClasses.filterNot(hideOutgoing(_)), subClasses.filterNot(hideIncoming(_)), incomingImplicits.filterNot(hideIncoming(_)), outgoingImplicits.filterNot(hideOutgoing(_)))) - case PackageDiagram(nodes0, edges0) => + case ContentDiagram(nodes0, edges0) => // Filter out all edges that: // (1) are sources of hidden classes // (2) are manually hidden by the user @@ -242,7 +242,7 @@ trait DiagramFactory extends DiagramDirectiveParser { val sourceNodes = edges.map(_._1) val sinkNodes = edges.map(_._2).flatten val nodes = (sourceNodes ::: sinkNodes).distinct - Some(PackageDiagram(nodes, edges)) + Some(ContentDiagram(nodes, edges)) } } diff --git a/test/scaladoc/resources/SI-3314-diagrams.scala b/test/scaladoc/resources/SI-3314-diagrams.scala new file mode 100644 index 0000000000..b80a97b522 --- /dev/null +++ b/test/scaladoc/resources/SI-3314-diagrams.scala @@ -0,0 +1,78 @@ +package scala.test.scaladoc { + + /** Check the interaction between SI-3314 and diagrams + * - the three enumerations below should get valid content diagrams: + * Value + * __________/|\__________ + * / / / | \ \ \ + * Mon Tue Wed Thu Fri Sat Sun + * + * - each member should receive an inhertiance diagram: + * Value + * | + * | + * {Mon,Tue,Wed,Thu,Fri,Sat,Sun} + */ + package diagrams { + + /** @contentDiagram + * @inheritanceDiagram hideDiagram */ + trait WeekDayTraitWithDiagram extends Enumeration { + type WeekDay = Value + /** @inheritanceDiagram */ + object Mon extends WeekDay + /** @inheritanceDiagram */ + object Tue extends WeekDay + /** @inheritanceDiagram */ + object Wed extends WeekDay + /** @inheritanceDiagram */ + object Thu extends WeekDay + /** @inheritanceDiagram */ + object Fri extends WeekDay + /** @inheritanceDiagram */ + object Sat extends WeekDay + /** @inheritanceDiagram */ + object Sun extends WeekDay + } + + /** @contentDiagram + * @inheritanceDiagram hideDiagram */ + class WeekDayClassWithDiagram extends Enumeration { + type WeekDay = Value + /** @inheritanceDiagram */ + object Mon extends WeekDay + /** @inheritanceDiagram */ + object Tue extends WeekDay + /** @inheritanceDiagram */ + object Wed extends WeekDay + /** @inheritanceDiagram */ + object Thu extends WeekDay + /** @inheritanceDiagram */ + object Fri extends WeekDay + /** @inheritanceDiagram */ + object Sat extends WeekDay + /** @inheritanceDiagram */ + object Sun extends WeekDay + } + + /** @contentDiagram + * @inheritanceDiagram hideDiagram */ + object WeekDayObjectWithDiagram extends Enumeration { + type WeekDay = Value + /** @inheritanceDiagram */ + object Mon extends WeekDay + /** @inheritanceDiagram */ + object Tue extends WeekDay + /** @inheritanceDiagram */ + object Wed extends WeekDay + /** @inheritanceDiagram */ + object Thu extends WeekDay + /** @inheritanceDiagram */ + object Fri extends WeekDay + /** @inheritanceDiagram */ + object Sat extends WeekDay + /** @inheritanceDiagram */ + object Sun extends WeekDay + } + } +} \ No newline at end of file diff --git a/test/scaladoc/run/SI-3314-diagrams.check b/test/scaladoc/run/SI-3314-diagrams.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/SI-3314-diagrams.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/SI-3314-diagrams.scala b/test/scaladoc/run/SI-3314-diagrams.scala new file mode 100644 index 0000000000..0b07e4c5bd --- /dev/null +++ b/test/scaladoc/run/SI-3314-diagrams.scala @@ -0,0 +1,37 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.nsc.doc.model.diagram._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def resourceFile = "SI-3314-diagrams.scala" + + // no need for special settings + def scaladocSettings = "-diagrams" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + // just need to check the member exists, access methods will throw an error if there's a problem + val base = rootPackage._package("scala")._package("test")._package("scaladoc") + + val diagrams = base._package("diagrams") + def testDiagram(doc: DocTemplateEntity, diag: Option[Diagram], nodes: Int, edges: Int) = { + assert(diag.isDefined, doc.qualifiedName + " diagram missing") + assert(diag.get.nodes.length == nodes, + doc.qualifiedName + "'s diagram: node count " + diag.get.nodes.length + " == " + nodes) + assert(diag.get.edges.length == edges, + doc.qualifiedName + "'s diagram: edge count " + diag.get.edges.length + " == " + edges) + } + + val templates = List(diagrams._trait("WeekDayTraitWithDiagram"), diagrams._class("WeekDayClassWithDiagram"), diagrams._object("WeekDayObjectWithDiagram")) + + for (template <- templates) { + testDiagram(template, template.contentDiagram, 8, 7) + val subtemplates = List("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun").map(template._object(_)) + for (subtemplate <- subtemplates) + testDiagram(subtemplate, subtemplate.inheritanceDiagram, 2, 1) + } + } +} \ No newline at end of file diff --git a/test/scaladoc/run/diagrams-base.scala b/test/scaladoc/run/diagrams-base.scala index 38bed06502..b7aeed51d2 100644 --- a/test/scaladoc/run/diagrams-base.scala +++ b/test/scaladoc/run/diagrams-base.scala @@ -42,7 +42,7 @@ object Test extends ScaladocModelTest { assert(diag.nodes.filter(_.isThisNode).length == 1) // 1. check class E diagram - assert(diag.isClassDiagram) + assert(diag.isInheritanceDiagram) val (incoming, outgoing) = diag.edges.partition(!_._1.isThisNode) assert(incoming.length == 5) @@ -56,14 +56,14 @@ object Test extends ScaladocModelTest { assert(incomingSubclass.length == 2) assert(incomingImplicit.length == 3) - val classDiag = diag.asInstanceOf[ClassDiagram] + val classDiag = diag.asInstanceOf[InheritanceDiagram] assert(classDiag.incomingImplicits.length == 3) assert(classDiag.outgoingImplicits.length == 1) // 2. check package diagram // NOTE: Z should be eliminated because it's isolated val packDiag = base.contentDiagram.get - assert(packDiag.isPackageDiagram) + assert(packDiag.isContentDiagram) assert(packDiag.nodes.length == 8) // check singular object removal assert(packDiag.edges.length == 4) assert(packDiag.edges.foldLeft(0)(_ + _._2.length) == 6) -- cgit v1.2.3 From 8291106ec985fbe3ee4b8863ccf238dc1d42af58 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Mon, 2 Jul 2012 16:52:58 +0200 Subject: SI-5965 Scaladoc crash This bug was fixed by the model upgrade in c11427c1. Test case confirmation. --- test/scaladoc/run/SI-5965.check | 1 + test/scaladoc/run/SI-5965.scala | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 test/scaladoc/run/SI-5965.check create mode 100644 test/scaladoc/run/SI-5965.scala diff --git a/test/scaladoc/run/SI-5965.check b/test/scaladoc/run/SI-5965.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/SI-5965.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/SI-5965.scala b/test/scaladoc/run/SI-5965.scala new file mode 100644 index 0000000000..6f4540d239 --- /dev/null +++ b/test/scaladoc/run/SI-5965.scala @@ -0,0 +1,24 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def code = """ + abstract class Param + class Test + object Test { + def apply(i: Int): Test = new Test + def apply(i: Int, p: Param = new Param { }): Test = new Test + } + """ + + // no need for special settings + def scaladocSettings = "" + + def testModel(rootPackage: Package) = { + import access._ + + // just need to make sure the model exists + val base = rootPackage._object("Test") + } +} \ No newline at end of file -- cgit v1.2.3 From 242c2fc94766f4dd8b7f1f88ad055f0f62d2e109 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Mon, 2 Jul 2012 17:46:42 +0200 Subject: SI-5558 Package object members indexing This bug was fixed by the model upgrade in c11427c1. Test case confirmation. --- src/compiler/scala/tools/nsc/doc/html/page/Index.scala | 2 +- test/scaladoc/resources/SI-5558.scala | 6 ++++++ test/scaladoc/scalacheck/IndexTest.scala | 13 ++++++++++--- 3 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 test/scaladoc/resources/SI-5558.scala diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala index ba48bf3f9b..81fbed884d 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala @@ -14,7 +14,7 @@ import scala.collection._ import scala.xml._ import scala.util.parsing.json.{JSONObject, JSONArray} -class Index(universe: doc.Universe, index: doc.Index) extends HtmlPage { +class Index(universe: doc.Universe, val index: doc.Index) extends HtmlPage { def path = List("index.html") diff --git a/test/scaladoc/resources/SI-5558.scala b/test/scaladoc/resources/SI-5558.scala new file mode 100644 index 0000000000..6523438b86 --- /dev/null +++ b/test/scaladoc/resources/SI-5558.scala @@ -0,0 +1,6 @@ +package test { + class T + object `package` { + def foo = ??? + } +} diff --git a/test/scaladoc/scalacheck/IndexTest.scala b/test/scaladoc/scalacheck/IndexTest.scala index 29e337da2b..bf385898fc 100644 --- a/test/scaladoc/scalacheck/IndexTest.scala +++ b/test/scaladoc/scalacheck/IndexTest.scala @@ -16,7 +16,7 @@ object Test extends Properties("Index") { val morepaths = Thread.currentThread.getContextClassLoader.getParent.asInstanceOf[URLClassLoader].getURLs.map(u => URLDecoder.decode(u.getPath)) (paths ++ morepaths).mkString(java.io.File.pathSeparator) } - + val docFactory = { val settings = new doc.Settings({Console.err.println(_)}) @@ -27,9 +27,9 @@ object Test extends Properties("Index") { new doc.DocFactory(reporter, settings) } - + val indexModelFactory = doc.model.IndexModelFactory - + def createIndex(path: String): Option[Index] = { val maybeUniverse = { @@ -79,4 +79,11 @@ object Test extends Properties("Index") { case None => false } } + property("package objects in index") = { + createIndex("test/scaladoc/resources/SI-5558.scala") match { + case Some(index) => + index.index.firstLetterIndex('f') isDefinedAt "foo" + case None => false + } + } } -- cgit v1.2.3 From 8779ade6f57ef15a04babf9715bc7ca4cbbdc425 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Mon, 2 Jul 2012 19:43:38 +0200 Subject: SI-4324 Scaladoc case class argument currying case class C(i: Int)(b: Boolean) would appear uncurried in scaladoc: case class C(i: Int, b: Boolean) --- .../scala/tools/nsc/doc/model/ModelFactory.scala | 5 ++++- test/scaladoc/run/SI-4324.check | 1 + test/scaladoc/run/SI-4324.scala | 24 ++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 test/scaladoc/run/SI-4324.check create mode 100644 test/scaladoc/run/SI-4324.scala diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 61b4267f3c..25b4a174ec 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -554,7 +554,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { new DocTemplateImpl(bSym, inTpl) with Class { def valueParams = // we don't want params on a class (non case class) signature - if (isCaseClass) List(sym.constrParamAccessors map (makeValueParam(_, this))) + if (isCaseClass) primaryConstructor match { + case Some(const) => const.sym.paramss map (_ map (makeValueParam(_, this))) + case None => List() + } else List.empty val constructors = members collect { case d: Constructor => d } diff --git a/test/scaladoc/run/SI-4324.check b/test/scaladoc/run/SI-4324.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/SI-4324.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/SI-4324.scala b/test/scaladoc/run/SI-4324.scala new file mode 100644 index 0000000000..686a133dc0 --- /dev/null +++ b/test/scaladoc/run/SI-4324.scala @@ -0,0 +1,24 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def code = """ + case class Test4324(arg11: String, arg12: Int)(arg21: String, arg22: Int)(arg31: Int, arg32: String) + """ + + // no need for special settings + def scaladocSettings = "" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + // just need to check the member exists, access methods will throw an error if there's a problem + rootPackage._class("Test4324").asInstanceOf[Class].valueParams match { + case List(List(arg11, arg12), List(arg21, arg22), List(arg31, arg32)) => //yeeey, do nothing + case other => + assert(false, "Incorrect valueParams generated: " + other + " instead of (arg11, arg12)(arg21, arg22)(arg31, arg32)") + } + } +} \ No newline at end of file -- cgit v1.2.3 From 891769fae541513d68ce7a8e84b7213472c333c9 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Mon, 2 Jul 2012 20:42:38 +0200 Subject: Scaladoc: workaround for untypical Map usecases SI-3448 partial fix: This enables a workaround for the fact that Map takes two type params while $Coll takes only one. But it doesn't fix the problem though, as there's one more piece missing from the puzzle - we need to adjust the `Coll`s in {immutable, mutable, concurrent}.Map to something that makes sense for the usecase. And that's not possible. But I'm committing this nevertheless, maybe other projects can benefit from it. And for SI-3448, the solution lies in automatic usecase generation, whenever that will be ready. --- src/compiler/scala/tools/nsc/ast/DocComments.scala | 19 ++++++----- test/scaladoc/run/SI-3448.check | 1 + test/scaladoc/run/SI-3448.scala | 38 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 test/scaladoc/run/SI-3448.check create mode 100644 test/scaladoc/run/SI-3448.scala diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala index b2d6800ebb..19af01bda8 100755 --- a/src/compiler/scala/tools/nsc/ast/DocComments.scala +++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala @@ -513,23 +513,25 @@ trait DocComments { self: Global => tstr } - val aliasExpansions: List[Type] = + // the Boolean tells us whether we can normalize: if we found an actual type, then yes, we can normalize, else no, + // use the synthetic alias created for the variable + val aliasExpansions: List[(Type, Boolean)] = for (alias <- aliases) yield lookupVariable(alias.name.toString.substring(1), site) match { case Some(repl) => val repl2 = cleanupVariable(repl) val tpe = getType(repl2, alias.name.toString) - if (tpe != NoType) tpe + if (tpe != NoType) (tpe, true) else { val alias1 = alias.cloneSymbol(rootMirror.RootClass, alias.rawflags, newTypeName(repl2)) - typeRef(NoPrefix, alias1, Nil) + (typeRef(NoPrefix, alias1, Nil), false) } case None => - typeRef(NoPrefix, alias, Nil) + (typeRef(NoPrefix, alias, Nil), false) } - def subst(sym: Symbol, from: List[Symbol], to: List[Type]): Type = - if (from.isEmpty) sym.tpe + def subst(sym: Symbol, from: List[Symbol], to: List[(Type, Boolean)]): (Type, Boolean) = + if (from.isEmpty) (sym.tpe, false) else if (from.head == sym) to.head else subst(sym, from.tail, to.tail) @@ -537,8 +539,9 @@ trait DocComments { self: Global => def apply(tp: Type) = mapOver(tp) match { case tp1 @ TypeRef(pre, sym, args) if (sym.name.length > 1 && sym.name.startChar == '$') => subst(sym, aliases, aliasExpansions) match { - case TypeRef(pre1, sym1, _) => - typeRef(pre1, sym1, args) + case (TypeRef(pre1, sym1, _), canNormalize) => + val tpe = typeRef(pre1, sym1, args) + if (canNormalize) tpe.normalize else tpe case _ => tp1 } diff --git a/test/scaladoc/run/SI-3448.check b/test/scaladoc/run/SI-3448.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/SI-3448.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/SI-3448.scala b/test/scaladoc/run/SI-3448.scala new file mode 100644 index 0000000000..a2d3f59596 --- /dev/null +++ b/test/scaladoc/run/SI-3448.scala @@ -0,0 +1,38 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + // Working around the fact that usecases have the form Coll[T] and not Coll[T, U], as required by Map + override def code = """ + /** + * @define Coll C[T] + */ + class C[T] { + /** + * @usecase def foo[T]: $Coll[T] + */ + def foo[T: Numeric]: C[T] + } + + + /** + * @define Coll D1[T] + */ + class D[U, T] extends C[T] { + protected type D1[Z] = D[U, Z] + } + """ + + // no need for special settings + def scaladocSettings = "" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + // just need to check the member exists, access methods will throw an error if there's a problem + assert(rootPackage._class("D")._method("foo").resultType.name == "D[U, T]", + rootPackage._class("D")._method("foo").resultType.name + " == D[U, T]") + } +} \ No newline at end of file -- cgit v1.2.3 From a119ad1ae58723bd2e757ed331a536ff4ae49bdf Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Thu, 12 Jul 2012 14:24:54 +0200 Subject: SI-4360 Adds prefixes to scaladoc This was a long-standing issue in scaladoc: It was unable to disambiguate between entries with the same name. One example is: immutable.Seq: trait Seq[+A] extends Iterable[A] with Seq[A] ... What's that? Seq extends Seq? No, immutable.Seq extends collection.Seq, but scaladoc was unable to show that. Now it does, depending on the template you're in. Prefixes are relative and can go back: -scala.collection.Seq has subclasses *immutable.Seq* and *mutable.Seq* -scala.immutable.Seq extends *collection.Seq* Unfortunately the price we pay for this is high, a 20% slowdown in scaladoc. This is why there is a new flag called -no-prefixes that disables the prefixes in front of types. Btw, it also fixes the notorious "booleanValue: This member is added by an implicit conversion from Boolean to Boolean ...". That's now java.lang.Boolean, so it becomes clear. Conflicts: src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala --- build.xml | 38 +++- src/compiler/scala/tools/ant/Scaladoc.scala | 9 + src/compiler/scala/tools/nsc/doc/DocFactory.scala | 1 + src/compiler/scala/tools/nsc/doc/Settings.scala | 8 +- .../html/page/diagram/DotDiagramGenerator.scala | 8 +- .../nsc/doc/html/page/diagram/DotRunner.scala | 9 +- .../scala/tools/nsc/doc/model/Entity.scala | 9 +- .../scala/tools/nsc/doc/model/ModelFactory.scala | 196 ++++--------------- .../doc/model/ModelFactoryImplicitSupport.scala | 7 +- .../nsc/doc/model/ModelFactoryTypeSupport.scala | 215 +++++++++++++++++++++ .../tools/nsc/doc/model/diagram/Diagram.scala | 8 +- .../nsc/doc/model/diagram/DiagramFactory.scala | 33 ++-- test/scaladoc/resources/SI-4360.scala | 42 ++++ test/scaladoc/resources/implicits-scopes-res.scala | 2 +- test/scaladoc/resources/package-object-res.scala | 2 +- test/scaladoc/run/SI-3314.scala | 14 +- test/scaladoc/run/SI-4360.check | 1 + test/scaladoc/run/SI-4360.scala | 48 +++++ test/scaladoc/run/implicits-scopes.scala | 6 +- test/scaladoc/scalacheck/CommentFactoryTest.scala | 4 +- 20 files changed, 439 insertions(+), 221 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala create mode 100644 test/scaladoc/resources/SI-4360.scala create mode 100644 test/scaladoc/run/SI-4360.check create mode 100644 test/scaladoc/run/SI-4360.scala diff --git a/build.xml b/build.xml index 8fa1b9cd76..b5db4ab4f4 100644 --- a/build.xml +++ b/build.xml @@ -2082,8 +2082,9 @@ DOCUMENTATION - + + @@ -2111,7 +2112,10 @@ DOCUMENTATION classpathref="pack.classpath" addparams="${scalac.args.all}" docRootContent="${src.dir}/library/rootdoc.txt" - implicits="on" diagrams="on" rawOutput="${scaladoc.raw.output}"> + implicits="on" + diagrams="on" + rawOutput="${scaladoc.raw.output}" + noPrefixes="${scaladoc.no.prefixes}"> @@ -2195,7 +2199,10 @@ DOCUMENTATION srcdir="${src.dir}/compiler" docRootContent="${src.dir}/compiler/rootdoc.txt" addparams="${scalac.args.all}" - implicits="on" diagrams="on" rawOutput="${scaladoc.raw.output}"> + implicits="on" + diagrams="on" + rawOutput="${scaladoc.raw.output}" + noPrefixes="${scaladoc.no.prefixes}"> @@ -2217,7 +2224,10 @@ DOCUMENTATION classpathref="pack.classpath" srcdir="${src.dir}/jline/src/main/java" addparams="${scalac.args.all}" - implicits="on" diagrams="on" rawOutput="${scaladoc.raw.output}"> + implicits="on" + diagrams="on" + rawOutput="${scaladoc.raw.output}" + noPrefixes="${scaladoc.no.prefixes}"> @@ -2241,7 +2251,10 @@ DOCUMENTATION classpathref="pack.classpath" srcdir="${src.dir}/scalap" addparams="${scalac.args.all}" - implicits="on" diagrams="on" rawOutput="${scaladoc.raw.output}"> + implicits="on" + diagrams="on" + rawOutput="${scaladoc.raw.output}" + noPrefixes="${scaladoc.no.prefixes}"> @@ -2263,7 +2276,10 @@ DOCUMENTATION classpathref="pack.classpath" srcdir="${src.dir}/partest" addparams="${scalac.args.all}" - implicits="on" diagrams="on" rawOutput="${scaladoc.raw.output}"> + implicits="on" + diagrams="on" + rawOutput="${scaladoc.raw.output}" + noPrefixes="${scaladoc.no.prefixes}"> @@ -2285,7 +2301,10 @@ DOCUMENTATION classpathref="pack.classpath" srcdir="${src.dir}/continuations/plugin" addparams="${scalac.args.all}" - implicits="on" diagrams="on" rawOutput="${scaladoc.raw.output}"> + implicits="on" + diagrams="on" + rawOutput="${scaladoc.raw.output}" + noPrefixes="${scaladoc.no.prefixes}"> @@ -2307,7 +2326,10 @@ DOCUMENTATION classpathref="pack.classpath" srcdir="${src.dir}/actors-migration" addparams="${scalac.args.all}" - implicits="on" diagrams="on" rawOutput="${scaladoc.raw.output}"> + implicits="on" + diagrams="on" + rawOutput="${scaladoc.raw.output}" + noPrefixes="${scaladoc.no.prefixes}"> diff --git a/src/compiler/scala/tools/ant/Scaladoc.scala b/src/compiler/scala/tools/ant/Scaladoc.scala index 2cada92c1e..9aa2f6f921 100644 --- a/src/compiler/scala/tools/ant/Scaladoc.scala +++ b/src/compiler/scala/tools/ant/Scaladoc.scala @@ -153,6 +153,8 @@ class Scaladoc extends ScalaMatchingTask { /** Instruct the scaladoc to produce textual ouput from html pages, for easy diff-ing */ private var docRawOutput: Boolean = false + /** Instruct the scaladoc not to generate prefixes */ + private var docNoPrefixes: Boolean = false /*============================================================================*\ ** Properties setters ** @@ -427,6 +429,12 @@ class Scaladoc extends ScalaMatchingTask { def setRawOutput(input: String) = docRawOutput = Flag.getBooleanValue(input, "rawOutput") + /** Set the `noPrefixes` bit to prevent Scaladoc from generating prefixes in + * front of types -- may lead to confusion, but significantly speeds up the generation. + * @param input One of the flags `yes/no` or `on/off`. Default if no/off. */ + def setNoPrefixes(input: String) = + docNoPrefixes = Flag.getBooleanValue(input, "noPrefixes") + /*============================================================================*\ ** Properties getters ** \*============================================================================*/ @@ -625,6 +633,7 @@ class Scaladoc extends ScalaMatchingTask { docSettings.docDiagrams.value = docDiagrams docSettings.docDiagramsDebug.value = docDiagramsDebug docSettings.docRawOutput.value = docRawOutput + docSettings.docNoPrefixes.value = docNoPrefixes if(!docDiagramsDotPath.isEmpty) docSettings.docDiagramsDotPath.value = docDiagramsDotPath.get if (!docgenerator.isEmpty) docSettings.docgenerator.value = docgenerator.get diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala index 3c92c3b4b6..964227a6a5 100644 --- a/src/compiler/scala/tools/nsc/doc/DocFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/DocFactory.scala @@ -81,6 +81,7 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor new { override val global: compiler.type = compiler } with model.ModelFactory(compiler, settings) with model.ModelFactoryImplicitSupport + with model.ModelFactoryTypeSupport with model.diagram.DiagramFactory with model.comment.CommentFactory with model.TreeFactory { diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index 31e49131f6..c7bdf74ebd 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -166,6 +166,11 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) "For each html file, create another .html.raw file containing only the text. (can be used for quickly diffing two scaladoc outputs)" ) + val docNoPrefixes = BooleanSetting ( + "-no-prefixes", + "Prevents generating prefixes in types, possibly creating ambiguous references, but significantly speeding up scaladoc." + ) + // Somewhere slightly before r18708 scaladoc stopped building unless the // self-type check was suppressed. I hijacked the slotted-for-removal-anyway // suppress-vt-warnings option and renamed it for this purpose. @@ -177,7 +182,8 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) docDiagrams, docDiagramsDebug, docDiagramsDotPath, docDiagramsDotTimeout, docDiagramsDotRestart, docImplicits, docImplicitsDebug, docImplicitsShowAll, - docDiagramsMaxNormalClasses, docDiagramsMaxImplicitClasses + docDiagramsMaxNormalClasses, docDiagramsMaxImplicitClasses, + docNoPrefixes ) val isScaladocSpecific: String => Boolean = scaladocSpecific map (_.name) diff --git a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala index 59560befc9..f3454f71b8 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala @@ -86,22 +86,22 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { // them by on node with a corresponding tooltip superClasses = if (_superClasses.length > settings.docDiagramsMaxNormalClasses.value) { val superClassesTooltip = Some(limitSize(_superClasses.map(_.tpe.name).mkString(", "))) - List(NormalNode(textTypeEntity(_superClasses.length + MultiSuffix), None, superClassesTooltip)) + List(NormalNode(textTypeEntity(_superClasses.length + MultiSuffix), None)(superClassesTooltip)) } else _superClasses subClasses = if (_subClasses.length > settings.docDiagramsMaxNormalClasses.value) { val subClassesTooltip = Some(limitSize(_subClasses.map(_.tpe.name).mkString(", "))) - List(NormalNode(textTypeEntity(_subClasses.length + MultiSuffix), None, subClassesTooltip)) + List(NormalNode(textTypeEntity(_subClasses.length + MultiSuffix), None)(subClassesTooltip)) } else _subClasses incomingImplicits = if (_incomingImplicits.length > settings.docDiagramsMaxImplicitClasses.value) { val incomingImplicitsTooltip = Some(limitSize(_incomingImplicits.map(_.tpe.name).mkString(", "))) - List(ImplicitNode(textTypeEntity(_incomingImplicits.length + MultiSuffix), None, incomingImplicitsTooltip)) + List(ImplicitNode(textTypeEntity(_incomingImplicits.length + MultiSuffix), None)(incomingImplicitsTooltip)) } else _incomingImplicits outgoingImplicits = if (_outgoingImplicits.length > settings.docDiagramsMaxImplicitClasses.value) { val outgoingImplicitsTooltip = Some(limitSize(_outgoingImplicits.map(_.tpe.name).mkString(", "))) - List(ImplicitNode(textTypeEntity(_outgoingImplicits.length + MultiSuffix), None, outgoingImplicitsTooltip)) + List(ImplicitNode(textTypeEntity(_outgoingImplicits.length + MultiSuffix), None)(outgoingImplicitsTooltip)) } else _outgoingImplicits thisNode = _thisNode diff --git a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotRunner.scala b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotRunner.scala index 37600fa908..adfeb3b012 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotRunner.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotRunner.scala @@ -28,7 +28,7 @@ class DotRunner(settings: doc.Settings) { if (dotProcess == null) { if (dotRestarts < settings.docDiagramsDotRestart.value) { if (dotRestarts != 0) - settings.printMsg("A new graphviz dot process will be created...\n") + settings.printMsg("Graphviz will be restarted...\n") dotRestarts += 1 dotProcess = new DotProcess(settings) } else @@ -145,9 +145,10 @@ class DotProcess(settings: doc.Settings) { settings.printMsg("**********************************************************************") } else { // we shouldn't just sit there for 50s not reporting anything, no? - settings.printMsg("Graphviz dot encountered an error when generating the diagram for") - settings.printMsg(templateName + ". Use the " + settings.docDiagramsDebug.name + " flag") - settings.printMsg("for more information.") + settings.printMsg("Graphviz dot encountered an error when generating the diagram for:") + settings.printMsg(templateName) + settings.printMsg("These are usually spurious errors, but if you notice a persistant error on") + settings.printMsg("a diagram, please use the " + settings.docDiagramsDebug.name + " flag and report a bug with the output.") } } } diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index 4ab77b356b..41ba95e072 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -96,9 +96,6 @@ trait TemplateEntity extends Entity { /** The self-type of this template, if it differs from the template type. */ def selfType : Option[TypeEntity] - - /** The type of this entity, with type members */ - def ownType: TypeEntity } @@ -206,7 +203,8 @@ trait HigherKinded { /** A template (class, trait, object or package) which is referenced in the universe, but for which no further * documentation is available. Only templates for which a source file is given are documented by Scaladoc. */ trait NoDocTemplate extends TemplateEntity { - def kind = "" + def kind = "" + //def kind = "(not documented) template" } /** An inherited template that was not documented in its original owner - example: @@ -214,7 +212,8 @@ trait NoDocTemplate extends TemplateEntity { * in the source: trait U extends T -- C appears in U as a NoDocTemplateMemberImpl * -- that is, U has a member for it but C doesn't get its own page */ trait NoDocTemplateMemberEntity extends TemplateEntity with MemberEntity { - def kind = "" + def kind = "" + //def kind = "(not documented) member template" } /** A template (class, trait, object or package) for which documentation is available. Only templates for which diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 25b4a174ec..b7c4eed87d 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -19,7 +19,12 @@ import model.{ RootPackage => RootPackageEntity } /** This trait extracts all required information for documentation from compilation units */ class ModelFactory(val global: Global, val settings: doc.Settings) { - thisFactory: ModelFactory with ModelFactoryImplicitSupport with DiagramFactory with CommentFactory with TreeFactory => + thisFactory: ModelFactory + with ModelFactoryImplicitSupport + with ModelFactoryTypeSupport + with DiagramFactory + with CommentFactory + with TreeFactory => import global._ import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass } @@ -32,7 +37,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { private var universe: Universe = null private def dbg(msg: String) = if (sys.props contains "scala.scaladoc.debug") println(msg) - private def closestPackage(sym: Symbol) = { + protected def closestPackage(sym: Symbol) = { if (sym.isPackage || sym.isPackageClass) sym else sym.enclosingPackage } @@ -63,7 +68,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { private val droppedPackages = mutable.Set[PackageImpl]() protected val docTemplatesCache = new mutable.LinkedHashMap[Symbol, DocTemplateImpl] protected val noDocTemplatesCache = new mutable.LinkedHashMap[Symbol, NoDocTemplateImpl] - protected var typeCache = new mutable.LinkedHashMap[Type, TypeEntity] + def packageDropped(tpl: DocTemplateImpl) = tpl match { + case p: PackageImpl => droppedPackages(p) + case _ => false + } def optimize(str: String): String = if (str.length < 16) str.intern else str @@ -95,7 +103,6 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def isObject = sym.isModule && !sym.isPackage def isCaseClass = sym.isCaseClass def isRootPackage = false - def ownType = makeType(sym.tpe, this) def selfType = if (sym.thisSym eq sym) None else Some(makeType(sym.thisSym.typeOfThis, this)) } @@ -338,14 +345,14 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def directSubClasses = allSubClasses.filter(_.parentTypes.map(_._1).contains(this)) /* Implcitly convertible class cache */ - private var implicitlyConvertibleClassesCache: mutable.ListBuffer[(DocTemplateEntity, ImplicitConversionImpl)] = null - def registerImplicitlyConvertibleClass(dtpl: DocTemplateEntity, conv: ImplicitConversionImpl): Unit = { + private var implicitlyConvertibleClassesCache: mutable.ListBuffer[(DocTemplateImpl, ImplicitConversionImpl)] = null + def registerImplicitlyConvertibleClass(dtpl: DocTemplateImpl, conv: ImplicitConversionImpl): Unit = { if (implicitlyConvertibleClassesCache == null) - implicitlyConvertibleClassesCache = mutable.ListBuffer[(DocTemplateEntity, ImplicitConversionImpl)]() + implicitlyConvertibleClassesCache = mutable.ListBuffer[(DocTemplateImpl, ImplicitConversionImpl)]() implicitlyConvertibleClassesCache += ((dtpl, conv)) } - def incomingImplicitlyConvertedClasses: List[(DocTemplateEntity, ImplicitConversionImpl)] = + def incomingImplicitlyConvertedClasses: List[(DocTemplateImpl, ImplicitConversionImpl)] = if (implicitlyConvertibleClassesCache == null) List() else @@ -408,7 +415,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { conv.targetTypeComponents map { case pair@(template, tpe) => template match { - case d: DocTemplateImpl => d.registerImplicitlyConvertibleClass(this, conv) + case d: DocTemplateImpl if (d != this) => d.registerImplicitlyConvertibleClass(this, conv) case _ => // nothing } (pair._1, pair._2, conv) @@ -508,6 +515,8 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { normalizeTemplate(aSym.owner) case _ if aSym.isModuleClass => normalizeTemplate(aSym.sourceModule) + // case t: ThisType => + // t. case _ => aSym } @@ -737,12 +746,12 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def findTemplate(query: String): Option[DocTemplateImpl] = { assert(modelFinished) - docTemplatesCache.values find { (tpl: TemplateImpl) => tpl.qualifiedName == query && !tpl.isObject } + docTemplatesCache.values find { (tpl: DocTemplateImpl) => tpl.qualifiedName == query && !packageDropped(tpl) && !tpl.isObject } } def findTemplateMaybe(aSym: Symbol): Option[DocTemplateImpl] = { assert(modelFinished) - docTemplatesCache.get(normalizeTemplate(aSym)) + docTemplatesCache.get(normalizeTemplate(aSym)).filterNot(packageDropped(_)) } def makeTemplate(aSym: Symbol): TemplateImpl = makeTemplate(aSym, None) @@ -890,158 +899,25 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { List((makeTemplate(aType.typeSymbol), makeType(aType, inTpl))) } - /** */ - def makeType(aType: Type, inTpl: TemplateImpl): TypeEntity = { - def templatePackage = closestPackage(inTpl.sym) - - def createTypeEntity = new TypeEntity { - private val nameBuffer = new StringBuilder - private var refBuffer = new immutable.TreeMap[Int, (LinkTo, Int)] - private def appendTypes0(types: List[Type], sep: String): Unit = types match { - case Nil => - case tp :: Nil => - appendType0(tp) - case tp :: tps => - appendType0(tp) - nameBuffer append sep - appendTypes0(tps, sep) - } - - private def appendType0(tpe: Type): Unit = tpe match { - /* Type refs */ - case tp: TypeRef if definitions.isFunctionType(tp) => - val args = tp.normalize.typeArgs - nameBuffer append '(' - appendTypes0(args.init, ", ") - nameBuffer append ") ⇒ " - appendType0(args.last) - case tp: TypeRef if definitions.isScalaRepeatedParamType(tp) => - appendType0(tp.args.head) - nameBuffer append '*' - case tp: TypeRef if definitions.isByNameParamType(tp) => - nameBuffer append "⇒ " - appendType0(tp.args.head) - case tp: TypeRef if definitions.isTupleType(tp) => - val args = tp.normalize.typeArgs - nameBuffer append '(' - appendTypes0(args, ", ") - nameBuffer append ')' - case TypeRef(pre, aSym, targs) => - val preSym = pre.widen.typeSymbol - // There's a work in progress here trying to deal with the - // places where undesirable prefixes are printed. - // ... - // If the prefix is something worthy of printing, see if the prefix type - // is in the same package as the enclosing template. If so, print it - // unqualified and they'll figure it out. - // - // val stripPrefixes = List(templatePackage.fullName + ".", "package.", "java.lang.") - // if (!preSym.printWithoutPrefix) { - // nameBuffer append stripPrefixes.foldLeft(pre.prefixString)(_ stripPrefix _) - // } - - // SI-3314/SI-4888: Classes, Traits and Types can be inherited from a template to another: - // class Enum { abstract class Value } - // class Day extends Enum { object Mon extends Value /*...*/ } - // ===> in such cases we have two options: - // (0) if there's no inheritance taking place (Enum#Value) we can link to the template directly - // (1) if we generate the doc template for Day, we can link to the correct member - // (2) if we don't generate the doc template, we should at least indicate the correct prefix in the tooltip - val bSym = normalizeTemplate(aSym) - val owner = - if ((preSym != NoSymbol) && /* it needs a prefix */ - (preSym != bSym.owner) && /* prefix is different from owner */ - // ((preSym != inTpl.sym) && /* prevent prefixes from being shown inside the defining class */ - // (preSym != inTpl.sym.moduleClass)) && /* or object */ - (aSym == bSym)) /* normalization doesn't play tricks on us */ - preSym - else - bSym.owner - - val bTpl = findTemplateMaybe(bSym) - val link = - if (owner == bSym.owner && bTpl.isDefined) - // (0) the owner's class is linked AND has a template - lovely - LinkToTpl(bTpl.get) - else { - val oTpl = findTemplateMaybe(owner) - val bMbr = oTpl.map(findMember(bSym, _)) - if (oTpl.isDefined && bMbr.isDefined && bMbr.get.isDefined) - // (1) the owner's class - LinkToMember(bMbr.get.get, oTpl.get) //ugh - else - // (2) if we still couldn't find the owner, make a noDocTemplate for everything (in the correct owner!) - LinkToTpl(makeTemplate(bSym, Some(makeTemplate(owner)))) - } - - // TODO: The name might include a prefix, take care of that! - val name = bSym.nameString - val pos0 = nameBuffer.length - refBuffer += pos0 -> ((link, name.length)) - nameBuffer append name - - // if (bSym.isNonClassType && bSym != AnyRefClass) { - // nameBuffer append bSym.decodedName - // } else { - // val tpl = makeTemplate(bSym) - // val pos0 = nameBuffer.length - // refBuffer += pos0 -> ((LinkToTpl(tpl), tpl.name.length)) - // nameBuffer append tpl.name - // } - - if (!targs.isEmpty) { - nameBuffer append '[' - appendTypes0(targs, ", ") - nameBuffer append ']' - } - /* Refined types */ - case RefinedType(parents, defs) => - val ignoreParents = Set[Symbol](AnyClass, ObjectClass) - val filtParents = parents filterNot (x => ignoreParents(x.typeSymbol)) match { - case Nil => parents - case ps => ps - } - appendTypes0(filtParents, " with ") - // XXX Still todo: properly printing refinements. - // Since I didn't know how to go about displaying a multi-line type, I went with - // printing single method refinements (which should be the most common) and printing - // the number of members if there are more. - defs.toList match { - case Nil => () - case x :: Nil => nameBuffer append (" { " + x.defString + " }") - case xs => nameBuffer append (" { ... /* %d definitions in type refinement */ }" format xs.size) - } - /* Eval-by-name types */ - case NullaryMethodType(result) => - nameBuffer append '⇒' - appendType0(result) - /* Polymorphic types */ - case PolyType(tparams, result) => assert(tparams.nonEmpty) -// throw new Error("Polymorphic type '" + tpe + "' cannot be printed as a type") - def typeParamsToString(tps: List[Symbol]): String = if (tps.isEmpty) "" else - tps.map{tparam => - tparam.varianceString + tparam.name + typeParamsToString(tparam.typeParams) - }.mkString("[", ", ", "]") - nameBuffer append typeParamsToString(tparams) - appendType0(result) - case tpen => - nameBuffer append tpen.toString + def makeQualifiedName(sym: Symbol, relativeTo: Option[Symbol] = None): String = { + val stop = if (relativeTo.isDefined) relativeTo.get.ownerChain.toSet else Set[Symbol]() + var sym1 = sym + var path = new StringBuilder() + // var path = List[Symbol]() + + while ((sym1 != NoSymbol) && (path.isEmpty || !stop(sym1))) { + val sym1Norm = normalizeTemplate(sym1) + if (!sym1.sourceModule.isPackageObject && sym1Norm != RootPackage) { + if (path.length != 0) + path.insert(0, ".") + path.insert(0, sym1Norm.nameString) + // path::= sym1Norm } - appendType0(aType) - val refEntity = refBuffer - val name = optimize(nameBuffer.toString) + sym1 = sym1.owner } - if (aType.isTrivial) - typeCache.get(aType) match { - case Some(typeEntity) => typeEntity - case None => - val typeEntity = createTypeEntity - typeCache += aType -> typeEntity - typeEntity - } - else - createTypeEntity + optimize(path.toString) + //path.mkString(".") } def normalizeOwner(aSym: Symbol): Symbol = diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index 493ad3d831..ddcdf1cf5c 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -53,7 +53,7 @@ import model.{ RootPackage => RootPackageEntity } * TODO: Give an overview here */ trait ModelFactoryImplicitSupport { - thisFactory: ModelFactory with CommentFactory with TreeFactory => + thisFactory: ModelFactory with ModelFactoryTypeSupport with CommentFactory with TreeFactory => import global._ import global.analyzer._ @@ -329,11 +329,6 @@ trait ModelFactoryImplicitSupport { } } - def makeQualifiedName(sym: Symbol): String = { - val remove = Set[Symbol](RootPackage, RootClass, EmptyPackage, EmptyPackageClass) - sym.ownerChain.filterNot(remove.contains(_)).reverse.map(_.nameString).mkString(".") - } - /* ============== IMPLEMENTATION PROVIDING ENTITY TYPES ============== */ class ImplicitConversionImpl( diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala new file mode 100644 index 0000000000..2bc9f070b1 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala @@ -0,0 +1,215 @@ +/* NSC -- new Scala compiler -- Copyright 2007-2011 LAMP/EPFL */ + +package scala.tools.nsc +package doc +package model + +import comment._ + +import diagram._ + +import scala.collection._ +import scala.util.matching.Regex + +import symtab.Flags + +import io._ + +import model.{ RootPackage => RootPackageEntity } + +/** This trait extracts all required information for documentation from compilation units */ +trait ModelFactoryTypeSupport { + thisFactory: ModelFactory + with ModelFactoryImplicitSupport + with ModelFactoryTypeSupport + with DiagramFactory + with CommentFactory + with TreeFactory => + + import global._ + import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass } + import rootMirror.{ RootPackage, RootClass, EmptyPackage } + + protected var typeCache = new mutable.LinkedHashMap[(Type, TemplateImpl), TypeEntity] + protected var typeCacheNoPrefix = new mutable.LinkedHashMap[Type, TypeEntity] + + /** */ + def makeType(aType: Type, inTpl: TemplateImpl): TypeEntity = { + def templatePackage = closestPackage(inTpl.sym) + + def createTypeEntity = new TypeEntity { + private var nameBuffer = new StringBuilder + private var refBuffer = new immutable.TreeMap[Int, (LinkTo, Int)] + private def appendTypes0(types: List[Type], sep: String): Unit = types match { + case Nil => + case tp :: Nil => + appendType0(tp) + case tp :: tps => + appendType0(tp) + nameBuffer append sep + appendTypes0(tps, sep) + } + + private def appendType0(tpe: Type): Unit = tpe match { + /* Type refs */ + case tp: TypeRef if definitions.isFunctionType(tp) => + val args = tp.normalize.typeArgs + nameBuffer append '(' + appendTypes0(args.init, ", ") + nameBuffer append ") ⇒ " + appendType0(args.last) + case tp: TypeRef if definitions.isScalaRepeatedParamType(tp) => + appendType0(tp.args.head) + nameBuffer append '*' + case tp: TypeRef if definitions.isByNameParamType(tp) => + nameBuffer append "⇒ " + appendType0(tp.args.head) + case tp: TypeRef if definitions.isTupleType(tp) => + val args = tp.normalize.typeArgs + nameBuffer append '(' + appendTypes0(args, ", ") + nameBuffer append ')' + case TypeRef(pre, aSym, targs) => + val preSym = pre.widen.typeSymbol + + // SI-3314/SI-4888: Classes, Traits and Types can be inherited from a template to another: + // class Enum { abstract class Value } + // class Day extends Enum { object Mon extends Value /*...*/ } + // ===> in such cases we have two options: + // (0) if there's no inheritance taking place (Enum#Value) we can link to the template directly + // (1) if we generate the doc template for Day, we can link to the correct member + // (2) if we don't generate the doc template, we should at least indicate the correct prefix in the tooltip + val bSym = normalizeTemplate(aSym) + val owner = + if ((preSym != NoSymbol) && /* it needs a prefix */ + (preSym != bSym.owner) && /* prefix is different from owner */ + // ((preSym != inTpl.sym) && /* prevent prefixes from being shown inside the defining class */ + // (preSym != inTpl.sym.moduleClass)) && /* or object */ + (aSym == bSym)) /* normalization doesn't play tricks on us */ + preSym + else + bSym.owner + + val bTpl = findTemplateMaybe(bSym) + val link = + if (owner == bSym.owner && bTpl.isDefined) + // (0) the owner's class is linked AND has a template - lovely + LinkToTpl(bTpl.get) + else { + val oTpl = findTemplateMaybe(owner) + val bMbr = oTpl.map(findMember(bSym, _)) + if (oTpl.isDefined && bMbr.isDefined && bMbr.get.isDefined) + // (1) the owner's class + LinkToMember(bMbr.get.get, oTpl.get) //ugh + else + // (2) if we still couldn't find the owner, make a noDocTemplate for everything (in the correct owner!) + LinkToTpl(makeTemplate(bSym, Some(makeTemplate(owner)))) + } + + // SI-4360 Showing prefixes when necessary + // We check whether there's any directly accessible type with the same name in the current template OR if the + // type is inherited from one template to another. There may be multiple symbols with the same name in scope, + // but we won't show the prefix if our symbol is among them, only if *it's not* -- that's equal to showing + // the prefix only for ambiguous references, not for overloaded ones. + def needsPrefix: Boolean = { + if (owner != bSym.owner && (normalizeTemplate(owner) != inTpl.sym)) + return true + + for (tpl <- inTpl.sym.ownerChain) { + tpl.info.member(bSym.name) match { + case NoSymbol => + // No syms with that name, look further inside the owner chain + case sym => + // Symbol found -- either the correct symbol, another one OR an overloaded alternative + if (sym == bSym) + return false + else sym.info match { + case OverloadedType(owner, alternatives) => + return alternatives.contains(bSym) + case _ => + return true + } + } + } + // if it's not found in the owner chain, we can safely leave out the prefix + false + } + + val prefix = + if (!settings.docNoPrefixes.value && needsPrefix && (bSym != AnyRefClass /* which we normalize */)) { + val qualifiedName = makeQualifiedName(owner, Some(inTpl.sym)) + if (qualifiedName != "") qualifiedName + "." else "" + } else "" + + //DEBUGGING: + //if (makeQualifiedName(bSym) == "pack1.A") println("needsPrefix(" + bSym + ", " + owner + ", " + inTpl.qualifiedName + ") => " + needsPrefix + " and prefix=" + prefix) + + val name = prefix + bSym.nameString + val pos0 = nameBuffer.length + refBuffer += pos0 -> ((link, name.length)) + nameBuffer append name + + if (!targs.isEmpty) { + nameBuffer append '[' + appendTypes0(targs, ", ") + nameBuffer append ']' + } + /* Refined types */ + case RefinedType(parents, defs) => + val ignoreParents = Set[Symbol](AnyClass, ObjectClass) + val filtParents = parents filterNot (x => ignoreParents(x.typeSymbol)) match { + case Nil => parents + case ps => ps + } + appendTypes0(filtParents, " with ") + // XXX Still todo: properly printing refinements. + // Since I didn't know how to go about displaying a multi-line type, I went with + // printing single method refinements (which should be the most common) and printing + // the number of members if there are more. + defs.toList match { + case Nil => () + case x :: Nil => nameBuffer append (" { " + x.defString + " }") + case xs => nameBuffer append (" { ... /* %d definitions in type refinement */ }" format xs.size) + } + /* Eval-by-name types */ + case NullaryMethodType(result) => + nameBuffer append '⇒' + appendType0(result) + /* Polymorphic types */ + case PolyType(tparams, result) => assert(tparams.nonEmpty) +// throw new Error("Polymorphic type '" + tpe + "' cannot be printed as a type") + def typeParamsToString(tps: List[Symbol]): String = if (tps.isEmpty) "" else + tps.map{tparam => + tparam.varianceString + tparam.name + typeParamsToString(tparam.typeParams) + }.mkString("[", ", ", "]") + nameBuffer append typeParamsToString(tparams) + appendType0(result) + case tpen => + nameBuffer append tpen.toString + } + appendType0(aType) + val refEntity = refBuffer + val name = optimize(nameBuffer.toString) + nameBuffer = null + } + + // SI-4360: Entity caching depends on both the type AND the template it's in, as the prefixes might change for the + // same type based on the template the type is shown in. + val cached = + if (!settings.docNoPrefixes.value) + typeCache.get((aType, inTpl)) + else + typeCacheNoPrefix.get(aType) + + cached match { + case Some(typeEntity) => typeEntity + case None => + val typeEntity = createTypeEntity + if (!settings.docNoPrefixes.value) + typeCache += (aType, inTpl) -> typeEntity + else + typeCacheNoPrefix += aType -> typeEntity + typeEntity + } + } +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala index 2b804ca10f..902d5da240 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala @@ -98,20 +98,20 @@ object OtherNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEn /** The node for the current class */ -case class ThisNode(tpe: TypeEntity, tpl: Option[TemplateEntity], tooltip: Option[String] = None) extends Node { override def isThisNode = true } +case class ThisNode(tpe: TypeEntity, tpl: Option[TemplateEntity])(val tooltip: Option[String] = None) extends Node { override def isThisNode = true } /** The usual node */ -case class NormalNode(tpe: TypeEntity, tpl: Option[TemplateEntity], tooltip: Option[String] = None) extends Node { override def isNormalNode = true } +case class NormalNode(tpe: TypeEntity, tpl: Option[TemplateEntity])(val tooltip: Option[String] = None) extends Node { override def isNormalNode = true } /** A class or trait the thisnode can be converted to by an implicit conversion * TODO: I think it makes more sense to use the tpe links to templates instead of the TemplateEntity for implicit nodes * since some implicit conversions convert the class to complex types that cannot be represented as a single tmeplate */ -case class ImplicitNode(tpe: TypeEntity, tpl: Option[TemplateEntity], tooltip: Option[String] = None) extends Node { override def isImplicitNode = true } +case class ImplicitNode(tpe: TypeEntity, tpl: Option[TemplateEntity])(val tooltip: Option[String] = None) extends Node { override def isImplicitNode = true } /** An outside node is shown in packages when a class from a different package makes it to the package diagram due to * its relation to a class in the template (see @contentDiagram hideInheritedNodes annotation) */ -case class OutsideNode(tpe: TypeEntity, tpl: Option[TemplateEntity], tooltip: Option[String] = None) extends Node { override def isOutsideNode = true } +case class OutsideNode(tpe: TypeEntity, tpl: Option[TemplateEntity])(val tooltip: Option[String] = None) extends Node { override def isOutsideNode = true } // Computing and offering node depth information diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala index 731801b143..d0b363854c 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala @@ -18,21 +18,14 @@ import scala.collection.immutable.SortedMap * @author Vlad Ureche */ trait DiagramFactory extends DiagramDirectiveParser { - this: ModelFactory with DiagramFactory with CommentFactory with TreeFactory => + this: ModelFactory with ModelFactoryTypeSupport with DiagramFactory with CommentFactory with TreeFactory => import this.global.definitions._ import this.global._ // the following can used for hardcoding different relations into the diagram, for bootstrapping purposes - lazy val AnyNode = normalNode(AnyClass) - lazy val AnyRefNode = normalNode(AnyRefClass) - lazy val AnyValNode = normalNode(AnyValClass) - lazy val NullNode = normalNode(NullClass) - lazy val NothingNode = normalNode(NothingClass) - def normalNode(sym: Symbol) = - NormalNode(makeTemplate(sym).ownType, Some(makeTemplate(sym))) def aggregationNode(text: String) = - NormalNode(new TypeEntity { val name = text; val refEntity = SortedMap[Int, (LinkTo, Int)]() }, None) + NormalNode(new TypeEntity { val name = text; val refEntity = SortedMap[Int, (LinkTo, Int)]() }, None)() /** Create the inheritance diagram for this template */ def makeInheritanceDiagram(tpl: DocTemplateImpl): Option[Diagram] = { @@ -52,31 +45,31 @@ trait DiagramFactory extends DiagramDirectiveParser { None else { // the main node - val thisNode = ThisNode(tpl.ownType, Some(tpl), Some(tpl.qualifiedName + " (this " + tpl.kind + ")")) + val thisNode = ThisNode(tpl.resultType, Some(tpl))(Some(tpl.qualifiedName + " (this " + tpl.kind + ")")) // superclasses var superclasses: List[Node] = tpl.parentTypes.collect { - case p: (TemplateEntity, TypeEntity) if !classExcluded(p._1) => NormalNode(p._2, Some(p._1)) + case p: (TemplateEntity, TypeEntity) if !classExcluded(p._1) => NormalNode(p._2, Some(p._1))() }.reverse // incoming implcit conversions lazy val incomingImplicitNodes = tpl.incomingImplicitlyConvertedClasses.map { case (incomingTpl, conv) => - ImplicitNode(incomingTpl.ownType, Some(incomingTpl), implicitTooltip(from=incomingTpl, to=tpl, conv=conv)) + ImplicitNode(makeType(incomingTpl.sym.tpe, tpl), Some(incomingTpl))(implicitTooltip(from=incomingTpl, to=tpl, conv=conv)) } // subclasses var subclasses: List[Node] = tpl.directSubClasses.flatMap { - case d: TemplateEntity if !classExcluded(d) => List(NormalNode(d.ownType, Some(d))) + case d: TemplateImpl if !classExcluded(d) => List(NormalNode(makeType(d.sym.tpe, tpl), Some(d))()) case _ => Nil }.sortBy(_.tpl.get.name)(implicitly[Ordering[String]].reverse) // outgoing implicit coversions lazy val outgoingImplicitNodes = tpl.outgoingImplicitlyConvertedClasses.map { case (outgoingTpl, outgoingType, conv) => - ImplicitNode(outgoingType, Some(outgoingTpl), implicitTooltip(from=tpl, to=tpl, conv=conv)) + ImplicitNode(outgoingType, Some(outgoingTpl))(implicitTooltip(from=tpl, to=tpl, conv=conv)) } // TODO: Everyone should be able to use the @{inherit,content}Diagram annotation to change the diagrams. @@ -149,7 +142,12 @@ trait DiagramFactory extends DiagramDirectiveParser { case _ => } - mapNodes += node -> (if (node.inTemplate == pack) NormalNode(node.ownType, Some(node)) else OutsideNode(node.ownType, Some(node))) + mapNodes += node -> ( + if (node.inTemplate == pack) + NormalNode(node.resultType, Some(node))() + else + OutsideNode(node.resultType, Some(node))() + ) } if (nodesShown.isEmpty) @@ -173,7 +171,10 @@ trait DiagramFactory extends DiagramDirectiveParser { val anyRefSubtypes = Nil val allAnyRefTypes = aggregationNode("All AnyRef subtypes") val nullTemplate = makeTemplate(NullClass) - ContentDiagram(allAnyRefTypes::nodes, (mapNodes(nullTemplate), allAnyRefTypes::anyRefSubtypes)::edges.filterNot(_._1.tpl == Some(nullTemplate))) + if (nullTemplate.isDocTemplate) + ContentDiagram(allAnyRefTypes::nodes, (mapNodes(nullTemplate), allAnyRefTypes::anyRefSubtypes)::edges.filterNot(_._1.tpl == Some(nullTemplate))) + else + ContentDiagram(nodes, edges) } else ContentDiagram(nodes, edges) diff --git a/test/scaladoc/resources/SI-4360.scala b/test/scaladoc/resources/SI-4360.scala new file mode 100644 index 0000000000..8e8b96afd5 --- /dev/null +++ b/test/scaladoc/resources/SI-4360.scala @@ -0,0 +1,42 @@ +package scala.test.scaladoc.prefix { + package pack1 { + + class A { + class Z + } + + class B extends A + + package a { + class C + } + + package b { + class C + } + + package c { + class C + + class L extends pack2.Z + + class TEST { + // test inherited classes + def fooCA(x: pack1.A#Z) = 1 + def fooCB(x: pack1.B#Z) = 1 + def fooCS(x: pack2.Z#Z) = 1 + def fooCL(x: L#Z) = 1 + // test in packages + def fooPA(x: pack1.a.C) = 1 + def fooPB(x: pack1.b.C) = 1 + def fooPC(x: pack1.c.C) = 1 + } + + class A extends pack1.A + } + } + + package pack2 { + class Z extends pack1.A + } +} \ No newline at end of file diff --git a/test/scaladoc/resources/implicits-scopes-res.scala b/test/scaladoc/resources/implicits-scopes-res.scala index aaeb43f95b..c675a645bd 100644 --- a/test/scaladoc/resources/implicits-scopes-res.scala +++ b/test/scaladoc/resources/implicits-scopes-res.scala @@ -22,7 +22,7 @@ package test2 { package classes { class A class B { def b = "" } - object test { /* (new A).b won't compile */ } + object test { (new A).b } } } diff --git a/test/scaladoc/resources/package-object-res.scala b/test/scaladoc/resources/package-object-res.scala index 17d5c0a499..f1f714dd1f 100644 --- a/test/scaladoc/resources/package-object-res.scala +++ b/test/scaladoc/resources/package-object-res.scala @@ -1,4 +1,4 @@ -/** This package have A and B. +/** This package has A and B. */ package test { trait A { def hi = "hello" } diff --git a/test/scaladoc/run/SI-3314.scala b/test/scaladoc/run/SI-3314.scala index 665223098a..3b5c658078 100644 --- a/test/scaladoc/run/SI-3314.scala +++ b/test/scaladoc/run/SI-3314.scala @@ -48,15 +48,15 @@ object Test extends ScaladocModelTest { val WeekDayInObject = test2._object("WeekDayObject")._member("WeekDay") val expected = List( - ("isWorkingDay1", "Value", ValueInClass), - ("isWorkingDay2", "Value", ValueInClass), - ("isWorkingDay3", "Value", ValueInTrait), - ("isWorkingDay4", "Value", ValueInTrait), - ("isWorkingDay5", "Value", ValueInObject), + ("isWorkingDay1", "WeekDayClass.Value", ValueInClass), + ("isWorkingDay2", "WeekDayClass.Value", ValueInClass), + ("isWorkingDay3", "WeekDayTrait.Value", ValueInTrait), + ("isWorkingDay4", "WeekDayTrait.Value", ValueInTrait), + ("isWorkingDay5", "WeekDayObject.Value", ValueInObject), ("isWorkingDay6", "WeekDay", WeekDayInObject), - ("isWorkingDay7", "Value", ValueInObject), + ("isWorkingDay7", "WeekDayObject.Value", ValueInObject), ("isWorkingDay8", "WeekDay", WeekDayInObject), - ("isWorkingDay9", "Value", ValueInObject)) + ("isWorkingDay9", "WeekDayObject.Value", ValueInObject)) for ((method, name, ref) <- expected) { assert(doc._method(method).valueParams(0)(0).resultType.name == name, diff --git a/test/scaladoc/run/SI-4360.check b/test/scaladoc/run/SI-4360.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/SI-4360.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/SI-4360.scala b/test/scaladoc/run/SI-4360.scala new file mode 100644 index 0000000000..3abc61c267 --- /dev/null +++ b/test/scaladoc/run/SI-4360.scala @@ -0,0 +1,48 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def resourceFile = "SI-4360.scala" + + // no need for special settings + def scaladocSettings = "" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + // just need to check the member exists, access methods will throw an error if there's a problem + val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("prefix") + + val TEST = base._package("pack1")._package("c")._class("TEST") + val fooCA = TEST._method("fooCA") + val fooCB = TEST._method("fooCB") + val fooCS = TEST._method("fooCS") + val fooCL = TEST._method("fooCL") + val fooPA = TEST._method("fooPA") + val fooPB = TEST._method("fooPB") + val fooPC = TEST._method("fooPC") + + val expected = List( + (fooCA, "Z", 1), + (fooCB, "B.Z", 1), + (fooCS, "pack2.Z.Z", 1), + (fooCL, "L.Z", 1), + (fooPA, "a.C", 1), + (fooPB, "b.C", 1), + (fooPC, "C", 1) + ) + + for ((method, name, refs) <- expected) { + assert(method.valueParams(0)(0).resultType.name == name, + method.valueParams(0)(0).resultType.name + " == " + name + " (in " + method.qualifiedName + ")") + assert(method.valueParams(0)(0).resultType.refEntity.size == refs, + method.valueParams(0)(0).resultType.refEntity.size + " == " + refs + " (in " + method.qualifiedName + ")") + } + + val A = base._package("pack1")._package("c")._class("A") + assert(A.linearizationTypes(0).name == "pack1.A", A.linearizationTypes(0).name + " == pack1.A") + assert(A.linearizationTypes(0).refEntity.size == 1, A.linearizationTypes(0).refEntity.size + " == 1") + } +} \ No newline at end of file diff --git a/test/scaladoc/run/implicits-scopes.scala b/test/scaladoc/run/implicits-scopes.scala index 7b9e80e148..d91deba326 100644 --- a/test/scaladoc/run/implicits-scopes.scala +++ b/test/scaladoc/run/implicits-scopes.scala @@ -24,7 +24,7 @@ object Test extends ScaladocModelTest { val test1 = base._package("test1") val A = test1._class("A") - conv = A._conversion(test1.qualifiedName + ".package.toB") // the .package means it's the package object + conv = A._conversion(test1.qualifiedName + ".toB") assert(conv.members.length == 1) assert(conv.constraints.length == 0) } @@ -36,7 +36,9 @@ object Test extends ScaladocModelTest { val classes = test2._package("classes") val A = classes._class("A") - assert(A._conversions(test2.qualifiedName + ".toB").isEmpty) + conv = A._conversion(test2.qualifiedName + ".toB") + assert(conv.members.length == 1) + assert(conv.constraints.length == 0) } //// test3 ///////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/test/scaladoc/scalacheck/CommentFactoryTest.scala b/test/scaladoc/scalacheck/CommentFactoryTest.scala index b576ba5544..b7869d5bf4 100644 --- a/test/scaladoc/scalacheck/CommentFactoryTest.scala +++ b/test/scaladoc/scalacheck/CommentFactoryTest.scala @@ -10,7 +10,7 @@ import scala.tools.nsc.doc.model.diagram._ class Factory(val g: Global, val s: doc.Settings) extends doc.model.ModelFactory(g, s) { - thisFactory: Factory with ModelFactoryImplicitSupport with DiagramFactory with CommentFactory with doc.model.TreeFactory => + thisFactory: Factory with ModelFactoryImplicitSupport with ModelFactoryTypeSupport with DiagramFactory with CommentFactory with doc.model.TreeFactory => def strip(c: Comment): Option[Inline] = { c.body match { @@ -31,7 +31,7 @@ object Test extends Properties("CommentFactory") { val settings = new doc.Settings((str: String) => {}) val reporter = new scala.tools.nsc.reporters.ConsoleReporter(settings) val g = new Global(settings, reporter) - (new Factory(g, settings) with ModelFactoryImplicitSupport with DiagramFactory with CommentFactory with doc.model.TreeFactory) + (new Factory(g, settings) with ModelFactoryImplicitSupport with ModelFactoryTypeSupport with DiagramFactory with CommentFactory with doc.model.TreeFactory) } def parse(src: String, dst: Inline) = { -- cgit v1.2.3 From 8d0ea747c240e4881c057a78cf2c90e69369ca4b Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Fri, 6 Jul 2012 02:17:01 +0200 Subject: Scaladoc minor fix: Typos in diagrams As suggested by Heather in pull request #816. --- src/compiler/scala/tools/nsc/doc/Settings.scala | 2 +- .../scala/tools/nsc/doc/html/page/diagram/DotRunner.scala | 2 +- src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index c7bdf74ebd..aae88aa03e 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -147,7 +147,7 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) val docDiagramsDotTimeout = IntSetting( "-diagrams-dot-timeout", - "The timeout before the graphviz dot util is forecefully closed, in seconds (default: 10)", + "The timeout before the graphviz dot util is forcefully closed, in seconds (default: 10)", 10, None, _ => None diff --git a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotRunner.scala b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotRunner.scala index adfeb3b012..3040278290 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotRunner.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotRunner.scala @@ -46,7 +46,7 @@ class DotRunner(settings: doc.Settings) { if (dotRestarts == settings.docDiagramsDotRestart.value) { settings.printMsg("\n") settings.printMsg("**********************************************************************") - settings.printMsg("Diagrams will be disabled for this run beucause the graphviz dot tool") + settings.printMsg("Diagrams will be disabled for this run because the graphviz dot tool") settings.printMsg("has malfunctioned too many times. These scaladoc flags may help:") settings.printMsg("") val baseList = List(settings.docDiagramsDebug, diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index b7c4eed87d..2ce7927f42 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -367,7 +367,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { lazy val memberSyms = sym.info.members.filter(s => membersShouldDocument(s, this)) // the inherited templates (classes, traits or objects) - var memberSymsLazy = memberSyms.filter(t => templateShouldDocument(t, this) && !inOriginalOnwer(t, this)) + var memberSymsLazy = memberSyms.filter(t => templateShouldDocument(t, this) && !inOriginalOwner(t, this)) // the direct members (methods, values, vars, types and directly contained templates) var memberSymsEager = memberSyms.filter(!memberSymsLazy.contains(_)) // the members generated by the symbols in memberSymsEager @@ -611,7 +611,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } else { // no class inheritance at this point - assert(inOriginalOnwer(bSym, inTpl)) + assert(inOriginalOwner(bSym, inTpl)) createDocTemplate(bSym, inTpl) } } @@ -716,7 +716,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { case _ => sys.error("'" + bSym + "' must be in a package") } - else if (!modelFinished && templateShouldDocument(bSym, inTpl) && inOriginalOnwer(bSym, inTpl)) + else if (!modelFinished && templateShouldDocument(bSym, inTpl) && inOriginalOwner(bSym, inTpl)) Some(modelCreation.createTemplate(bSym, inTpl)) else None @@ -945,7 +945,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { */ normalizeTemplate(aSym) - def inOriginalOnwer(aSym: Symbol, inTpl: TemplateImpl): Boolean = + def inOriginalOwner(aSym: Symbol, inTpl: TemplateImpl): Boolean = normalizeOwner(aSym.owner) == normalizeOwner(inTpl.sym) def templateShouldDocument(aSym: Symbol, inTpl: TemplateImpl): Boolean = @@ -953,7 +953,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { localShouldDocument(aSym) && !isEmptyJavaObject(aSym) && // either it's inside the original owner or we can document it later: - (!inOriginalOnwer(aSym, inTpl) || (aSym.isPackageClass || (aSym.sourceFile != null))) + (!inOriginalOwner(aSym, inTpl) || (aSym.isPackageClass || (aSym.sourceFile != null))) def membersShouldDocument(sym: Symbol, inTpl: TemplateImpl) = // pruning modules that shouldn't be documented -- cgit v1.2.3 From 929415a3f4d5d6261d10cc6d28720c5241716bae Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Sun, 8 Jul 2012 23:57:56 +0200 Subject: SI-4887 Link existentials in scaladoc Based on an inital patch by Nada Amin. Rewrote some of the code to compress it a bit. Also fixed a problem that was affecting the prefix printing for typerefs: type parameter and existentials won't get prefixes. While I can imagine cases where you'd want to see where they come from, you can always hover over them and see their origin. Also added support for pretty-printing ThisTypes, SuperTypes and SingleTypes (with links) --- .../nsc/doc/model/ModelFactoryTypeSupport.scala | 126 ++++++++++++++++++++- test/scaladoc/resources/SI-3314.scala | 15 +++ test/scaladoc/run/SI-3314.scala | 19 +++- test/scaladoc/run/SI-4887.check | 1 + test/scaladoc/run/SI-4887.scala | 46 ++++++++ 5 files changed, 200 insertions(+), 7 deletions(-) create mode 100644 test/scaladoc/run/SI-4887.check create mode 100644 test/scaladoc/run/SI-4887.scala diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala index 2bc9f070b1..0463ac0420 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala @@ -83,8 +83,6 @@ trait ModelFactoryTypeSupport { val owner = if ((preSym != NoSymbol) && /* it needs a prefix */ (preSym != bSym.owner) && /* prefix is different from owner */ - // ((preSym != inTpl.sym) && /* prevent prefixes from being shown inside the defining class */ - // (preSym != inTpl.sym.moduleClass)) && /* or object */ (aSym == bSym)) /* normalization doesn't play tricks on us */ preSym else @@ -112,8 +110,14 @@ trait ModelFactoryTypeSupport { // but we won't show the prefix if our symbol is among them, only if *it's not* -- that's equal to showing // the prefix only for ambiguous references, not for overloaded ones. def needsPrefix: Boolean = { - if (owner != bSym.owner && (normalizeTemplate(owner) != inTpl.sym)) + if ((owner != bSym.owner || preSym.isRefinementClass) && (normalizeTemplate(owner) != inTpl.sym)) return true + // don't get tricked into prefixng method type params and existentials: + // I tried several tricks BUT adding the method for which I'm creating the type => that simply won't scale, + // as ValueParams are independent of their parent member, and I really don't want to add this information to + // all terms, as we're already over the allowed memory footprint + if (aSym.isTypeParameterOrSkolem || aSym.isExistentiallyBound /* existential or existential skolem */) + return false for (tpl <- inTpl.sym.ownerChain) { tpl.info.member(bSym.name) match { @@ -137,8 +141,16 @@ trait ModelFactoryTypeSupport { val prefix = if (!settings.docNoPrefixes.value && needsPrefix && (bSym != AnyRefClass /* which we normalize */)) { - val qualifiedName = makeQualifiedName(owner, Some(inTpl.sym)) - if (qualifiedName != "") qualifiedName + "." else "" + if (!owner.isRefinementClass) { + val qName = makeQualifiedName(owner, Some(inTpl.sym)) + if (qName != "") qName + "." else "" + } + else { + nameBuffer append "(" + appendType0(pre) + nameBuffer append ")#" + "" // we already appended the prefix + } } else "" //DEBUGGING: @@ -175,15 +187,117 @@ trait ModelFactoryTypeSupport { case NullaryMethodType(result) => nameBuffer append '⇒' appendType0(result) + /* Polymorphic types */ case PolyType(tparams, result) => assert(tparams.nonEmpty) -// throw new Error("Polymorphic type '" + tpe + "' cannot be printed as a type") def typeParamsToString(tps: List[Symbol]): String = if (tps.isEmpty) "" else tps.map{tparam => tparam.varianceString + tparam.name + typeParamsToString(tparam.typeParams) }.mkString("[", ", ", "]") nameBuffer append typeParamsToString(tparams) appendType0(result) + + case et@ExistentialType(quantified, underlying) => + + def appendInfoStringReduced(sym: Symbol, tp: Type): Unit = { + if (sym.isType && !sym.isAliasType && !sym.isClass) { + tp match { + case PolyType(tparams, _) => + nameBuffer append "[" + appendTypes0(tparams.map(_.tpe), ", ") + nameBuffer append "]" + case _ => + } + tp.resultType match { + case rt @ TypeBounds(_, _) => + appendType0(rt) + case rt => + nameBuffer append " <: " + appendType0(rt) + } + } else { + // fallback to the Symbol infoString + nameBuffer append sym.infoString(tp) + } + } + + def appendClauses = { + nameBuffer append " forSome {" + var first = true + val qset = quantified.toSet + for (sym <- quantified) { + if (!first) { nameBuffer append ", " } else first = false + if (sym.isSingletonExistential) { + nameBuffer append "val " + nameBuffer append tpnme.dropSingletonName(sym.name) + nameBuffer append ": " + appendType0(dropSingletonType(sym.info.bounds.hi)) + } else { + if (sym.flagString != "") nameBuffer append (sym.flagString + " ") + if (sym.keyString != "") nameBuffer append (sym.keyString + " ") + nameBuffer append sym.varianceString + nameBuffer append sym.nameString + appendInfoStringReduced(sym, sym.info) + } + } + nameBuffer append "}" + } + + underlying match { + case TypeRef(pre, sym, args) if et.isRepresentableWithWildcards => + appendType0(typeRef(pre, sym, Nil)) + nameBuffer append "[" + var first = true + val qset = quantified.toSet + for (arg <- args) { + if (!first) { nameBuffer append ", " } else first = false + arg match { + case TypeRef(_, sym, _) if (qset contains sym) => + nameBuffer append "_" + appendInfoStringReduced(sym, sym.info) + case arg => + appendType0(arg) + } + } + nameBuffer append "]" + case MethodType(_, _) | NullaryMethodType(_) | PolyType(_, _) => + nameBuffer append "(" + appendType0(underlying) + nameBuffer append ")" + appendClauses + case _ => + appendType0(underlying) + appendClauses + } + + case tb@TypeBounds(lo, hi) => + if (tb.lo != TypeBounds.empty.lo) { + nameBuffer append " >: " + appendType0(lo) + } + if (tb.hi != TypeBounds.empty.hi) { + nameBuffer append " <: " + appendType0(hi) + } + // case tpen: ThisType | SingleType | SuperType => + // if (tpen.isInstanceOf[ThisType] && tpen.asInstanceOf[ThisType].sym.isEffectiveRoot) { + // appendType0 typeRef(NoPrefix, sym, Nil) + // } else { + // val underlying = + // val pre = underlying.typeSymbol.skipPackageObject + // if (pre.isOmittablePrefix) pre.fullName + ".type" + // else prefixString + "type" + case tpen@ThisType(sym) => + appendType0(typeRef(NoPrefix, sym, Nil)) + nameBuffer append ".this" + if (!tpen.underlying.typeSymbol.skipPackageObject.isOmittablePrefix) nameBuffer append ".type" + case tpen@SuperType(thistpe, supertpe) => + nameBuffer append "super[" + appendType0(supertpe) + nameBuffer append "]" + case tpen@SingleType(pre, sym) => + appendType0(typeRef(pre, sym, Nil)) + if (!tpen.underlying.typeSymbol.skipPackageObject.isOmittablePrefix) nameBuffer append ".type" case tpen => nameBuffer append tpen.toString } diff --git a/test/scaladoc/resources/SI-3314.scala b/test/scaladoc/resources/SI-3314.scala index e5773a4970..9e0afdce9d 100644 --- a/test/scaladoc/resources/SI-3314.scala +++ b/test/scaladoc/resources/SI-3314.scala @@ -1,5 +1,6 @@ package scala.test.scaladoc { + // testing inherited templates (Enum.Value is included in the source, thus is documented in scaladoc) package test1 { class Enum { abstract class Value @@ -12,6 +13,8 @@ package scala.test.scaladoc { } } + // testing inherited templates (scala.Enumeration.Value is taken from the library, thus is not + // documented in the scaladoc pages -- but should be inherited to make things clear!) package test2 { trait WeekDayTrait extends Enumeration { type WeekDay = Value @@ -67,4 +70,16 @@ package scala.test.scaladoc { def isWorkingDay9(d: WeekDayObject.Value) = ! (d == Sat || d == Sun) } } + + // testing type lambdas and useless prefixes (should be referenced as T instead of foo.T in the first example) + package test3 { + import language.higherKinds + object `package` { + trait T + trait A + trait X + def foo[T](x: T) = 7 + def bar[A](x: ({type Lambda[X] <: Either[A, X]})#Lambda[String]) = 5 + } + } } diff --git a/test/scaladoc/run/SI-3314.scala b/test/scaladoc/run/SI-3314.scala index 3b5c658078..fe220b08af 100644 --- a/test/scaladoc/run/SI-3314.scala +++ b/test/scaladoc/run/SI-3314.scala @@ -6,7 +6,7 @@ object Test extends ScaladocModelTest { override def resourceFile = "SI-3314.scala" // no need for special settings - def scaladocSettings = "" + def scaladocSettings = "-feature" def testModel(rootPackage: Package) = { // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) @@ -15,6 +15,10 @@ object Test extends ScaladocModelTest { // just need to check the member exists, access methods will throw an error if there's a problem val base = rootPackage._package("scala")._package("test")._package("scaladoc") + + + // test1 + val test1 = base._package("test1") val test1Value = test1._class("Enum")._method("Value").resultType assert(test1Value.name == "Value", test1Value.name + " == Value") @@ -26,6 +30,9 @@ object Test extends ScaladocModelTest { assert(test1Constants.refEntity(0)._1 == LinkToMember(test1._object("Constants")._class("Value"), test1._object("Constants")), test1Constants.refEntity(0)._1 + " == LinkToMember(test1.Enum.Value)") + + // test2 + val test2 = base._package("test2") def testDefinition(doc: DocTemplateEntity) = { for (day <- List("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")) { @@ -70,5 +77,15 @@ object Test extends ScaladocModelTest { testUsage(test2._object("UserObject")) testUsage(test2._class("UserClass")) testUsage(test2._trait("UserTrait")) + + + // test3 + val test3 = base._package("test3") + val foo = test3._method("foo") + assert(foo.valueParams(0)(0).resultType.name == "T", + foo.valueParams(0)(0).resultType.name + " == T") + val bar = test3._method("bar") + assert(bar.valueParams(0)(0).resultType.name == "(AnyRef { type Lambda[X] <: Either[A,X] })#Lambda[String]", + bar.valueParams(0)(0).resultType.name + " == (AnyRef { type Lambda[X] <: Either[A,X] })#Lambda[String]") } } \ No newline at end of file diff --git a/test/scaladoc/run/SI-4887.check b/test/scaladoc/run/SI-4887.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/SI-4887.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/SI-4887.scala b/test/scaladoc/run/SI-4887.scala new file mode 100644 index 0000000000..af83344613 --- /dev/null +++ b/test/scaladoc/run/SI-4887.scala @@ -0,0 +1,46 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def code = """ + package scala.test.scaladoc.existentials { + import language.higherKinds + import language.existentials + + class X[T, U, V] + + trait TEST { + type T + type U + type A + def foo1(x: X[T, U, _]) = 3 + def foo2(x: X[Z[_], U, z.type] forSome {type Z[_] <: { def z: String }; val z: Z[_ <: Int]}) = 4 + def foo3(x: X[Z, Z, V] forSome { type Z <: T; type V <: T }) = 6 + } + } + """ + + // no need for special settings + def scaladocSettings = "-feature" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("existentials") + val TEST = base._trait("TEST") + + val foo1 = TEST._method("foo1") + assert(foo1.valueParams(0)(0).resultType.name == "X[T, U, _]", + foo1.valueParams(0)(0).resultType.name + " == X[T, U, _]") + + val foo2 = TEST._method("foo2") + assert(foo2.valueParams(0)(0).resultType.name == "X[Z[_], U, _ <: [_]AnyRef { def z: String } with Singleton]", + foo2.valueParams(0)(0).resultType.name + " == X[Z[_], U, _ <: [_]AnyRef { def z: String } with Singleton]") + + val foo3 = TEST._method("foo3") + assert(foo3.valueParams(0)(0).resultType.name == "X[Z, Z, V] forSome {type Z <: T, type V <: T}", + foo3.valueParams(0)(0).resultType.name + " == X[Z, Z, V] forSome {type Z <: T, type V <: T}") + } +} \ No newline at end of file -- cgit v1.2.3 From dc70d1b7bd193ff42e9bed5d80f632cffb85a667 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Thu, 12 Jul 2012 00:31:25 +0200 Subject: SI-3695 SI-4224 SI-4497 SI-5079 scaladoc links Adds the ability to link to members, classes and objects in scaladoc. The links can now be either qualified names or relative names, they both work. See the test/scaladoc/resources/links.scala for a usage example. Also introduced -no-link-warnings scaladoc flag, in case the build output gets swamped with link warnings. --- src/compiler/scala/tools/nsc/doc/DocFactory.scala | 7 +- src/compiler/scala/tools/nsc/doc/Settings.scala | 7 +- .../scala/tools/nsc/doc/html/HtmlPage.scala | 45 ++--- .../scala/tools/nsc/doc/html/page/Template.scala | 23 +-- .../scala/tools/nsc/doc/model/Entity.scala | 19 +++ src/compiler/scala/tools/nsc/doc/model/Links.scala | 24 +++ .../scala/tools/nsc/doc/model/MemberLookup.scala | 185 +++++++++++++++++++++ .../scala/tools/nsc/doc/model/ModelFactory.scala | 67 +++++--- .../nsc/doc/model/ModelFactoryTypeSupport.scala | 4 +- .../scala/tools/nsc/doc/model/TypeEntity.scala | 5 - .../scala/tools/nsc/doc/model/comment/Body.scala | 7 +- .../nsc/doc/model/comment/CommentFactory.scala | 55 +++--- .../scala/tools/partest/ScaladocModelTest.scala | 13 +- test/scaladoc/resources/links.scala | 57 +++++++ test/scaladoc/run/SI-5235.scala | 4 +- test/scaladoc/run/links.check | 1 + test/scaladoc/run/links.scala | 28 ++++ test/scaladoc/scalacheck/CommentFactoryTest.scala | 16 +- 18 files changed, 459 insertions(+), 108 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/doc/model/Links.scala create mode 100644 src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala create mode 100644 test/scaladoc/resources/links.scala create mode 100644 test/scaladoc/run/links.check create mode 100644 test/scaladoc/run/links.scala diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala index 964227a6a5..9fccc57e44 100644 --- a/src/compiler/scala/tools/nsc/doc/DocFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/DocFactory.scala @@ -48,8 +48,8 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor /** Creates a scaladoc site for all symbols defined in this call's `source`, * as well as those defined in `sources` of previous calls to the same processor. - * @param files The list of paths (relative to the compiler's source path, - * or absolute) of files to document. */ + * @param source The list of paths (relative to the compiler's source path, + * or absolute) of files to document or the source code. */ def makeUniverse(source: Either[List[String], String]): Option[Universe] = { assert(settings.docformat.value == "html") source match { @@ -84,7 +84,8 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor with model.ModelFactoryTypeSupport with model.diagram.DiagramFactory with model.comment.CommentFactory - with model.TreeFactory { + with model.TreeFactory + with model.MemberLookup { override def templateShouldDocument(sym: compiler.Symbol, inTpl: TemplateImpl) = extraTemplatesToDocument(sym) || super.templateShouldDocument(sym, inTpl) } diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index aae88aa03e..da478121e5 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -171,6 +171,11 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) "Prevents generating prefixes in types, possibly creating ambiguous references, but significantly speeding up scaladoc." ) + val docNoLinkWarnings = BooleanSetting ( + "-no-link-warnings", + "Avoid warnings for ambiguous and incorrect links." + ) + // Somewhere slightly before r18708 scaladoc stopped building unless the // self-type check was suppressed. I hijacked the slotted-for-removal-anyway // suppress-vt-warnings option and renamed it for this purpose. @@ -183,7 +188,7 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) docDiagramsDotTimeout, docDiagramsDotRestart, docImplicits, docImplicitsDebug, docImplicitsShowAll, docDiagramsMaxNormalClasses, docDiagramsMaxImplicitClasses, - docNoPrefixes + docNoPrefixes, docNoLinkWarnings, docRawOutput ) val isScaladocSpecific: String => Boolean = scaladocSpecific map (_.name) diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala index af5e90083e..226ed49aca 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala @@ -122,17 +122,30 @@ abstract class HtmlPage extends Page { thisPage => case Text(text) => xml.Text(text) case Summary(in) => inlineToHtml(in) case HtmlTag(tag) => xml.Unparsed(tag) - case EntityLink(target, template) => template() match { - case Some(tpl) => - templateToHtml(tpl) - case None => - xml.Text(target) - } + case EntityLink(target, link) => linkToHtml(target, link, true) + } + + def linkToHtml(text: Inline, link: LinkTo, hasLinks: Boolean) = link match { + case LinkToTpl(dtpl) => + if (hasLinks) + { inlineToHtml(text) } + else + { inlineToHtml(text) } + case LinkToMember(mbr, inTpl) => + if (hasLinks) + { inlineToHtml(text) } + else + { inlineToHtml(text) } + case Tooltip(tooltip) => + { inlineToHtml(text) } + // TODO: add case LinkToExternal here + case NoLink => + inlineToHtml(text) } def typeToHtml(tpes: List[model.TypeEntity], hasLinks: Boolean): NodeSeq = tpes match { case Nil => - sys.error("Internal Scaladoc error") + NodeSeq.Empty case List(tpe) => typeToHtml(tpe, hasLinks) case tpe :: rest => @@ -153,21 +166,9 @@ abstract class HtmlPage extends Page { thisPage => } } def toLinksIn(inPos: Int, starts: List[Int]): NodeSeq = { - val (tpl, width) = tpe.refEntity(inPos) - (tpl match { - case LinkToTpl(dtpl:DocTemplateEntity) if hasLinks => - { - string.slice(inPos, inPos + width) - } - case LinkToTpl(tpl) => - { string.slice(inPos, inPos + width) } - case LinkToMember(mbr, inTpl) if hasLinks => - { - string.slice(inPos, inPos + width) - } - case LinkToMember(mbr, inTpl) => - { string.slice(inPos, inPos + width) } - }) ++ toLinksOut(inPos + width, starts.tail) + val (link, width) = tpe.refEntity(inPos) + val text = comment.Text(string.slice(inPos, inPos + width)) + linkToHtml(text, link, hasLinks) ++ toLinksOut(inPos + width, starts.tail) } if (hasLinks) toLinksOut(0, tpe.refEntity.keySet.toList) diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index bba838ddcf..cfd50db99f 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -64,7 +64,7 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp nonAbsValueMembers partition (_.deprecation.isDefined) val (concValueMembers, shadowedImplicitMembers) = - nonDeprValueMembers partition (!Template.isShadowedOrAmbiguousImplicit(_)) + nonDeprValueMembers partition (!_.isShadowedOrAmbiguousImplicit) val typeMembers = tpl.abstractTypes ++ tpl.aliasTypes ++ tpl.templates.filter(x => x.isTrait || x.isClass) sorted @@ -387,7 +387,7 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp { constraintText } } ++ { - if (Template.isShadowedOrAmbiguousImplicit(mbr)) { + if (mbr.isShadowedOrAmbiguousImplicit) { // These are the members that are shadowing or ambiguating the current implicit // see ImplicitMemberShadowing trait for more information val shadowingSuggestion = { @@ -402,10 +402,10 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp } val shadowingWarning: NodeSeq = - if (Template.isShadowedImplicit(mbr)) + if (mbr.isShadowedImplicit) xml.Text("This implicitly inherited member is shadowed by one or more members in this " + "class.") ++ shadowingSuggestion - else if (Template.isAmbiguousImplicit(mbr)) + else if (mbr.isAmbiguousImplicit) xml.Text("This implicitly inherited member is ambiguous. One or more implicitly " + "inherited members have similar signatures, so calling this member may produce an ambiguous " + "implicit conversion compiler error.") ++ shadowingSuggestion @@ -646,13 +646,13 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp case PrivateInTemplate(owner) if (owner == mbr.inTemplate) => Some(Paragraph(CText("private"))) case PrivateInTemplate(owner) => - Some(Paragraph(Chain(List(CText("private["), EntityLink(owner.qualifiedName, () => Some(owner)), CText("]"))))) + Some(Paragraph(Chain(List(CText("private["), EntityLink(comment.Text(owner.qualifiedName), LinkToTpl(owner)), CText("]"))))) case ProtectedInInstance() => Some(Paragraph(CText("protected[this]"))) case ProtectedInTemplate(owner) if (owner == mbr.inTemplate) => Some(Paragraph(CText("protected"))) case ProtectedInTemplate(owner) => - Some(Paragraph(Chain(List(CText("protected["), EntityLink(owner.qualifiedName, () => Some(owner)), CText("]"))))) + Some(Paragraph(Chain(List(CText("protected["), EntityLink(comment.Text(owner.qualifiedName), LinkToTpl(owner)), CText("]"))))) case Public() => None } @@ -669,8 +669,8 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp { val nameClass = - if (Template.isImplicit(mbr)) - if (Template.isShadowedOrAmbiguousImplicit(mbr)) + if (mbr.isImplicitlyInherited) + if (mbr.isShadowedOrAmbiguousImplicit) "implicit shadowed" else "implicit" @@ -934,12 +934,5 @@ object Template { /* Vlad: Lesson learned the hard way: don't put any stateful code that references the model here, * it won't be garbage collected and you'll end up filling the heap with garbage */ - def isImplicit(mbr: MemberEntity) = mbr.byConversion.isDefined - def isShadowedImplicit(mbr: MemberEntity): Boolean = - mbr.byConversion.map(_.source.implicitsShadowing.get(mbr).map(_.isShadowed).getOrElse(false)).getOrElse(false) - def isAmbiguousImplicit(mbr: MemberEntity): Boolean = - mbr.byConversion.map(_.source.implicitsShadowing.get(mbr).map(_.isAmbiguous).getOrElse(false)).getOrElse(false) - def isShadowedOrAmbiguousImplicit(mbr: MemberEntity) = isShadowedImplicit(mbr) || isAmbiguousImplicit(mbr) - def lowerFirstLetter(s: String) = if (s.length >= 1) s.substring(0,1).toLowerCase() + s.substring(1) else s } diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index 41ba95e072..42db408fee 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -56,6 +56,12 @@ trait Entity { /** Whether or not the template was defined in a package object */ def inPackageObject: Boolean + + /** Indicates whether this entity lives in the types namespace (classes, traits, abstract/alias types) */ + def isType: Boolean + + /** Indicates whether this entity lives in the terms namespace (objects, packages, methods, values) */ + def isTerm: Boolean } object Entity { @@ -183,6 +189,19 @@ trait MemberEntity extends Entity { /** The identity of this member, used for linking */ def signature: String + + /** Indicates whether the member is inherited by implicit conversion */ + def isImplicitlyInherited: Boolean + + /** Indicates whether there is another member with the same name in the template that will take precendence */ + def isShadowedImplicit: Boolean + + /** Indicates whether there are other implicitly inherited members that have similar signatures (and thus they all + * become ambiguous) */ + def isAmbiguousImplicit: Boolean + + /** Indicates whether the implicitly inherited member is shadowed or ambiguous in its template */ + def isShadowedOrAmbiguousImplicit: Boolean } object MemberEntity { diff --git a/src/compiler/scala/tools/nsc/doc/model/Links.scala b/src/compiler/scala/tools/nsc/doc/model/Links.scala new file mode 100644 index 0000000000..b76dee0f14 --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/model/Links.scala @@ -0,0 +1,24 @@ +/* NSC -- new Scala compiler + * Copyright 2007-2011 LAMP/EPFL + */ + +package scala.tools.nsc +package doc +package model + +import scala.collection._ + +abstract sealed class LinkTo +case class LinkToTpl(tpl: DocTemplateEntity) extends LinkTo +case class LinkToMember(mbr: MemberEntity, inTpl: DocTemplateEntity) extends LinkTo +case class Tooltip(name: String) extends LinkTo { def this(tpl: TemplateEntity) = this(tpl.qualifiedName) } +// case class LinkToExternal(name: String, url: String) extends LinkTo // for SI-191, whenever Manohar will have time +case object NoLink extends LinkTo // you should use Tooltip if you have a name from the user, this is only in case all fails + +object LinkToTpl { + // this makes it easier to create links + def apply(tpl: TemplateEntity) = tpl match { + case dtpl: DocTemplateEntity => new LinkToTpl(dtpl) + case ntpl: TemplateEntity => new Tooltip(ntpl.qualifiedName) + } +} diff --git a/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala b/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala new file mode 100644 index 0000000000..7b131c13ef --- /dev/null +++ b/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala @@ -0,0 +1,185 @@ +package scala.tools.nsc +package doc +package model + +import comment._ + +import scala.reflect.internal.util.FakePos //Position + +/** This trait extracts all required information for documentation from compilation units */ +trait MemberLookup { + thisFactory: ModelFactory => + + import global._ + + def memberLookup(pos: Position, query: String, inTplOpt: Option[DocTemplateImpl]): LinkTo = { + assert(modelFinished) + + var members = breakMembers(query) + //println(query + " => " + members) + + // (1) Lookup in the root package, as most of the links are qualified + var linkTo: List[LinkTo] = lookupInRootPackage(pos, members) + + // (2) Recursively go into each + if (inTplOpt.isDefined) { + var currentTpl = inTplOpt.get + while (currentTpl != null && !currentTpl.isRootPackage && (linkTo.isEmpty)) { + linkTo = lookupInTemplate(pos, members, currentTpl) + currentTpl = currentTpl.inTemplate + } + if (currentTpl == null) println("\n\n\n\n\nnull found in:" + inTplOpt + "\n\n\n\n\n\n\n\n") + } + + // (3) Look at external links + if (linkTo.isEmpty) { + // TODO: IF THIS IS THE ROOT PACKAGE, LOOK AT EXTERNAL LINKS + } + + // (4) if we still haven't found anything, create a tooltip, if we found too many, report + if (linkTo.isEmpty){ + if (!settings.docNoLinkWarnings.value) + reporter.warning(pos, "Could not find any member to link for \"" + query + "\".") + Tooltip(query) + } else { + if (linkTo.length > 1) { + + val chosen = + if (linkTo.exists(_.isInstanceOf[LinkToMember])) + linkTo.collect({case lm: LinkToMember => lm}).min(Ordering[MemberEntity].on[LinkToMember](_.mbr)) + else + linkTo.head + + def linkToString(link: LinkTo) = { + val description = + link match { + case lm@LinkToMember(mbr, inTpl) => " * " + mbr.kind + " \"" + mbr.signature + "\" in " + inTpl.kind + " " + inTpl.qualifiedName + case lt@LinkToTpl(tpl) => " * " + tpl.kind + " \"" + tpl.qualifiedName + "\"" + case other => " * " + other.toString + } + val chosenInfo = + if (link == chosen) + " [chosen]" + else + "" + description + chosenInfo + "\n" + } + if (!settings.docNoLinkWarnings.value) + reporter.warning(pos, + "The link target \"" + query + "\" is ambiguous. Several (possibly overloaded) members fit the target:\n" + + linkTo.map(link => linkToString(link)).mkString + + (if (MemberLookup.showExplanation) + "\n\n" + + "Quick crash course on using Scaladoc links\n" + + "==========================================\n" + + "Disambiguating terms and types: Prefix terms with '$' and types with '!' in case both names are in use:\n" + + " - [[scala.collection.immutable.List!.apply class List's apply method]] and\n" + + " - [[scala.collection.immutable.List$.apply object List's apply method]]\n" + + "Disambiguating overloaded members: If a term is overloaded, you can indicate the first part of its signature followed by *:\n" + + " - [[[scala.collection.immutable.List$.fill[A](Int)(⇒A):List[A]* Fill with a single parameter]]]\n" + + " - [[[scala.collection.immutable.List$.fill[A](Int,Int)(⇒A):List[List[A]]* Fill with a two parameters]]]\n" + + "Notes: \n" + + " - you can use any number of matching square brackets to avoid interference with the signature\n" + + " - you can use \\. to escape dots in prefixes (don't forget to use * at the end to match the signature!)\n" + + " - you can use \\# to escape hashes, otherwise they will be considered as delimiters, like dots.\n" + else "") + ) + chosen + } else + linkTo.head + } + } + + private abstract class SearchStrategy + private object BothTypeAndTerm extends SearchStrategy + private object OnlyType extends SearchStrategy + private object OnlyTerm extends SearchStrategy + + private def lookupInRootPackage(pos: Position, members: List[String]) = lookupInTemplate(pos, members, makeRootPackage) + + private def lookupInTemplate(pos: Position, members: List[String], inTpl: DocTemplateImpl): List[LinkTo] = { + // Maintaining compatibility with previous links is a bit tricky here: + // we have a preference for term names for all terms except for the last, where we prefer a class: + // How to do this: + // - at each step we do a DFS search with the prefered strategy + // - if the search doesn't return any members, we backtrack on the last decision + // * we look for terms with the last member's name + // * we look for types with the same name, all the way up + val result = members match { + case Nil => + Nil + case mbrName::Nil => + var members = lookupInTemplate(pos, mbrName, inTpl, OnlyType) + if (members.isEmpty) + members = lookupInTemplate(pos, mbrName, inTpl, OnlyTerm) + + members.map(_ match { + case tpl: DocTemplateEntity => LinkToTpl(tpl) + case mbr => LinkToMember(mbr, inTpl) + }) + + case tplName::rest => + + def completeSearch(mbrs: List[MemberImpl]) = + mbrs.collect({case d:DocTemplateImpl => d}).flatMap(tpl => lookupInTemplate(pos, rest, tpl)) + + var members = completeSearch(lookupInTemplate(pos, tplName, inTpl, OnlyTerm)) + if (members.isEmpty) + members = completeSearch(lookupInTemplate(pos, tplName, inTpl, OnlyType)) + + members + } + //println("lookupInTemplate(" + members + ", " + inTpl + ") => " + result) + result + } + + private def lookupInTemplate(pos: Position, member: String, inTpl: DocTemplateImpl, strategy: SearchStrategy): List[MemberImpl] = { + val name = member.stripSuffix("$").stripSuffix("!").stripSuffix("*") + val result = if (member.endsWith("$")) + inTpl.members.filter(mbr => (mbr.name == name) && (mbr.isTerm)) + else if (member.endsWith("!")) + inTpl.members.filter(mbr => (mbr.name == name) && (mbr.isType)) + else if (member.endsWith("*")) + inTpl.members.filter(mbr => (mbr.signature.startsWith(name))) + else { + if (strategy == BothTypeAndTerm) + inTpl.members.filter(_.name == name) + else if (strategy == OnlyType) + inTpl.members.filter(mbr => (mbr.name == name) && (mbr.isType)) + else if (strategy == OnlyTerm) + inTpl.members.filter(mbr => (mbr.name == name) && (mbr.isTerm)) + else + Nil + } + + //println("lookupInTemplate(" + member + ", " + inTpl + ") => " + result) + result + } + + private def breakMembers(query: String): List[String] = { + // Okay, how does this work? Well: you split on . but you don't want to split on \. => thus the ugly regex + // query.split((?<=[^\\\\])\\.).map(_.replaceAll("\\.")) + // The same code, just faster: + var members = List[String]() + var index = 0 + var last_index = 0 + val length = query.length + while (index < length) { + if ((query.charAt(index) == '.' || query.charAt(index) == '#') && + ((index == 0) || (query.charAt(index-1) != '\\'))) { + + members ::= query.substring(last_index, index).replaceAll("\\\\([#\\.])", "$1") + last_index = index + 1 + } + index += 1 + } + if (last_index < length) + members ::= query.substring(last_index, length).replaceAll("\\\\\\.", ".") + members.reverse + } +} + +object MemberLookup { + private[this] var _showExplanation = true + def showExplanation: Boolean = if (_showExplanation) { _showExplanation = false; true } else false +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 2ce7927f42..2642551aa8 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -24,7 +24,8 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { with ModelFactoryTypeSupport with DiagramFactory with CommentFactory - with TreeFactory => + with TreeFactory + with MemberLookup => import global._ import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass } @@ -92,6 +93,8 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def qualifiedName = name def annotations = sym.annotations.map(makeAnnotation) def inPackageObject: Boolean = sym.owner.isModuleClass && sym.owner.sourceModule.isPackageObject + def isType = sym.name.isTypeName + def isTerm = sym.name.isTermName } trait TemplateImpl extends EntityImpl with TemplateEntity { @@ -108,7 +111,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { abstract class MemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl) extends EntityImpl(sym, inTpl) with MemberEntity { lazy val comment = { - val commentTpl = + val inRealTpl = /* Variable precendence order for implicitly added members: Take the variable defifinitions from ... * 1. the target of the implicit conversion * 2. the definition template (owner) @@ -121,7 +124,11 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { case _ => inTpl } } else inTpl - if (commentTpl != null) thisFactory.comment(sym, commentTpl) else None + val thisTpl = this match { + case d: DocTemplateImpl => Some(d) + case _ => None + } + if (inRealTpl != null) thisFactory.comment(sym, thisTpl, inRealTpl) else None } override def inTemplate = inTpl override def toRoot: List[MemberImpl] = this :: inTpl.toRoot @@ -167,9 +174,9 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def deprecation = if (sym.isDeprecated) Some((sym.deprecationMessage, sym.deprecationVersion) match { - case (Some(msg), Some(ver)) => parseWiki("''(Since version " + ver + ")'' " + msg, NoPosition) - case (Some(msg), None) => parseWiki(msg, NoPosition) - case (None, Some(ver)) => parseWiki("''(Since version " + ver + ")''", NoPosition) + case (Some(msg), Some(ver)) => parseWiki("''(Since version " + ver + ")'' " + msg, NoPosition, Some(inTpl)) + case (Some(msg), None) => parseWiki(msg, NoPosition, Some(inTpl)) + case (None, Some(ver)) => parseWiki("''(Since version " + ver + ")''", NoPosition, Some(inTpl)) case (None, None) => Body(Nil) }) else @@ -177,9 +184,9 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def migration = if(sym.hasMigrationAnnotation) Some((sym.migrationMessage, sym.migrationVersion) match { - case (Some(msg), Some(ver)) => parseWiki("''(Changed in version " + ver + ")'' " + msg, NoPosition) - case (Some(msg), None) => parseWiki(msg, NoPosition) - case (None, Some(ver)) => parseWiki("''(Changed in version " + ver + ")''", NoPosition) + case (Some(msg), Some(ver)) => parseWiki("''(Changed in version " + ver + ")'' " + msg, NoPosition, Some(inTpl)) + case (Some(msg), None) => parseWiki(msg, NoPosition, Some(inTpl)) + case (None, Some(ver)) => parseWiki("''(Changed in version " + ver + ")''", NoPosition, Some(inTpl)) case (None, None) => Body(Nil) }) else @@ -213,28 +220,34 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def byConversion = if (implConv ne null) Some(implConv) else None lazy val signature = { - val defParamsString = this match { + def defParams(mbr: Any): String = mbr match { case d: MemberEntity with Def => val paramLists: List[String] = - if (d.valueParams.isEmpty) Nil - else d.valueParams map (ps => ps map (_.resultType.name) mkString ("(",",",")")) - - val tParams = if (d.typeParams.isEmpty) "" else { - def boundsToString(hi: Option[TypeEntity], lo: Option[TypeEntity]): String = { - def bound0(bnd: Option[TypeEntity], pre: String): String = bnd match { - case None => "" - case Some(tpe) => pre ++ tpe.toString - } - bound0(hi, "<:") ++ bound0(lo, ">:") + if (d.valueParams.isEmpty) Nil + else d.valueParams map (ps => ps map (_.resultType.name) mkString ("(",",",")")) + paramLists.mkString + case _ => "" + } + + def tParams(mbr: Any): String = mbr match { + case hk: HigherKinded if !hk.typeParams.isEmpty => + def boundsToString(hi: Option[TypeEntity], lo: Option[TypeEntity]): String = { + def bound0(bnd: Option[TypeEntity], pre: String): String = bnd match { + case None => "" + case Some(tpe) => pre ++ tpe.toString } - "[" + d.typeParams.map(tp => tp.variance + tp.name + boundsToString(tp.hi, tp.lo)).mkString(", ") + "]" + bound0(hi, "<:") ++ bound0(lo, ">:") } - - tParams + paramLists.mkString + "[" + hk.typeParams.map(tp => tp.variance + tp.name + tParams(tp) + boundsToString(tp.hi, tp.lo)).mkString(", ") + "]" case _ => "" } - name + defParamsString +":"+ resultType.name + + (name + tParams(this) + defParams(this) +":"+ resultType.name).replaceAll("\\s","") // no spaces allowed, they break links } + def isImplicitlyInherited = { assert(modelFinished); byConversion.isDefined } + def isShadowedImplicit = isImplicitlyInherited && inTpl.implicitsShadowing.get(this).map(_.isShadowed).getOrElse(false) + def isAmbiguousImplicit = isImplicitlyInherited && inTpl.implicitsShadowing.get(this).map(_.isAmbiguous).getOrElse(false) + def isShadowedOrAmbiguousImplicit = isShadowedImplicit || isAmbiguousImplicit } /** A template that is not documented at all. The class is instantiated during lookups, to indicate that the class @@ -546,7 +559,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { import Streamable._ Path(settings.docRootContent.value) match { case f : File => { - val rootComment = closing(f.inputStream)(is => parse(slurp(is), "", NoPosition)) + val rootComment = closing(f.inputStream)(is => parse(slurp(is), "", NoPosition, Option(inTpl))) Some(rootComment) } case _ => None @@ -658,7 +671,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { if (bSym.isGetter && bSym.isLazy) Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with Val { override lazy val comment = // The analyser does not duplicate the lazy val's DocDef when it introduces its accessor. - thisFactory.comment(bSym.accessed, inTpl.asInstanceOf[DocTemplateImpl]) // This hack should be removed after analyser is fixed. + thisFactory.comment(bSym.accessed, None, inTpl.asInstanceOf[DocTemplateImpl]) // This hack should be removed after analyser is fixed. override def isLazyVal = true override def useCaseOf = _useCaseOf }) @@ -744,6 +757,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { inTpl.members.find(_.sym == aSym) } + @deprecated("2.10", "Use findLinkTarget instead!") def findTemplate(query: String): Option[DocTemplateImpl] = { assert(modelFinished) docTemplatesCache.values find { (tpl: DocTemplateImpl) => tpl.qualifiedName == query && !packageDropped(tpl) && !tpl.isObject } @@ -776,7 +790,6 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } } - /** */ def makeAnnotation(annot: AnnotationInfo): Annotation = { val aSym = annot.symbol diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala index 0463ac0420..5efae3257c 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala @@ -100,8 +100,8 @@ trait ModelFactoryTypeSupport { // (1) the owner's class LinkToMember(bMbr.get.get, oTpl.get) //ugh else - // (2) if we still couldn't find the owner, make a noDocTemplate for everything (in the correct owner!) - LinkToTpl(makeTemplate(bSym, Some(makeTemplate(owner)))) + // (2) if we still couldn't find the owner, show a tooltip with the qualified name + Tooltip(makeQualifiedName(bSym)) } // SI-4360 Showing prefixes when necessary diff --git a/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala b/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala index a16e99bf06..643067cca5 100644 --- a/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/TypeEntity.scala @@ -9,10 +9,6 @@ package model import scala.collection._ -abstract sealed class LinkTo -case class LinkToTpl(tpl: TemplateEntity) extends LinkTo -case class LinkToMember(mbr: MemberEntity, inTpl: DocTemplateEntity) extends LinkTo - /** A type. Note that types and templates contain the same information only for the simplest types. For example, a type * defines how a template's type parameters are instantiated (as in `List[Cow]`), what the template's prefix is * (as in `johnsFarm.Cow`), and supports compound or structural types. */ @@ -28,5 +24,4 @@ abstract class TypeEntity { /** The human-readable representation of this type. */ override def toString = name - } diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala b/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala index ecc3273903..3ab8fc7805 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala @@ -44,7 +44,6 @@ final case class Body(blocks: Seq[Block]) { case inlines => Some(Chain(inlines)) } } - } /** A block-level element of text, such as a paragraph or code block. */ @@ -67,10 +66,14 @@ final case class Bold(text: Inline) extends Inline final case class Underline(text: Inline) extends Inline final case class Superscript(text: Inline) extends Inline final case class Subscript(text: Inline) extends Inline -final case class EntityLink(target: String, template: () => Option[TemplateEntity]) extends Inline final case class Link(target: String, title: Inline) extends Inline final case class Monospace(text: Inline) extends Inline final case class Text(text: String) extends Inline +abstract class EntityLink(val title: Inline) extends Inline { def link: LinkTo } +object EntityLink { + def apply(title: Inline, linkTo: LinkTo) = new EntityLink(title) { def link: LinkTo = linkTo} + def unapply(el: EntityLink): Option[(Inline, LinkTo)] = Some((el.title, el.link)) +} final case class HtmlTag(data: String) extends Inline { def canClose(open: HtmlTag) = { open.data.stripPrefix("<") == data.stripPrefix(" +trait CommentFactory { thisFactory: ModelFactory with CommentFactory with MemberLookup=> val global: Global import global.{ reporter, definitions } @@ -31,16 +31,16 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => protected val commentCache = mutable.HashMap.empty[(global.Symbol, TemplateImpl), Comment] def addCommentBody(sym: global.Symbol, inTpl: TemplateImpl, docStr: String, docPos: global.Position): global.Symbol = { - commentCache += (sym, inTpl) -> parse(docStr, docStr, docPos) + commentCache += (sym, inTpl) -> parse(docStr, docStr, docPos, None) sym } - def comment(sym: global.Symbol, inTpl: DocTemplateImpl): Option[Comment] = { + def comment(sym: global.Symbol, currentTpl: Option[DocTemplateImpl], inTpl: DocTemplateImpl): Option[Comment] = { val key = (sym, inTpl) if (commentCache isDefinedAt key) Some(commentCache(key)) else { - val c = defineComment(sym, inTpl) + val c = defineComment(sym, currentTpl, inTpl) if (c isDefined) commentCache += (sym, inTpl) -> c.get c } @@ -50,7 +50,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => * cases we have to give some `inTpl` comments (parent class for example) * to the comment of the symbol. * This function manages some of those cases : Param accessor and Primary constructor */ - def defineComment(sym: global.Symbol, inTpl: DocTemplateImpl):Option[Comment] = { + def defineComment(sym: global.Symbol, currentTpl: Option[DocTemplateImpl], inTpl: DocTemplateImpl):Option[Comment] = { //param accessor case // We just need the @param argument, we put it into the body @@ -87,7 +87,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => else { val rawComment = global.expandedDocComment(sym, inTpl.sym).trim if (rawComment != "") { - val c = parse(rawComment, global.rawDocComment(sym), global.docCommentPos(sym)) + val tplOpt = if (currentTpl.isDefined) currentTpl else Some(inTpl) + val c = parse(rawComment, global.rawDocComment(sym), global.docCommentPos(sym), tplOpt) Some(c) } else None @@ -225,7 +226,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => * @param comment The expanded comment string (including start and end markers) to be parsed. * @param src The raw comment source string. * @param pos The position of the comment in source. */ - protected def parse(comment: String, src: String, pos: Position): Comment = { + protected def parse(comment: String, src: String, pos: Position, inTplOpt: Option[DocTemplateImpl] = None): Comment = { + assert(!inTplOpt.isDefined || inTplOpt.get != null) /** The cleaned raw comment as a list of lines. Cleaning removes comment * start and end markers, line start markers and unnecessary whitespace. */ @@ -351,7 +353,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => val tagsWithoutDiagram = tags.filterNot(pair => pair._1 == inheritDiagramTag || pair._1 == contentDiagramTag) val bodyTags: mutable.Map[TagKey, List[Body]] = - mutable.Map(tagsWithoutDiagram mapValues {tag => tag map (parseWiki(_, pos))} toSeq: _*) + mutable.Map(tagsWithoutDiagram mapValues {tag => tag map (parseWiki(_, pos, inTplOpt))} toSeq: _*) def oneTag(key: SimpleTagKey): Option[Body] = ((bodyTags remove key): @unchecked) match { @@ -384,7 +386,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => } val com = createComment ( - body0 = Some(parseWiki(docBody.toString, pos)), + body0 = Some(parseWiki(docBody.toString, pos, inTplOpt)), authors0 = allTags(SimpleTagKey("author")), see0 = allTags(SimpleTagKey("see")), result0 = oneTag(SimpleTagKey("return")), @@ -420,8 +422,10 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => * - Removed start-of-line star and one whitespace afterwards (if present). * - Removed all end-of-line whitespace. * - Only `endOfLine` is used to mark line endings. */ - def parseWiki(string: String, pos: Position): Body = { - new WikiParser(string, pos).document() + def parseWiki(string: String, pos: Position, inTplOpt: Option[DocTemplateImpl]): Body = { + assert(!inTplOpt.isDefined || inTplOpt.get != null) + + new WikiParser(string, pos, inTplOpt).document() } /** TODO @@ -429,7 +433,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => * @author Ingo Maier * @author Manohar Jonnalagedda * @author Gilles Dubochet */ - protected final class WikiParser(val buffer: String, pos: Position) extends CharReader(buffer) { wiki => + protected final class WikiParser(val buffer: String, pos: Position, inTplOpt: Option[DocTemplateImpl]) extends CharReader(buffer) { wiki => + assert(!inTplOpt.isDefined || inTplOpt.get != null) var summaryParsed = false @@ -617,7 +622,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => else if (check(",,")) subscript() else if (check("[[")) link() else { - readUntil { char == safeTagMarker || check("''") || char == '`' || check("__") || char == '^' || check(",,") || check("[[") || isInlineEnd || checkParaEnded || char == endOfLine } + readUntil { char == safeTagMarker || check("''") || char == '`' || check("__") || char == '^' || check(",,") || check("[[") || check("{{") || isInlineEnd || checkParaEnded || char == endOfLine } Text(getRead()) } } @@ -719,29 +724,27 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory => def link(): Inline = { val SchemeUri = """([^:]+:.*)""".r jump("[[") - readUntil { check("]]") || check(" ") } + var parens = 1 + readUntil { parens += 1; !check("[") } + getRead // clear the buffer + val start = "[" * parens + val stop = "]" * parens + //println("link with " + parens + " matching parens") + readUntil { check(stop) || check(" ") } val target = getRead() val title = - if (!check("]]")) Some({ + if (!check(stop)) Some({ jump(" ") - inline(check("]]")) + inline(check(stop)) }) else None - jump("]]") + jump(stop) (target, title) match { case (SchemeUri(uri), optTitle) => Link(uri, optTitle getOrElse Text(uri)) case (qualName, optTitle) => - optTitle foreach (text => reportError(pos, "entity link to " + qualName + " cannot have a custom title'" + text + "'")) - // XXX rather than warning here we should allow unqualified names - // to refer to members of the same package. The "package exists" - // exclusion is because [[scala]] is used in some scaladoc. - if (!qualName.contains(".") && !definitions.packageExists(qualName)) - reportError(pos, "entity link to " + qualName + " should be a fully qualified name") - - // move the template resolution as late as possible - EntityLink(qualName, () => findTemplate(qualName)) + new EntityLink(optTitle getOrElse Text(target)) { def link = memberLookup(pos, target, inTplOpt) } } } diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala index c89dd2cb8f..fb93e98726 100644 --- a/src/partest/scala/tools/partest/ScaladocModelTest.scala +++ b/src/partest/scala/tools/partest/ScaladocModelTest.scala @@ -12,7 +12,7 @@ import scala.tools.nsc.util.CommandLineParser import scala.tools.nsc.doc.{Settings, DocFactory, Universe} import scala.tools.nsc.doc.model._ import scala.tools.nsc.reporters.ConsoleReporter -import scala.tools.nsc.doc.model.comment.Comment +import scala.tools.nsc.doc.model.comment._ /** A class for testing scaladoc model generation * - you need to specify the code in the `code` method @@ -165,10 +165,21 @@ abstract class ScaladocModelTest extends DirectTest { def extractCommentText(c: Comment) = { def extractText(body: Any): String = body match { case s: String => s + case s: Seq[_] => s.toList.map(extractText(_)).mkString case p: Product => p.productIterator.toList.map(extractText(_)).mkString case _ => "" } extractText(c.body) } + + def countLinks(c: Comment, p: EntityLink => Boolean) = { + def countLinks(body: Any): Int = body match { + case el: EntityLink if p(el) => 1 + case s: Seq[_] => s.toList.map(countLinks(_)).sum + case p: Product => p.productIterator.toList.map(countLinks(_)).sum + case _ => 0 + } + countLinks(c.body) + } } } diff --git a/test/scaladoc/resources/links.scala b/test/scaladoc/resources/links.scala new file mode 100644 index 0000000000..679d0b0dce --- /dev/null +++ b/test/scaladoc/resources/links.scala @@ -0,0 +1,57 @@ +// that would be: +// SI-5079 "Scaladoc can't link to an object (only a class or trait)" +// SI-4497 "Links in ScalaDoc - Spec and implementation unsufficient" +// SI-4224 "Wiki-links should support method targets" +// SI-3695 "support non-fully-qualified type links in scaladoc comments" +package scala.test.scaladoc.links { + import language.higherKinds + class C + + trait Target { + type T + type S = String + class C + def foo(i: Int) = 2 + def foo(s: String) = 3 + def foo[A[_]](x: A[String]) = 5 + def foo[A[_[_]]](x: A[List]) = 6 + val bar: Boolean + def baz(c: scala.test.scaladoc.links.C) = 7 + } + + object Target { + type T = Int => Int + type S = Int + class C + def foo(i: Int) = 2 + def foo(z: String) = 3 + def foo[A[_]](x: A[String]) = 5 + def foo[A[_[_]]](x: A[List]) = 6 + val bar: Boolean = false + val onlyInObject = 1 + def baz(c: scala.test.scaladoc.links.C) = 7 + } + + /** + * Links to the trait: + * - [[scala.test.scaladoc.links.Target!.T trait Target -> type T]] + * - [[test.scaladoc.links.Target!.S trait Target -> type S]] + * - [[scaladoc.links.Target!.foo(Int)* trait Target -> def foo]] + * - [[links.Target!.bar trait Target -> def bar]] + * - [[[[Target!.foo[A[_[_]]]* trait Target -> def foo with 3 nested tparams]]]] (should exercise nested parens) + * - [[Target$.T object Target -> type T]] + * - [[Target$.S object Target -> type S]] + * - [[Target$.foo(Str* object Target -> def foo]] + * - [[Target$.bar object Target -> def bar]] + * - [[[[Target$.foo[A[_[_]]]* trait Target -> def foo with 3 nested tparams]]]] (should exercise nested parens) + * - [[Target.onlyInObject object Target -> def foo]] (should find the object) + * - [[Target$.C object Target -> class C]] (should link directly to C, not as a member) + * - [[Target!.C trait Target -> class C]] (should link directly to C, not as a member) + * - [[Target$.baz(links\.C)* object Target -> def baz]] (should use dots in prefix) + * - [[Target!.baz(links\.C)* trait Target -> def baz]] (should use dots in prefix) + * - [[localMethod object TEST -> localMethod]] (should use the current template to resolve link instead of inTpl, that's the package) + */ + object TEST { + def localMethod = 3 + } +} diff --git a/test/scaladoc/run/SI-5235.scala b/test/scaladoc/run/SI-5235.scala index f0c6e1cf17..6295fc7786 100644 --- a/test/scaladoc/run/SI-5235.scala +++ b/test/scaladoc/run/SI-5235.scala @@ -79,8 +79,8 @@ object Test extends ScaladocModelTest { assert(mcReverseType.name == "MyCollection",mcReverseType.name + " == MyCollection") assert(gcReverseType.refEntity(0)._1 == LinkToTpl(GenericColl), gcReverse.qualifiedName + "'s return type has a link to " + GenericColl.qualifiedName) - assert(!scReverseType.refEntity(0)._1.asInstanceOf[LinkToTpl].tpl.isDocTemplate, - scReverse.qualifiedName + "'s return type does not have links") + assert(scReverseType.refEntity(0)._1 == Tooltip("BullSh"), + scReverseType.refEntity(0)._1 + " == Tooltip(\"BullSh\")") assert(mcReverseType.refEntity(0)._1 == LinkToTpl(MyCollection), mcReverse.qualifiedName + "'s return type has a link to " + MyCollection.qualifiedName) } diff --git a/test/scaladoc/run/links.check b/test/scaladoc/run/links.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/links.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/links.scala b/test/scaladoc/run/links.scala new file mode 100644 index 0000000000..40ce6368ce --- /dev/null +++ b/test/scaladoc/run/links.scala @@ -0,0 +1,28 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest + +// SI-5079 "Scaladoc can't link to an object (only a class or trait)" +// SI-4497 "Links in ScalaDoc - Spec and implementation unsufficient" +// SI-4224 "Wiki-links should support method targets" +// SI-3695 "support non-fully-qualified type links in scaladoc comments" +object Test extends ScaladocModelTest { + + override def resourceFile = "links.scala" + + // no need for special settings + def scaladocSettings = "" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + // just need to check the member exists, access methods will throw an error if there's a problem + val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("links") + val TEST = base._object("TEST") + + val memberLinks = countLinks(TEST.comment.get, _.link.isInstanceOf[LinkToMember]) + val templateLinks = countLinks(TEST.comment.get, _.link.isInstanceOf[LinkToTpl]) + assert(memberLinks == 14, memberLinks + " == 14 (the member links in object TEST)") + assert(templateLinks == 2, templateLinks + " == 2 (the template links in object TEST)") + } +} \ No newline at end of file diff --git a/test/scaladoc/scalacheck/CommentFactoryTest.scala b/test/scaladoc/scalacheck/CommentFactoryTest.scala index b7869d5bf4..5e3141bdc0 100644 --- a/test/scaladoc/scalacheck/CommentFactoryTest.scala +++ b/test/scaladoc/scalacheck/CommentFactoryTest.scala @@ -10,7 +10,13 @@ import scala.tools.nsc.doc.model.diagram._ class Factory(val g: Global, val s: doc.Settings) extends doc.model.ModelFactory(g, s) { - thisFactory: Factory with ModelFactoryImplicitSupport with ModelFactoryTypeSupport with DiagramFactory with CommentFactory with doc.model.TreeFactory => + thisFactory: Factory + with ModelFactoryImplicitSupport + with ModelFactoryTypeSupport + with DiagramFactory + with CommentFactory + with doc.model.TreeFactory + with MemberLookup => def strip(c: Comment): Option[Inline] = { c.body match { @@ -31,7 +37,13 @@ object Test extends Properties("CommentFactory") { val settings = new doc.Settings((str: String) => {}) val reporter = new scala.tools.nsc.reporters.ConsoleReporter(settings) val g = new Global(settings, reporter) - (new Factory(g, settings) with ModelFactoryImplicitSupport with ModelFactoryTypeSupport with DiagramFactory with CommentFactory with doc.model.TreeFactory) + (new Factory(g, settings) + with ModelFactoryImplicitSupport + with ModelFactoryTypeSupport + with DiagramFactory + with CommentFactory + with doc.model.TreeFactory + with MemberLookup) } def parse(src: String, dst: Inline) = { -- cgit v1.2.3 From 740361b8ae5e9ac8c545b0be878bcae06070dcf0 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Tue, 10 Jul 2012 21:25:45 +0200 Subject: Scaladoc: Reducing the memory footprint - diagrams are not stored because they create many TypeEntities, nodes and edges -- now they are created on demand, so make sure you don't demand them twice! - eliminated the type entity cache, which was nearly useless (6s gain) but was preventing the GC from eliminating TypeEntities - an unsuccessful attempt at reducing the tons of :: garbage we're generating - there's 200MB-250MB of ::s during a typical 'ant docs.lib' --- src/compiler/scala/tools/nsc/doc/DocFactory.scala | 1 - .../scala/tools/nsc/doc/html/page/Template.scala | 26 ++++++++++--------- .../scala/tools/nsc/doc/model/ModelFactory.scala | 6 ++--- .../nsc/doc/model/ModelFactoryTypeSupport.scala | 30 +++++++++------------- .../nsc/doc/model/diagram/DiagramFactory.scala | 5 ++-- 5 files changed, 31 insertions(+), 37 deletions(-) diff --git a/src/compiler/scala/tools/nsc/doc/DocFactory.scala b/src/compiler/scala/tools/nsc/doc/DocFactory.scala index 9fccc57e44..27a03d5381 100644 --- a/src/compiler/scala/tools/nsc/doc/DocFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/DocFactory.scala @@ -101,7 +101,6 @@ class DocFactory(val reporter: Reporter, val settings: doc.Settings) { processor println("no documentable class found in compilation units") None } - } object NoCompilerRunException extends ControlThrowable { } diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index cfd50db99f..417bfcfb96 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -601,13 +601,13 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp } val typeHierarchy = if (s.docDiagrams.isSetByUser) mbr match { - case dtpl: DocTemplateEntity if isSelf && !isReduced && dtpl.inheritanceDiagram.isDefined => + case dtpl: DocTemplateEntity if isSelf && !isReduced => makeDiagramHtml(dtpl, dtpl.inheritanceDiagram, "Type Hierarchy", "inheritance-diagram") case _ => NodeSeq.Empty } else NodeSeq.Empty // diagrams not generated val contentHierarchy = if (s.docDiagrams.isSetByUser) mbr match { - case dtpl: DocTemplateEntity if isSelf && !isReduced && dtpl.contentDiagram.isDefined => + case dtpl: DocTemplateEntity if isSelf && !isReduced => makeDiagramHtml(dtpl, dtpl.contentDiagram, "Content Hierarchy", "content-diagram") case _ => NodeSeq.Empty } else NodeSeq.Empty // diagrams not generated @@ -916,16 +916,18 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp } def makeDiagramHtml(tpl: DocTemplateEntity, diagram: Option[Diagram], description: String, id: String) = { - val s = universe.settings - val diagramSvg = generator.generate(diagram.get, tpl, this) - if (diagramSvg != NodeSeq.Empty) { -
    - { description } - Learn more about scaladoc diagrams -
    { - diagramSvg - }
    -
    + if (diagram.isDefined) { + val s = universe.settings + val diagramSvg = generator.generate(diagram.get, tpl, this) + if (diagramSvg != NodeSeq.Empty) { +
    + { description } + Learn more about scaladoc diagrams +
    { + diagramSvg + }
    +
    + } else NodeSeq.Empty } else NodeSeq.Empty } } diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 2642551aa8..d3d229d848 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -449,9 +449,9 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { case _ => None } - // We make the diagram a lazy val, since we're not sure we'll include the diagrams in the page - lazy val inheritanceDiagram = makeInheritanceDiagram(this) - lazy val contentDiagram = makeContentDiagram(this) + // These are generated on-demand, make sure you don't call them more than once + def inheritanceDiagram = makeInheritanceDiagram(this) + def contentDiagram = makeContentDiagram(this) } abstract class PackageImpl(sym: Symbol, inTpl: PackageImpl) extends DocTemplateImpl(sym, inTpl) with Package { diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala index 5efae3257c..d2a26d1309 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryTypeSupport.scala @@ -30,8 +30,7 @@ trait ModelFactoryTypeSupport { import definitions.{ ObjectClass, NothingClass, AnyClass, AnyValClass, AnyRefClass } import rootMirror.{ RootPackage, RootClass, EmptyPackage } - protected var typeCache = new mutable.LinkedHashMap[(Type, TemplateImpl), TypeEntity] - protected var typeCacheNoPrefix = new mutable.LinkedHashMap[Type, TypeEntity] + protected var typeCache = new mutable.LinkedHashMap[Type, TypeEntity] /** */ def makeType(aType: Type, inTpl: TemplateImpl): TypeEntity = { @@ -309,21 +308,16 @@ trait ModelFactoryTypeSupport { // SI-4360: Entity caching depends on both the type AND the template it's in, as the prefixes might change for the // same type based on the template the type is shown in. - val cached = - if (!settings.docNoPrefixes.value) - typeCache.get((aType, inTpl)) - else - typeCacheNoPrefix.get(aType) - - cached match { - case Some(typeEntity) => typeEntity - case None => - val typeEntity = createTypeEntity - if (!settings.docNoPrefixes.value) - typeCache += (aType, inTpl) -> typeEntity - else - typeCacheNoPrefix += aType -> typeEntity - typeEntity - } + if (settings.docNoPrefixes.value) { + val cached = typeCache.get(aType) + cached match { + case Some(typeEntity) => + typeEntity + case None => + val typeEntity = createTypeEntity + typeCache += aType -> typeEntity + typeEntity + } + } else createTypeEntity } } \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala index d0b363854c..9b56509623 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala @@ -61,9 +61,8 @@ trait DiagramFactory extends DiagramDirectiveParser { // subclasses var subclasses: List[Node] = - tpl.directSubClasses.flatMap { - case d: TemplateImpl if !classExcluded(d) => List(NormalNode(makeType(d.sym.tpe, tpl), Some(d))()) - case _ => Nil + tpl.directSubClasses.collect { + case d: TemplateImpl if !classExcluded(d) => NormalNode(makeType(d.sym.tpe, tpl), Some(d))() }.sortBy(_.tpl.get.name)(implicitly[Ordering[String]].reverse) // outgoing implicit coversions -- cgit v1.2.3 From b651269275a4cfd72761586e088bff5a130949b5 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Wed, 11 Jul 2012 22:28:00 +0200 Subject: Scaladoc: Reducing the memory footprint 2 - got rid of references to WikiParser when creating EntityLinks --- src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala | 3 +++ src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala b/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala index 7b131c13ef..277e86f9af 100644 --- a/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala +++ b/src/compiler/scala/tools/nsc/doc/model/MemberLookup.scala @@ -12,6 +12,9 @@ trait MemberLookup { import global._ + def makeEntityLink(title: Inline, pos: Position, query: String, inTplOpt: Option[DocTemplateImpl]) = + new EntityLink(title) { lazy val link = memberLookup(pos, query, inTplOpt) } + def memberLookup(pos: Position, query: String, inTplOpt: Option[DocTemplateImpl]): LinkTo = { assert(modelFinished) 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 eff899b789..df913555a7 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala @@ -744,7 +744,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member case (SchemeUri(uri), optTitle) => Link(uri, optTitle getOrElse Text(uri)) case (qualName, optTitle) => - new EntityLink(optTitle getOrElse Text(target)) { def link = memberLookup(pos, target, inTplOpt) } + makeEntityLink(optTitle getOrElse Text(target), pos, target, inTplOpt) } } -- cgit v1.2.3 From 0f2a0b7441153f3bdac49ca8878ffd9215458918 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Wed, 11 Jul 2012 23:56:32 +0200 Subject: Scaladoc: updated type class descriptions The known type class descriptions give you a humanly-understandable explanation for [T: TypeClass] in the implicit conversions. For example [T: Integral] will tell you that T must be an integral type such as Integer and co. Now that the reflection dust settled, I can actually add the test to make sure the well-known type classes are intercepted and explanations are given instead of the usual "the type T is context-bounded by TypeClass" --- src/compiler/scala/tools/ant/Scaladoc.scala | 1 - src/compiler/scala/tools/nsc/doc/Settings.scala | 19 +++++------ .../scala/tools/nsc/doc/model/Entity.scala | 3 ++ .../doc/model/ModelFactoryImplicitSupport.scala | 8 +++-- .../implicits-known-type-classes-res.scala | 38 ++++++++++++++++++++++ .../run/implicits-known-type-classes.check | 1 + .../run/implicits-known-type-classes.scala | 33 +++++++++++++++++++ 7 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 test/scaladoc/resources/implicits-known-type-classes-res.scala create mode 100644 test/scaladoc/run/implicits-known-type-classes.check create mode 100644 test/scaladoc/run/implicits-known-type-classes.scala diff --git a/src/compiler/scala/tools/ant/Scaladoc.scala b/src/compiler/scala/tools/ant/Scaladoc.scala index 9aa2f6f921..61db3c7fa9 100644 --- a/src/compiler/scala/tools/ant/Scaladoc.scala +++ b/src/compiler/scala/tools/ant/Scaladoc.scala @@ -677,5 +677,4 @@ class Scaladoc extends ScalaMatchingTask { "(no error message provided); see the error output for details.") } } - } diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index da478121e5..12b27387a7 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -209,16 +209,15 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) * the function result should be a humanly-understandable description of the type class */ val knownTypeClasses: Map[String, String => String] = Map() + - // TODO: Bring up to date and test these - ("scala.package.Numeric" -> ((tparam: String) => tparam + " is a numeric class, such as Int, Long, Float or Double")) + - ("scala.package.Integral" -> ((tparam: String) => tparam + " is an integral numeric class, such as Int or Long")) + - ("scala.package.Fractional" -> ((tparam: String) => tparam + " is a fractional numeric class, such as Float or Double")) + - ("scala.reflect.Manifest" -> ((tparam: String) => tparam + " is accompanied by a Manifest, which is a runtime representation of its type that survives erasure")) + - ("scala.reflect.ClassManifest" -> ((tparam: String) => tparam + " is accompanied by a ClassManifest, which is a runtime representation of its type that survives erasure")) + - ("scala.reflect.OptManifest" -> ((tparam: String) => tparam + " is accompanied by an OptManifest, which can be either a runtime representation of its type or the NoManifest, which means the runtime type is not available")) + - ("scala.reflect.ClassTag" -> ((tparam: String) => tparam + " is accompanied by a ClassTag, which is a runtime representation of its type that survives erasure")) + - ("scala.reflect.AbsTypeTag" -> ((tparam: String) => tparam + " is accompanied by an AbsTypeTag, which is a runtime representation of its type that survives erasure")) + - ("scala.reflect.TypeTag" -> ((tparam: String) => tparam + " is accompanied by a TypeTag, which is a runtime representation of its type that survives erasure")) + ("scala.math.Numeric" -> ((tparam: String) => tparam + " is a numeric class, such as Int, Long, Float or Double")) + + ("scala.math.Integral" -> ((tparam: String) => tparam + " is an integral numeric class, such as Int or Long")) + + ("scala.math.Fractional" -> ((tparam: String) => tparam + " is a fractional numeric class, such as Float or Double")) + + ("scala.reflect.Manifest" -> ((tparam: String) => tparam + " is accompanied by a Manifest, which is a runtime representation of its type that survives erasure")) + + ("scala.reflect.ClassManifest" -> ((tparam: String) => tparam + " is accompanied by a ClassManifest, which is a runtime representation of its type that survives erasure")) + + ("scala.reflect.OptManifest" -> ((tparam: String) => tparam + " is accompanied by an OptManifest, which can be either a runtime representation of its type or the NoManifest, which means the runtime type is not available")) + + ("scala.reflect.ClassTag" -> ((tparam: String) => tparam + " is accompanied by a ClassTag, which is a runtime representation of its type that survives erasure")) + + ("scala.reflect.AbsTypeTag" -> ((tparam: String) => tparam + " is accompanied by an AbsTypeTag, which is a runtime representation of its type that survives erasure")) + + ("scala.reflect.base.TypeTags.TypeTag" -> ((tparam: String) => tparam + " is accompanied by a TypeTag, which is a runtime representation of its type that survives erasure")) /** * Set of classes to exclude from index and diagrams diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index 42db408fee..626c1500f1 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -520,6 +520,9 @@ trait ImplicitConversion { /** The members inherited by this implicit conversion */ def members: List[MemberEntity] + + /** Is this a common implicit conversion (aka conversion that affects all classes, in Predef?) */ + def isCommonConversion: Boolean } /** Shadowing captures the information that the member is shadowed by some other members diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index ddcdf1cf5c..bdae09e84d 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -115,7 +115,7 @@ trait ModelFactoryImplicitSupport { // Put the class-specific conversions in front val (ownConversions, commonConversions) = - conversions.partition(conv => !hardcoded.commonConversionTargets.contains(conv.conversionQualifiedName)) + conversions.partition(!_.isCommonConversion) ownConversions ::: commonConversions } @@ -347,7 +347,7 @@ trait ModelFactoryImplicitSupport { if (convSym != NoSymbol) makeTemplate(convSym.owner) else { - error("Scaladoc implicits: Implicit conversion from " + sym.tpe + " to " + toType + " done by " + convSym + " = NoSymbol!") + error("Scaladoc implicits: " + toString + " = NoSymbol!") makeRootPackage } @@ -415,6 +415,10 @@ trait ModelFactoryImplicitSupport { } lazy val members: List[MemberEntity] = memberImpls + + def isCommonConversion = hardcoded.commonConversionTargets.contains(conversionQualifiedName) + + override def toString = "Implcit conversion from " + sym.tpe + " to " + toType + " done by " + convSym } /* ========================= HELPER METHODS ========================== */ diff --git a/test/scaladoc/resources/implicits-known-type-classes-res.scala b/test/scaladoc/resources/implicits-known-type-classes-res.scala new file mode 100644 index 0000000000..9ad652947d --- /dev/null +++ b/test/scaladoc/resources/implicits-known-type-classes-res.scala @@ -0,0 +1,38 @@ +/** Tests the "known type classes" feature of scaladoc implicits + * if the test fails, please update the correct qualified name of + * the type class in src/compiler/scala/tools/nsc/doc/Settings.scala + * in the knownTypeClasses map. Thank you! */ +package scala.test.scaladoc.implicits.typeclasses { + class A[T] + object A { + import language.implicitConversions + import scala.reflect.{ClassTag, TypeTag} + implicit def convertNumeric [T: Numeric] (a: A[T]) = new B(implicitly[Numeric[T]]) + implicit def convertIntegral [T: Integral] (a: A[T]) = new B(implicitly[Integral[T]]) + implicit def convertFractional [T: Fractional] (a: A[T]) = new B(implicitly[Fractional[T]]) + implicit def convertManifest [T: Manifest] (a: A[T]) = new B(implicitly[Manifest[T]]) + implicit def convertClassManifest [T: ClassManifest] (a: A[T]) = new B(implicitly[ClassManifest[T]]) + implicit def convertOptManifest [T: OptManifest] (a: A[T]) = new B(implicitly[OptManifest[T]]) + implicit def convertClassTag [T: ClassTag] (a: A[T]) = new B(implicitly[ClassTag[T]]) + implicit def convertTypeTag [T: TypeTag] (a: A[T]) = new B(implicitly[TypeTag[T]]) + type K[X] = Numeric[X] + type L[X] = Integral[X] + type M[X] = Fractional[X] + type N[X] = Manifest[X] + type O[X] = ClassManifest[X] + type P[X] = OptManifest[X] + type Q[X] = ClassTag[X] + type R[X] = TypeTag[X] + implicit def convertK [T: K] (a: A[T]) = new B(implicitly[K[T]]) + implicit def convertL [T: L] (a: A[T]) = new B(implicitly[L[T]]) + implicit def convertM [T: M] (a: A[T]) = new B(implicitly[M[T]]) + implicit def convertN [T: N] (a: A[T]) = new B(implicitly[N[T]]) + implicit def convertO [T: O] (a: A[T]) = new B(implicitly[O[T]]) + implicit def convertP [T: P] (a: A[T]) = new B(implicitly[P[T]]) + implicit def convertQ [T: Q] (a: A[T]) = new B(implicitly[Q[T]]) + implicit def convertR [T: R] (a: A[T]) = new B(implicitly[R[T]]) + } + class B[T](t: T) { + def typeClass: T = t + } +} \ No newline at end of file diff --git a/test/scaladoc/run/implicits-known-type-classes.check b/test/scaladoc/run/implicits-known-type-classes.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/implicits-known-type-classes.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/implicits-known-type-classes.scala b/test/scaladoc/run/implicits-known-type-classes.scala new file mode 100644 index 0000000000..9f4ca372b0 --- /dev/null +++ b/test/scaladoc/run/implicits-known-type-classes.scala @@ -0,0 +1,33 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest +import language._ + +object Test extends ScaladocModelTest { + + // test a file instead of a piece of code + override def resourceFile = "implicits-known-type-classes-res.scala" + + // start implicits + def scaladocSettings = "-implicits" + + def testModel(root: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + /** Tests the "known type classes" feature of scaladoc implicits + * if the test fails, please update the correct qualified name of + * the type class in src/compiler/scala/tools/nsc/doc/Settings.scala + * in the knownTypeClasses map. Thank you! */ + + val base = root._package("scala")._package("test")._package("scaladoc")._package("implicits")._package("typeclasses") + var conv: ImplicitConversion = null + + val A = base._class("A") + + for (conversion <- A.conversions if !conversion.isCommonConversion) { + assert(conversion.constraints.length == 1, conversion.constraints.length + " == 1 (in " + conversion + ")") + assert(conversion.constraints.head.isInstanceOf[KnownTypeClassConstraint], + conversion.constraints.head + " is not a known type class constraint!") + } + } +} -- cgit v1.2.3 From 376403266c699190882ef7c9f2a7c65c86a23a3d Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Fri, 13 Jul 2012 15:59:01 +0200 Subject: SI-5533 Skip scaladoc packages from documentation --- src/compiler/scala/tools/nsc/doc/Settings.scala | 18 ++++++- .../scala/tools/nsc/doc/model/Entity.scala | 3 ++ .../scala/tools/nsc/doc/model/ModelFactory.scala | 59 +++++++++++----------- .../nsc/doc/model/diagram/DiagramFactory.scala | 2 +- .../scala/tools/partest/ScaladocModelTest.scala | 11 +++- test/scaladoc/run/SI-3314-diagrams.scala | 9 ---- test/scaladoc/run/SI-5533.check | 1 + test/scaladoc/run/SI-5533.scala | 37 ++++++++++++++ 8 files changed, 98 insertions(+), 42 deletions(-) create mode 100644 test/scaladoc/run/SI-5533.check create mode 100644 test/scaladoc/run/SI-5533.scala diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index 12b27387a7..7cb539feee 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -176,6 +176,13 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) "Avoid warnings for ambiguous and incorrect links." ) + val docSkipPackages = StringSetting ( + "-skip-packages", + ":...:", + "A colon-delimited list of fully qualified package names that will be skipped from scaladoc.", + "" + ) + // Somewhere slightly before r18708 scaladoc stopped building unless the // self-type check was suppressed. I hijacked the slotted-for-removal-anyway // suppress-vt-warnings option and renamed it for this purpose. @@ -188,7 +195,7 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) docDiagramsDotTimeout, docDiagramsDotRestart, docImplicits, docImplicitsDebug, docImplicitsShowAll, docDiagramsMaxNormalClasses, docDiagramsMaxImplicitClasses, - docNoPrefixes, docNoLinkWarnings, docRawOutput + docNoPrefixes, docNoLinkWarnings, docRawOutput, docSkipPackages ) val isScaladocSpecific: String => Boolean = scaladocSpecific map (_.name) @@ -197,6 +204,15 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) // set by the testsuite, when checking test output var scaladocQuietRun = false + lazy val skipPackageNames = + if (docSkipPackages.value == "") + Set[String]() + else + docSkipPackages.value.toLowerCase.split(':').toSet + + def skipPackage(qname: String) = + skipPackageNames(qname.toLowerCase) + /** * This is the hardcoded area of Scaladoc. This is where "undesirable" stuff gets eliminated. I know it's not pretty, * but ultimately scaladoc has to be useful. :) diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index 626c1500f1..d9758c6f2f 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -97,6 +97,9 @@ trait TemplateEntity extends Entity { /** Whether documentation is available for this template. */ def isDocTemplate: Boolean + /** Whether documentation is available for this template. */ + def isNoDocMemberTemplate: Boolean + /** Whether this template is a case class. */ def isCaseClass: Boolean diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index d3d229d848..2fec832db8 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -106,6 +106,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def isObject = sym.isModule && !sym.isPackage def isCaseClass = sym.isCaseClass def isRootPackage = false + def isNoDocMemberTemplate = false def selfType = if (sym.thisSym eq sym) None else Some(makeType(sym.thisSym.typeOfThis, this)) } @@ -270,6 +271,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // no templates cache for this class, each owner gets its own instance override def isTemplate = true def isDocTemplate = false + override def isNoDocMemberTemplate = true lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name) } @@ -518,7 +520,29 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } /* ============== MAKER METHODS ============== */ - /** */ + /** This method makes it easier to work with the different kinds of symbols created by scalac + * + * Okay, here's the explanation of what happens. The code: + * + * package foo { + * object `package` { + * class Bar + * } + * } + * + * will yield this Symbol structure: + * + * +---------------+ +--------------------------+ + * | package foo#1 ----(1)---> module class foo#2 | + * +---------------+ | +----------------------+ | +-------------------------+ + * | | package object foo#3 ------(1)---> module class package#4 | + * | +----------------------+ | | +---------------------+ | + * +--------------------------+ | | class package$Bar#5 | | + * | +---------------------+ | + * +-------------------------+ + * (1) sourceModule + * (2) you get out of owners with .owner + */ def normalizeTemplate(aSym: Symbol): Symbol = aSym match { case null | rootMirror.EmptyPackage | NoSymbol => normalizeTemplate(RootPackage) @@ -528,8 +552,6 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { normalizeTemplate(aSym.owner) case _ if aSym.isModuleClass => normalizeTemplate(aSym.sourceModule) - // case t: ThisType => - // t. case _ => aSym } @@ -720,6 +742,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { override def useCaseOf = _useCaseOf }) else if (bSym.isPackage && !modelFinished) + if (settings.skipPackage(makeQualifiedName(bSym))) None else inTpl match { case inPkg: PackageImpl => modelCreation.createTemplate(bSym, inTpl) match { case p: PackageImpl if droppedPackages contains p => None @@ -933,33 +956,8 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { //path.mkString(".") } - def normalizeOwner(aSym: Symbol): Symbol = - /* - * Okay, here's the explanation of what happens. The code: - * - * package foo { - * object `package` { - * class Bar - * } - * } - * - * will yield this Symbol structure: - * - * +---------------+ +--------------------------+ - * | package foo#1 ----(1)---> module class foo#2 | - * +---------------+ | +----------------------+ | +-------------------------+ - * | | package object foo#3 ------(1)---> module class package#4 | - * | +----------------------+ | | +---------------------+ | - * +--------------------------+ | | class package$Bar#5 | | - * | +---------------------+ | - * +-------------------------+ - * (1) sourceModule - * (2) you get out of owners with .owner - */ - normalizeTemplate(aSym) - def inOriginalOwner(aSym: Symbol, inTpl: TemplateImpl): Boolean = - normalizeOwner(aSym.owner) == normalizeOwner(inTpl.sym) + normalizeTemplate(aSym.owner) == normalizeTemplate(inTpl.sym) def templateShouldDocument(aSym: Symbol, inTpl: TemplateImpl): Boolean = (aSym.isClass || aSym.isModule || aSym == AnyRefClass) && @@ -968,7 +966,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // either it's inside the original owner or we can document it later: (!inOriginalOwner(aSym, inTpl) || (aSym.isPackageClass || (aSym.sourceFile != null))) - def membersShouldDocument(sym: Symbol, inTpl: TemplateImpl) = + def membersShouldDocument(sym: Symbol, inTpl: TemplateImpl) = { // pruning modules that shouldn't be documented // Why Symbol.isInitialized? Well, because we need to avoid exploring all the space available to scaladoc // from the classpath -- scaladoc is a hog, it will explore everything starting from the root package unless we @@ -981,6 +979,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { (!sym.isConstructor || sym.owner == inTpl.sym) && // If the @bridge annotation overrides a normal member, show it !isPureBridge(sym) + } def isEmptyJavaObject(aSym: Symbol): Boolean = aSym.isModule && aSym.isJavaDefined && diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala index 9b56509623..e0f6d42a68 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala @@ -142,7 +142,7 @@ trait DiagramFactory extends DiagramDirectiveParser { } mapNodes += node -> ( - if (node.inTemplate == pack) + if (node.inTemplate == pack && !node.isNoDocMemberTemplate) NormalNode(node.resultType, Some(node))() else OutsideNode(node.resultType, Some(node))() diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala index fb93e98726..fc540f83fe 100644 --- a/src/partest/scala/tools/partest/ScaladocModelTest.scala +++ b/src/partest/scala/tools/partest/ScaladocModelTest.scala @@ -11,8 +11,9 @@ import scala.tools.nsc._ import scala.tools.nsc.util.CommandLineParser import scala.tools.nsc.doc.{Settings, DocFactory, Universe} import scala.tools.nsc.doc.model._ -import scala.tools.nsc.reporters.ConsoleReporter +import scala.tools.nsc.doc.model.diagram._ import scala.tools.nsc.doc.model.comment._ +import scala.tools.nsc.reporters.ConsoleReporter /** A class for testing scaladoc model generation * - you need to specify the code in the `code` method @@ -181,5 +182,13 @@ abstract class ScaladocModelTest extends DirectTest { } countLinks(c.body) } + + def testDiagram(doc: DocTemplateEntity, diag: Option[Diagram], nodes: Int, edges: Int) = { + assert(diag.isDefined, doc.qualifiedName + " diagram missing") + assert(diag.get.nodes.length == nodes, + doc.qualifiedName + "'s diagram: node count " + diag.get.nodes.length + " == " + nodes) + assert(diag.get.edges.map(_._2.length).sum == edges, + doc.qualifiedName + "'s diagram: edge count " + diag.get.edges.length + " == " + edges) + } } } diff --git a/test/scaladoc/run/SI-3314-diagrams.scala b/test/scaladoc/run/SI-3314-diagrams.scala index 0b07e4c5bd..050c6e7f48 100644 --- a/test/scaladoc/run/SI-3314-diagrams.scala +++ b/test/scaladoc/run/SI-3314-diagrams.scala @@ -1,5 +1,4 @@ import scala.tools.nsc.doc.model._ -import scala.tools.nsc.doc.model.diagram._ import scala.tools.partest.ScaladocModelTest object Test extends ScaladocModelTest { @@ -17,14 +16,6 @@ object Test extends ScaladocModelTest { val base = rootPackage._package("scala")._package("test")._package("scaladoc") val diagrams = base._package("diagrams") - def testDiagram(doc: DocTemplateEntity, diag: Option[Diagram], nodes: Int, edges: Int) = { - assert(diag.isDefined, doc.qualifiedName + " diagram missing") - assert(diag.get.nodes.length == nodes, - doc.qualifiedName + "'s diagram: node count " + diag.get.nodes.length + " == " + nodes) - assert(diag.get.edges.length == edges, - doc.qualifiedName + "'s diagram: edge count " + diag.get.edges.length + " == " + edges) - } - val templates = List(diagrams._trait("WeekDayTraitWithDiagram"), diagrams._class("WeekDayClassWithDiagram"), diagrams._object("WeekDayObjectWithDiagram")) for (template <- templates) { diff --git a/test/scaladoc/run/SI-5533.check b/test/scaladoc/run/SI-5533.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/SI-5533.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/SI-5533.scala b/test/scaladoc/run/SI-5533.scala new file mode 100644 index 0000000000..e7b5f57860 --- /dev/null +++ b/test/scaladoc/run/SI-5533.scala @@ -0,0 +1,37 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + // Working around the fact that usecases have the form Coll[T] and not Coll[T, U], as required by Map + override def code = """ + package a { + class A { class Z } + class C extends b.B { class X extends Y } + } + + package b { + /** @contentDiagram */ + class B extends a.A { class Y extends Z } + /** @contentDiagram */ + class D extends a.C { class V extends X } + } + """ + + // no need for special settings + def scaladocSettings = "-diagrams -skip-packages a" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + // just need to check the member exists, access methods will throw an error if there's a problem + assert(!rootPackage.templates.exists(_.name == "a"), "package a should not exist in the root package") + assert(rootPackage.templates.exists(_.name == "b"), "package b should exist in the root package") + val b = rootPackage._package("b") + val B = b._class("B") + val D = b._class("D") + testDiagram(B, B.contentDiagram, 2, 1) + testDiagram(D, D.contentDiagram, 2, 1) + } +} \ No newline at end of file -- cgit v1.2.3 From 17f745d33cbda90aa552c95bc5456ed793180333 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Mon, 16 Jul 2012 13:32:05 +0200 Subject: Scaladoc: Refactoring the entities for SI-5784. This commit has been checked with tools/scaladoc-compare and the only difference is that the containing entities in the index are not duplicate anymore, which solves yet another bug we did not know about. :) --- src/compiler/scala/tools/nsc/doc/html/Page.scala | 22 ++- .../scala/tools/nsc/doc/html/page/Index.scala | 2 +- .../tools/nsc/doc/html/page/IndexScript.scala | 2 +- .../scala/tools/nsc/doc/html/page/Template.scala | 16 +- .../scala/tools/nsc/doc/model/Entity.scala | 55 +++---- .../tools/nsc/doc/model/IndexModelFactory.scala | 7 +- .../scala/tools/nsc/doc/model/ModelFactory.scala | 172 ++++++++++++--------- .../doc/model/ModelFactoryImplicitSupport.scala | 2 +- .../scala/tools/partest/ScaladocModelTest.scala | 12 +- test/scaladoc/run/diagrams-determinism.scala | 20 +-- test/scaladoc/run/diagrams-filtering.scala | 2 +- test/scaladoc/run/implicits-ambiguating.scala | 2 +- test/scaladoc/run/implicits-shadowing.scala | 2 +- 13 files changed, 159 insertions(+), 157 deletions(-) diff --git a/src/compiler/scala/tools/nsc/doc/html/Page.scala b/src/compiler/scala/tools/nsc/doc/html/Page.scala index 08df26e745..d30ca5dc08 100644 --- a/src/compiler/scala/tools/nsc/doc/html/Page.scala +++ b/src/compiler/scala/tools/nsc/doc/html/Page.scala @@ -48,13 +48,21 @@ abstract class Page { * @param generator The generator that is writing this page. */ def writeFor(site: HtmlFactory): Unit - def docEntityKindToString(ety: TemplateEntity) = - if (ety.isTrait) "trait" - else if (ety.isCaseClass) "case class" - else if (ety.isClass) "class" - else if (ety.isObject) "object" - else if (ety.isPackage) "package" - else "class" // FIXME: an entity *should* fall into one of the above categories, but AnyRef is somehow not + def kindToString(mbr: MemberEntity) = + mbr match { + case c: Class => if (c.isCaseClass) "case class" else "class" + case _: Trait => "trait" + case _: Package => "package" + case _: Object => "object" + case _: AbstractType => "type" + case _: AliasType => "type" + case _: Constructor => "new" + case v: Def => "def" + case v: Val if (v.isLazyVal) => "lazy val" + case v: Val if (v.isVal) => "val" + case v: Val if (v.isVar) => "var" + case _ => sys.error("Cannot create kind for: " + mbr + " of class " + mbr.getClass) + } def templateToPath(tpl: TemplateEntity): List[String] = { def doName(tpl: TemplateEntity): String = diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala index 81fbed884d..b15d602b57 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Index.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Index.scala @@ -68,7 +68,7 @@ class Index(universe: doc.Universe, val index: doc.Index) extends HtmlPage { val placeholderSeq: NodeSeq =
    def createLink(entity: DocTemplateEntity, includePlaceholder: Boolean, includeText: Boolean) = { - val entityType = docEntityKindToString(entity) + val entityType = kindToString(entity) val linkContent = ( { if (includePlaceholder) placeholderSeq else NodeSeq.Empty } ++ diff --git a/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala b/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala index e1ab479f9d..a37dd3fb8b 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/IndexScript.scala @@ -27,7 +27,7 @@ class IndexScript(universe: doc.Universe, index: doc.Index) extends Page { val ary = merged.keys.toList.sortBy(_.toLowerCase).map(key => { val pairs = merged(key).map( - t => docEntityKindToString(t) -> relativeLinkTo(t) + t => kindToString(t) -> relativeLinkTo(t) ) :+ ("name" -> key) JSONObject(scala.collection.immutable.Map(pairs : _*)) diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index 417bfcfb96..d691692920 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -67,7 +67,7 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp nonDeprValueMembers partition (!_.isShadowedOrAmbiguousImplicit) val typeMembers = - tpl.abstractTypes ++ tpl.aliasTypes ++ tpl.templates.filter(x => x.isTrait || x.isClass) sorted + tpl.abstractTypes ++ tpl.aliasTypes ++ tpl.templates.filter(x => x.isTrait || x.isClass) sorted (implicitly[Ordering[MemberEntity]]) val constructors = (tpl match { case cls: Class => (cls.constructors: List[MemberEntity]).sorted @@ -615,20 +615,6 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp memberComment ++ paramComments ++ attributesBlock ++ linearization ++ subclasses ++ typeHierarchy ++ contentHierarchy } - def kindToString(mbr: MemberEntity): String = { - mbr match { - case tpl: DocTemplateEntity => docEntityKindToString(tpl) - case tpl: NoDocTemplateMemberEntity => docEntityKindToString(tpl) - case ctor: Constructor => "new" - case tme: MemberEntity => - ( if (tme.isDef) "def" - else if (tme.isVal) "val" - else if (tme.isLazyVal) "lazy val" - else if (tme.isVar) "var" - else "type") - } - } - def boundsToHtml(hi: Option[TypeEntity], lo: Option[TypeEntity], hasLinks: Boolean): NodeSeq = { def bound0(bnd: Option[TypeEntity], pre: String): NodeSeq = bnd match { case None => NodeSeq.Empty diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index d9758c6f2f..620aa4253f 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -185,7 +185,7 @@ trait MemberEntity extends Entity { /** If this symbol is a use case, the useCaseOf will contain the member it was derived from, containing the full * signature and the complete parameter descriptions. */ - def useCaseOf: Option[MemberEntity] = None + def useCaseOf: Option[MemberEntity] /** If this member originates from an implicit conversion, we set the implicit information to the correct origin */ def byConversion: Option[ImplicitConversion] @@ -225,22 +225,27 @@ trait HigherKinded { /** A template (class, trait, object or package) which is referenced in the universe, but for which no further * documentation is available. Only templates for which a source file is given are documented by Scaladoc. */ trait NoDocTemplate extends TemplateEntity { - def kind = "" - //def kind = "(not documented) template" + def kind = + if (isClass) "class" + else if (isTrait) "trait" + else if (isObject) "object" + else "" } /** An inherited template that was not documented in its original owner - example: * in classpath: trait T { class C } -- T (and implicitly C) are not documented - * in the source: trait U extends T -- C appears in U as a NoDocTemplateMemberImpl + * in the source: trait U extends T -- C appears in U as a MemberTemplateImpl * -- that is, U has a member for it but C doesn't get its own page */ -trait NoDocTemplateMemberEntity extends TemplateEntity with MemberEntity { - def kind = "" - //def kind = "(not documented) member template" +trait MemberTemplateEntity extends TemplateEntity with MemberEntity with HigherKinded { + + /** The value parameters of this case class, or an empty list if this class is not a case class. As case class value + * parameters cannot be curried, the outer list has exactly one element. */ + def valueParams: List[List[ValueParam]] } /** A template (class, trait, object or package) for which documentation is available. Only templates for which * a source file is given are documented by Scaladoc. */ -trait DocTemplateEntity extends TemplateEntity with MemberEntity { +trait DocTemplateEntity extends MemberTemplateEntity { /** The list of templates such that each is a member of the template that follows it; the first template is always * this template, the last the root package entity. */ @@ -295,6 +300,12 @@ trait DocTemplateEntity extends TemplateEntity with MemberEntity { /** All type aliases that are members of this template. */ def aliasTypes: List[AliasType] + /** The primary constructor of this class, if it has been defined. */ + def primaryConstructor: Option[Constructor] + + /** All constructors of this class, including the primary constructor. */ + def constructors: List[Constructor] + /** The companion of this template, or none. If a class and an object are defined as a pair of the same name, the * other entity of the pair is the companion. */ def companion: Option[DocTemplateEntity] @@ -319,39 +330,24 @@ trait DocTemplateEntity extends TemplateEntity with MemberEntity { def contentDiagram: Option[Diagram] } - /** A trait template. */ -trait Trait extends DocTemplateEntity with HigherKinded { +trait Trait extends MemberTemplateEntity { def kind = "trait" } - /** A class template. */ -trait Class extends Trait with HigherKinded { - - /** The primary constructor of this class, if it has been defined. */ - def primaryConstructor: Option[Constructor] - - /** All constructors of this class, including the primary constructor. */ - def constructors: List[Constructor] - - /** The value parameters of this case class, or an empty list if this class is not a case class. As case class value - * parameters cannot be curried, the outer list has exactly one element. */ - def valueParams: List[List[ValueParam]] - +trait Class extends MemberTemplateEntity { override def kind = "class" } - /** An object template. */ -trait Object extends DocTemplateEntity { +trait Object extends MemberTemplateEntity { def kind = "object" } - /** A package template. A package is in the universe if it is declared as a package object, or if it * contains at least one template. */ -trait Package extends Object { +trait Package extends DocTemplateEntity { /** The package of which this package is a member. */ def inTemplate: Package @@ -382,7 +378,6 @@ trait NonTemplateMemberEntity extends MemberEntity { /** Whether this member is a bridge member. A bridge member does only exist for binary compatibility reasons * and should not appear in ScalaDoc. */ def isBridge: Boolean - } @@ -419,7 +414,7 @@ trait Val extends NonTemplateMemberEntity { /** An abstract type member of a template. */ -trait AbstractType extends NonTemplateMemberEntity with HigherKinded { +trait AbstractType extends MemberTemplateEntity with HigherKinded { /** The lower bound for this abstract type, if it has been defined. */ def lo: Option[TypeEntity] @@ -432,7 +427,7 @@ trait AbstractType extends NonTemplateMemberEntity with HigherKinded { /** An type alias of a template. */ -trait AliasType extends NonTemplateMemberEntity with HigherKinded { +trait AliasType extends MemberTemplateEntity with HigherKinded { /** The type aliased by this type alias. */ def alias: TypeEntity diff --git a/src/compiler/scala/tools/nsc/doc/model/IndexModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/IndexModelFactory.scala index 6392de22ff..f4c96505a7 100755 --- a/src/compiler/scala/tools/nsc/doc/model/IndexModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/IndexModelFactory.scala @@ -36,7 +36,6 @@ object IndexModelFactory { } + d this(firstLetter) = letter + (d.name -> members) } - } //@scala.annotation.tailrec // TODO @@ -46,11 +45,7 @@ object IndexModelFactory { case tpl: DocTemplateEntity => result.addMember(tpl) gather(tpl) - case alias: AliasType => - result.addMember(alias) - case absType: AbstractType => - result.addMember(absType) - case non: NonTemplateMemberEntity if !non.isConstructor => + case non: MemberEntity if !non.isConstructor => result.addMember(non) case x @ _ => } diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 2fec832db8..fc5fde3239 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -110,26 +110,16 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def selfType = if (sym.thisSym eq sym) None else Some(makeType(sym.thisSym.typeOfThis, this)) } - abstract class MemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl) extends EntityImpl(sym, inTpl) with MemberEntity { + abstract class MemberImpl(sym: Symbol, inTpl: DocTemplateImpl) extends EntityImpl(sym, inTpl) with MemberEntity { lazy val comment = { - val inRealTpl = - /* Variable precendence order for implicitly added members: Take the variable defifinitions from ... - * 1. the target of the implicit conversion - * 2. the definition template (owner) - * 3. the current template - */ - if (implConv != null) findTemplateMaybe(implConv.toType.typeSymbol) match { - case Some(d) if d != makeRootPackage => d //in case of NoSymbol, it will give us the root package - case _ => findTemplateMaybe(sym.owner) match { - case Some(d) if d != makeRootPackage => d //in case of NoSymbol, it will give us the root package - case _ => inTpl - } - } else inTpl + // If the current tpl is a DocTemplate, we consider itself as the root for resolving link targets (instead of the + // package the class is in) -- so people can refer to methods directly [[foo]], instead of using [[MyClass.foo]] + // in the doc comment of MyClass val thisTpl = this match { case d: DocTemplateImpl => Some(d) case _ => None } - if (inRealTpl != null) thisFactory.comment(sym, thisTpl, inRealTpl) else None + if (inTpl != null) thisFactory.comment(sym, thisTpl, inTpl) else None } override def inTemplate = inTpl override def toRoot: List[MemberImpl] = this :: inTpl.toRoot @@ -168,7 +158,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { * }}} * the type the method returns is TraversableOps, which has all-abstract symbols. But in reality, it couldn't have * any abstract terms, otherwise it would fail compilation. So we reset the DEFERRED flag. */ - if (!sym.isTrait && (sym hasFlag Flags.DEFERRED) && (implConv eq null)) fgs += Paragraph(Text("abstract")) + if (!sym.isTrait && (sym hasFlag Flags.DEFERRED) && (!isImplicitlyInherited)) fgs += Paragraph(Text("abstract")) if (!sym.isModule && (sym hasFlag Flags.FINAL)) fgs += Paragraph(Text("final")) fgs.toList } @@ -202,7 +192,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { case NullaryMethodType(res) => resultTpe(res) case _ => tpe } - val tpe = if (implConv eq null) sym.tpe else implConv.toType memberInfo sym + val tpe = if (!isImplicitlyInherited) sym.tpe else byConversion.get.toType memberInfo sym makeTypeInTemplateContext(resultTpe(tpe), inTemplate, sym) } def isDef = false @@ -214,11 +204,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def isAliasType = false def isAbstractType = false def isAbstract = - // for the explanation of implConv == null see comment on flags - ((!sym.isTrait && ((sym hasFlag Flags.ABSTRACT) || (sym hasFlag Flags.DEFERRED)) && (implConv == null)) || + // for the explanation of conversion == null see comment on flags + ((!sym.isTrait && ((sym hasFlag Flags.ABSTRACT) || (sym hasFlag Flags.DEFERRED)) && (!isImplicitlyInherited)) || sym.isAbstractClass || sym.isAbstractType) && !sym.isSynthetic def isTemplate = false - def byConversion = if (implConv ne null) Some(implConv) else None lazy val signature = { def defParams(mbr: Any): String = mbr match { @@ -245,10 +234,13 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { (name + tParams(this) + defParams(this) +":"+ resultType.name).replaceAll("\\s","") // no spaces allowed, they break links } - def isImplicitlyInherited = { assert(modelFinished); byConversion.isDefined } - def isShadowedImplicit = isImplicitlyInherited && inTpl.implicitsShadowing.get(this).map(_.isShadowed).getOrElse(false) - def isAmbiguousImplicit = isImplicitlyInherited && inTpl.implicitsShadowing.get(this).map(_.isAmbiguous).getOrElse(false) - def isShadowedOrAmbiguousImplicit = isShadowedImplicit || isAmbiguousImplicit + // these only apply for NonTemplateMemberEntities + def useCaseOf: Option[MemberEntity] = None + def byConversion: Option[ImplicitConversionImpl] = None + def isImplicitlyInherited = false + def isShadowedImplicit = false + def isAmbiguousImplicit = false + def isShadowedOrAmbiguousImplicit = false } /** A template that is not documented at all. The class is instantiated during lookups, to indicate that the class @@ -263,22 +255,22 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { /** An inherited template that was not documented in its original owner - example: * in classpath: trait T { class C } -- T (and implicitly C) are not documented - * in the source: trait U extends T -- C appears in U as a NoDocTemplateMemberImpl -- that is, U has a member for it + * in the source: trait U extends T -- C appears in U as a MemberTemplateImpl -- that is, U has a member for it * but C doesn't get its own page */ - class NoDocTemplateMemberImpl(sym: Symbol, inTpl: DocTemplateImpl) extends MemberImpl(sym, null, inTpl) with TemplateImpl with HigherKindedImpl with NoDocTemplateMemberEntity { - assert(modelFinished) + abstract class MemberTemplateImpl(sym: Symbol, inTpl: DocTemplateImpl) extends MemberImpl(sym, inTpl) with TemplateImpl with HigherKindedImpl with MemberTemplateEntity { // no templates cache for this class, each owner gets its own instance override def isTemplate = true def isDocTemplate = false override def isNoDocMemberTemplate = true lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name) + def valueParams: List[List[ValueParam]] = Nil /** TODO, these are now only computed for DocTemplates */ } /** The instantiation of `TemplateImpl` triggers the creation of the following entities: * All ancestors of the template and all non-package members. */ - abstract class DocTemplateImpl(sym: Symbol, inTpl: DocTemplateImpl) extends MemberImpl(sym, null, inTpl) with TemplateImpl with HigherKindedImpl with DocTemplateEntity { + abstract class DocTemplateImpl(sym: Symbol, inTpl: DocTemplateImpl) extends MemberTemplateImpl(sym, inTpl) with DocTemplateEntity { assert(!modelFinished) assert(!(docTemplatesCache isDefinedAt sym), sym) docTemplatesCache += (sym -> this) @@ -386,7 +378,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // the direct members (methods, values, vars, types and directly contained templates) var memberSymsEager = memberSyms.filter(!memberSymsLazy.contains(_)) // the members generated by the symbols in memberSymsEager - val ownMembers = (memberSyms.flatMap(makeMember(_, null, this))) + val ownMembers = (memberSyms.flatMap(makeMember(_, None, this))) // all the members that are documentented PLUS the members inherited by implicit conversions var members: List[MemberImpl] = ownMembers @@ -439,8 +431,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { ) override def isTemplate = true - lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name) - def isDocTemplate = true + override def isDocTemplate = true def companion = sym.companionSymbol match { case NoSymbol => None case comSym if !isEmptyJavaObject(comSym) && (comSym.isClass || comSym.isModule) => @@ -451,6 +442,15 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { case _ => None } + def constructors: List[MemberImpl with Constructor] = if (isClass) members collect { case d: Constructor => d } else Nil + def primaryConstructor: Option[MemberImpl with Constructor] = if (isClass) constructors find { _.isPrimary } else None + override def valueParams = + // we don't want params on a class (non case class) signature + if (isCaseClass) primaryConstructor match { + case Some(const) => const.sym.paramss map (_ map (makeValueParam(_, this))) + case None => List() + } + else List.empty // These are generated on-demand, make sure you don't call them more than once def inheritanceDiagram = makeInheritanceDiagram(this) def contentDiagram = makeContentDiagram(this) @@ -470,23 +470,49 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { abstract class RootPackageImpl(sym: Symbol) extends PackageImpl(sym, null) with RootPackageEntity - abstract class NonTemplateMemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl) extends MemberImpl(sym, implConv, inTpl) with NonTemplateMemberEntity { + abstract class NonTemplateMemberImpl(sym: Symbol, conversion: Option[ImplicitConversionImpl], + override val useCaseOf: Option[MemberEntity], inTpl: DocTemplateImpl) + extends MemberImpl(sym, inTpl) with NonTemplateMemberEntity { + override lazy val comment = { + val inRealTpl = + /* Variable precendence order for implicitly added members: Take the variable defifinitions from ... + * 1. the target of the implicit conversion + * 2. the definition template (owner) + * 3. the current template + */ + if (conversion.isDefined) findTemplateMaybe(conversion.get.toType.typeSymbol) match { + case Some(d) if d != makeRootPackage => d //in case of NoSymbol, it will give us the root package + case _ => findTemplateMaybe(sym.owner) match { + case Some(d) if d != makeRootPackage => d //in case of NoSymbol, it will give us the root package + case _ => inTpl + } + } else inTpl + if (inRealTpl != null) thisFactory.comment(sym, None, inRealTpl) else None + } + override def qualifiedName = optimize(inTemplate.qualifiedName + "#" + name) lazy val definitionName = { // this contrived name is here just to satisfy some older tests -- if you decide to remove it, be my guest, and // also remove property("package object") from test/scaladoc/scalacheck/HtmlFactoryTest.scala so you don't break // the test suite... val packageObject = if (inPackageObject) ".package" else "" - if (implConv == null) optimize(inDefinitionTemplates.head.qualifiedName + packageObject + "#" + name) - else optimize(implConv.conversionQualifiedName + packageObject + "#" + name) + if (!conversion.isDefined) optimize(inDefinitionTemplates.head.qualifiedName + packageObject + "#" + name) + else optimize(conversion.get.conversionQualifiedName + packageObject + "#" + name) } - def isUseCase = sym.isSynthetic def isBridge = sym.isBridge + def isUseCase = useCaseOf.isDefined + override def byConversion: Option[ImplicitConversionImpl] = conversion + override def isImplicitlyInherited = { assert(modelFinished); conversion.isDefined } + override def isShadowedImplicit = isImplicitlyInherited && inTpl.implicitsShadowing.get(this).map(_.isShadowed).getOrElse(false) + override def isAmbiguousImplicit = isImplicitlyInherited && inTpl.implicitsShadowing.get(this).map(_.isAmbiguous).getOrElse(false) + override def isShadowedOrAmbiguousImplicit = isShadowedImplicit || isAmbiguousImplicit } - abstract class NonTemplateParamMemberImpl(sym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl) extends NonTemplateMemberImpl(sym, implConv, inTpl) { + abstract class NonTemplateParamMemberImpl(sym: Symbol, conversion: Option[ImplicitConversionImpl], + useCaseOf: Option[MemberEntity], inTpl: DocTemplateImpl) + extends NonTemplateMemberImpl(sym, conversion, useCaseOf, inTpl) { def valueParams = { - val info = if (implConv eq null) sym.info else implConv.toType memberInfo sym + val info = if (!isImplicitlyInherited) sym.info else conversion.get.toType memberInfo sym info.paramss map { ps => (ps.zipWithIndex) map { case (p, i) => if (p.nameString contains "$") makeValueParam(p, inTpl, optimize("arg" + i)) else makeValueParam(p, inTpl) }} @@ -591,24 +617,13 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def createDocTemplate(bSym: Symbol, inTpl: DocTemplateImpl): DocTemplateImpl = { assert(!modelFinished) // only created BEFORE the model is finished if (bSym.isModule || (bSym.isAliasType && bSym.tpe.typeSymbol.isModule)) - new DocTemplateImpl(bSym, inTpl) with Object + new DocTemplateImpl(bSym, inTpl) with Object {} else if (bSym.isTrait || (bSym.isAliasType && bSym.tpe.typeSymbol.isTrait)) - new DocTemplateImpl(bSym, inTpl) with Trait + new DocTemplateImpl(bSym, inTpl) with Trait {} else if (bSym.isClass || (bSym.isAliasType && bSym.tpe.typeSymbol.isClass)) - new DocTemplateImpl(bSym, inTpl) with Class { - def valueParams = - // we don't want params on a class (non case class) signature - if (isCaseClass) primaryConstructor match { - case Some(const) => const.sym.paramss map (_ map (makeValueParam(_, this))) - case None => List() - } - else List.empty - val constructors = - members collect { case d: Constructor => d } - def primaryConstructor = constructors find { _.isPrimary } - } + new DocTemplateImpl(bSym, inTpl) with Class {} else - sys.error("'" + bSym + "' isn't a class, trait or object thus cannot be built as a documentable template") + sys.error("'" + bSym + "' isn't a class, trait or object thus cannot be built as a documentable template.") } val bSym = normalizeTemplate(aSym) @@ -638,7 +653,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { inTpl match { case inPkg: PackageImpl => val pack = new PackageImpl(bSym, inPkg) {} - if (pack.templates.isEmpty && pack.memberSymsLazy.isEmpty) + if (pack.templates.filter(_.isDocTemplate).isEmpty && pack.memberSymsLazy.isEmpty) droppedPackages += pack pack case _ => @@ -654,15 +669,22 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { /** * After the model is completed, no more DocTemplateEntities are created. * Therefore any symbol that still appears is: - * - NoDocTemplateMemberEntity (created here) + * - MemberTemplateEntity (created here) * - NoDocTemplateEntity (created in makeTemplate) */ def createLazyTemplateMember(aSym: Symbol, inTpl: DocTemplateImpl): MemberImpl = { // Code is duplicate because the anonymous classes are created statically - def createNoDocMemberTemplate(bSym: Symbol, inTpl: DocTemplateImpl): NoDocTemplateMemberImpl = { + def createNoDocMemberTemplate(bSym: Symbol, inTpl: DocTemplateImpl): MemberTemplateImpl = { assert(modelFinished) // only created AFTER the model is finished - new NoDocTemplateMemberImpl(bSym, inTpl) + if (bSym.isModule || (bSym.isAliasType && bSym.tpe.typeSymbol.isModule)) + new MemberTemplateImpl(bSym, inTpl) with Object {} + else if (bSym.isTrait || (bSym.isAliasType && bSym.tpe.typeSymbol.isTrait)) + new MemberTemplateImpl(bSym, inTpl) with Trait {} + else if (bSym.isClass || (bSym.isAliasType && bSym.tpe.typeSymbol.isClass)) + new MemberTemplateImpl(bSym, inTpl) with Class {} + else + sys.error("'" + bSym + "' isn't a class, trait or object thus cannot be built as a member template.") } assert(modelFinished) @@ -687,20 +709,18 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def makeRootPackage: PackageImpl = docTemplatesCache(RootPackage).asInstanceOf[PackageImpl] // TODO: Should be able to override the type - def makeMember(aSym: Symbol, implConv: ImplicitConversionImpl, inTpl: DocTemplateImpl): List[MemberImpl] = { + def makeMember(aSym: Symbol, conversion: Option[ImplicitConversionImpl], inTpl: DocTemplateImpl): List[MemberImpl] = { - def makeMember0(bSym: Symbol, _useCaseOf: Option[MemberImpl]): Option[MemberImpl] = { + def makeMember0(bSym: Symbol, useCaseOf: Option[MemberImpl]): Option[MemberImpl] = { if (bSym.isGetter && bSym.isLazy) - Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with Val { + Some(new NonTemplateMemberImpl(bSym, conversion, useCaseOf, inTpl) with Val { override lazy val comment = // The analyser does not duplicate the lazy val's DocDef when it introduces its accessor. thisFactory.comment(bSym.accessed, None, inTpl.asInstanceOf[DocTemplateImpl]) // This hack should be removed after analyser is fixed. override def isLazyVal = true - override def useCaseOf = _useCaseOf }) else if (bSym.isGetter && bSym.accessed.isMutable) - Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with Val { + Some(new NonTemplateMemberImpl(bSym, conversion, useCaseOf, inTpl) with Val { override def isVar = true - override def useCaseOf = _useCaseOf }) else if (bSym.isMethod && !bSym.hasAccessorFlag && !bSym.isConstructor && !bSym.isModule) { val cSym = { // This unsightly hack closes issue #4086. @@ -714,32 +734,30 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } else bSym } - Some(new NonTemplateParamMemberImpl(cSym, implConv, inTpl) with HigherKindedImpl with Def { + Some(new NonTemplateParamMemberImpl(cSym, conversion, useCaseOf, inTpl) with HigherKindedImpl with Def { override def isDef = true - override def useCaseOf = _useCaseOf }) } - else if (bSym.isConstructor && (implConv == null)) - Some(new NonTemplateParamMemberImpl(bSym, implConv, inTpl) with Constructor { - override def isConstructor = true - def isPrimary = sym.isPrimaryConstructor - override def useCaseOf = _useCaseOf - }) + else if (bSym.isConstructor) + if (conversion.isDefined) + None // don't list constructors inherted by implicit conversion + else + Some(new NonTemplateParamMemberImpl(bSym, conversion, useCaseOf, inTpl) with Constructor { + override def isConstructor = true + def isPrimary = sym.isPrimaryConstructor + }) else if (bSym.isGetter) // Scala field accessor or Java field - Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with Val { + Some(new NonTemplateMemberImpl(bSym, conversion, useCaseOf, inTpl) with Val { override def isVal = true - override def useCaseOf = _useCaseOf }) else if (bSym.isAbstractType) - Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with TypeBoundsImpl with HigherKindedImpl with AbstractType { + Some(new MemberTemplateImpl(bSym, inTpl) with TypeBoundsImpl with AbstractType { override def isAbstractType = true - override def useCaseOf = _useCaseOf }) else if (bSym.isAliasType && bSym != AnyRefClass) - Some(new NonTemplateMemberImpl(bSym, implConv, inTpl) with HigherKindedImpl with AliasType { + Some(new MemberTemplateImpl(bSym, inTpl) with AliasType { override def isAliasType = true def alias = makeTypeInTemplateContext(sym.tpe.dealias, inTpl, sym) - override def useCaseOf = _useCaseOf }) else if (bSym.isPackage && !modelFinished) if (settings.skipPackage(makeQualifiedName(bSym))) None else @@ -822,7 +840,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { val arguments = { // lazy def noParams = annot.args map { _ => None } val params: List[Option[ValueParam]] = annotationClass match { - case aClass: Class => + case aClass: DocTemplateEntity with Class => (aClass.primaryConstructor map { _.valueParams.head }) match { case Some(vps) => vps map { Some(_) } case None => noParams diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala index bdae09e84d..00254df7ef 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactoryImplicitSupport.scala @@ -410,7 +410,7 @@ trait ModelFactoryImplicitSupport { // at the same time, the member itself is in the inTpl, not in the new template -- but should pick up // variables from the old template. Ugly huh? We'll always create the member inTpl, but it will change // the template when expanding variables in the comment :) - makeMember(aSym, this, inTpl) + makeMember(aSym, Some(this), inTpl) }) } diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala index fc540f83fe..d8d5dfbbeb 100644 --- a/src/partest/scala/tools/partest/ScaladocModelTest.scala +++ b/src/partest/scala/tools/partest/ScaladocModelTest.scala @@ -106,20 +106,20 @@ abstract class ScaladocModelTest extends DirectTest { def _class(name: String): DocTemplateEntity = getTheFirst(_classes(name), tpl.qualifiedName + ".class(" + name + ")") def _classes(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case c: DocTemplateEntity with Class => c}) - def _classMbr(name: String): NoDocTemplateMemberEntity = getTheFirst(_classesMbr(name), tpl.qualifiedName + ".classMember(" + name + ")") - def _classesMbr(name: String): List[NoDocTemplateMemberEntity] = tpl.templates.filter(_.name == name).collect({ case c: NoDocTemplateMemberEntity if c.isClass => c}) + def _classMbr(name: String): MemberTemplateEntity = getTheFirst(_classesMbr(name), tpl.qualifiedName + ".classMember(" + name + ")") + def _classesMbr(name: String): List[MemberTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case c: MemberTemplateEntity if c.isClass => c}) def _trait(name: String): DocTemplateEntity = getTheFirst(_traits(name), tpl.qualifiedName + ".trait(" + name + ")") def _traits(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case t: DocTemplateEntity with Trait => t}) - def _traitMbr(name: String): NoDocTemplateMemberEntity = getTheFirst(_traitsMbr(name), tpl.qualifiedName + ".traitMember(" + name + ")") - def _traitsMbr(name: String): List[NoDocTemplateMemberEntity] = tpl.templates.filter(_.name == name).collect({ case t: NoDocTemplateMemberEntity if t.isTrait => t}) + def _traitMbr(name: String): MemberTemplateEntity = getTheFirst(_traitsMbr(name), tpl.qualifiedName + ".traitMember(" + name + ")") + def _traitsMbr(name: String): List[MemberTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case t: MemberTemplateEntity if t.isTrait => t}) def _object(name: String): DocTemplateEntity = getTheFirst(_objects(name), tpl.qualifiedName + ".object(" + name + ")") def _objects(name: String): List[DocTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case o: DocTemplateEntity with Object => o}) - def _objectMbr(name: String): NoDocTemplateMemberEntity = getTheFirst(_objectsMbr(name), tpl.qualifiedName + ".objectMember(" + name + ")") - def _objectsMbr(name: String): List[NoDocTemplateMemberEntity] = tpl.templates.filter(_.name == name).collect({ case o: NoDocTemplateMemberEntity if o.isObject => o}) + def _objectMbr(name: String): MemberTemplateEntity = getTheFirst(_objectsMbr(name), tpl.qualifiedName + ".objectMember(" + name + ")") + def _objectsMbr(name: String): List[MemberTemplateEntity] = tpl.templates.filter(_.name == name).collect({ case o: MemberTemplateEntity if o.isObject => o}) def _method(name: String): Def = getTheFirst(_methods(name), tpl.qualifiedName + ".method(" + name + ")") def _methods(name: String): List[Def] = tpl.methods.filter(_.name == name) diff --git a/test/scaladoc/run/diagrams-determinism.scala b/test/scaladoc/run/diagrams-determinism.scala index 6c8db05d78..2b6f8eecc7 100644 --- a/test/scaladoc/run/diagrams-determinism.scala +++ b/test/scaladoc/run/diagrams-determinism.scala @@ -49,19 +49,19 @@ object Test extends ScaladocModelTest { assert(run2 == run3) // 2. check the order in the diagram: this node, subclasses, and then implicit conversions - def assertRightOrder(diagram: Diagram) = { + def assertRightOrder(template: DocTemplateEntity, diagram: Diagram) = for ((node, subclasses) <- diagram.edges) assert(subclasses == subclasses.filter(_.isThisNode) ::: - subclasses.filter(_.isNormalNode) ::: - subclasses.filter(_.isImplicitNode)) - } + subclasses.filter(node => node.isNormalNode || node.isOutsideNode) ::: + subclasses.filter(_.isImplicitNode), + "Diagram order for " + template + ": " + subclasses) val base = rootPackage._package("scala")._package("test")._package("scaladoc")._package("diagrams") - assertRightOrder(base.contentDiagram.get) - assertRightOrder(base._trait("A").inheritanceDiagram.get) - assertRightOrder(base._trait("B").inheritanceDiagram.get) - assertRightOrder(base._trait("C").inheritanceDiagram.get) - assertRightOrder(base._trait("D").inheritanceDiagram.get) - assertRightOrder(base._trait("E").inheritanceDiagram.get) + assertRightOrder(base, base.contentDiagram.get) + assertRightOrder(base._trait("A"), base._trait("A").inheritanceDiagram.get) + assertRightOrder(base._trait("B"), base._trait("B").inheritanceDiagram.get) + assertRightOrder(base._trait("C"), base._trait("C").inheritanceDiagram.get) + assertRightOrder(base._trait("D"), base._trait("D").inheritanceDiagram.get) + assertRightOrder(base._trait("E"), base._trait("E").inheritanceDiagram.get) } } \ No newline at end of file diff --git a/test/scaladoc/run/diagrams-filtering.scala b/test/scaladoc/run/diagrams-filtering.scala index 8cb32180a1..54e3e9ac63 100644 --- a/test/scaladoc/run/diagrams-filtering.scala +++ b/test/scaladoc/run/diagrams-filtering.scala @@ -65,7 +65,7 @@ object Test extends ScaladocModelTest { assert(!C.inheritanceDiagram.isDefined) // trait G - val G = base._trait("G") + val G = base._class("G") assert(!G.inheritanceDiagram.isDefined) // trait E diff --git a/test/scaladoc/run/implicits-ambiguating.scala b/test/scaladoc/run/implicits-ambiguating.scala index 1420593b74..05daf1f805 100644 --- a/test/scaladoc/run/implicits-ambiguating.scala +++ b/test/scaladoc/run/implicits-ambiguating.scala @@ -17,7 +17,7 @@ object Test extends ScaladocModelTest { mbr.byConversion.map(_.source.implicitsShadowing.get(mbr).map(_.isAmbiguous).getOrElse(false)).getOrElse(false) // SEE THE test/resources/implicits-chaining-res.scala FOR THE EXPLANATION OF WHAT'S CHECKED HERE: - val base = root._package("scala")._package("test")._package("scaladoc")._package("implicits")._object("ambiguating") + val base = root._package("scala")._package("test")._package("scaladoc")._package("implicits")._package("ambiguating") var conv1: ImplicitConversion = null var conv2: ImplicitConversion = null diff --git a/test/scaladoc/run/implicits-shadowing.scala b/test/scaladoc/run/implicits-shadowing.scala index 2827d31122..6869b12a94 100644 --- a/test/scaladoc/run/implicits-shadowing.scala +++ b/test/scaladoc/run/implicits-shadowing.scala @@ -17,7 +17,7 @@ object Test extends ScaladocModelTest { mbr.byConversion.map(_.source.implicitsShadowing.get(mbr).map(_.isShadowed).getOrElse(false)).getOrElse(false) // SEE THE test/resources/implicits-chaining-res.scala FOR THE EXPLANATION OF WHAT'S CHECKED HERE: - val base = root._package("scala")._package("test")._package("scaladoc")._package("implicits")._object("shadowing") + val base = root._package("scala")._package("test")._package("scaladoc")._package("implicits")._package("shadowing") var conv: ImplicitConversion = null //// class A /////////////////////////////////////////////////////////////////////////////////////////////////////////// -- cgit v1.2.3 From 9117cbee2715ce184829a9f5b1b839240083d166 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Mon, 16 Jul 2012 23:52:28 +0200 Subject: SI-6086 magic symbols strike back Some of the symbols inside the compiler get created on the fly, because there are no physical entities in classfiles corresponding to them. This curious fact needs to be taken into account when loading symbols, so that the magic symbols get correctly loaded by reflective mirrors. magicSymbols (as defined in Definitions.scala) include not only top-level classes, but some other stuff (e.g. String_+ or methods on Any). Hence a filtering was done to exclude the stuff that's irrelevant to reflective symbol loading. Unfortunately a filter was configured to accept only _.isClass, which consequently ruled out scala.AnyRef (that is a type alias). --- .../scala/reflect/runtime/JavaMirrors.scala | 2 +- test/files/run/reflection-magicsymbols-repl.check | 39 ++++++++++++++++++++++ test/files/run/reflection-magicsymbols-repl.scala | 23 +++++++++++++ .../run/reflection-magicsymbols-vanilla.check | 8 +++++ .../run/reflection-magicsymbols-vanilla.scala | 20 +++++++++++ test/files/run/reflection-magicsymbols.check | 22 ------------ test/files/run/reflection-magicsymbols.scala | 11 ------ test/files/run/t6086-repl.check | 12 +++++++ test/files/run/t6086-repl.scala | 8 +++++ test/files/run/t6086-vanilla.check | 1 + test/files/run/t6086-vanilla.scala | 6 ++++ 11 files changed, 118 insertions(+), 34 deletions(-) create mode 100644 test/files/run/reflection-magicsymbols-repl.check create mode 100644 test/files/run/reflection-magicsymbols-repl.scala create mode 100644 test/files/run/reflection-magicsymbols-vanilla.check create mode 100644 test/files/run/reflection-magicsymbols-vanilla.scala delete mode 100644 test/files/run/reflection-magicsymbols.check delete mode 100644 test/files/run/reflection-magicsymbols.scala create mode 100644 test/files/run/t6086-repl.check create mode 100644 test/files/run/t6086-repl.scala create mode 100644 test/files/run/t6086-vanilla.check create mode 100644 test/files/run/t6086-vanilla.scala diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 185621efa4..75d43a7553 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -1001,7 +1001,7 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym private lazy val magicSymbols: Map[(String, Name), Symbol] = { def mapEntry(sym: Symbol): ((String, Name), Symbol) = (sym.owner.fullName, sym.name) -> sym - Map() ++ (definitions.magicSymbols filter (_.isClass) map mapEntry) + Map() ++ (definitions.magicSymbols filter (_.isType) map mapEntry) } /** 1. If `owner` is a package class (but not the empty package) and `name` is a term name, make a new package diff --git a/test/files/run/reflection-magicsymbols-repl.check b/test/files/run/reflection-magicsymbols-repl.check new file mode 100644 index 0000000000..d2ef4ad3cd --- /dev/null +++ b/test/files/run/reflection-magicsymbols-repl.check @@ -0,0 +1,39 @@ +Type in expressions to have them evaluated. +Type :help for more information. + +scala> + +scala> import scala.reflect.runtime.universe._ +import scala.reflect.runtime.universe._ + +scala> class A { + def foo1(x: Int*) = ??? + def foo2(x: => Int) = ??? + def foo3(x: Any) = ??? + def foo4(x: AnyRef) = ??? + def foo5(x: AnyVal) = ??? + def foo6(x: Null) = ??? + def foo7(x: Nothing) = ??? + def foo8(x: Singleton) = ??? +} +defined class A + +scala> def test(n: Int): Unit = { + val sig = typeOf[A] member newTermName("foo" + n) typeSignature + val x = sig.asInstanceOf[MethodType].params.head + println(x.typeSignature) +} +warning: there were 1 feature warnings; re-run with -feature for details +test: (n: Int)Unit + +scala> for (i <- 1 to 8) test(i) +scala.Int* +=> scala.Int +scala.Any +scala.AnyRef +scala.AnyVal +scala.Null +scala.Nothing +scala.Singleton + +scala> diff --git a/test/files/run/reflection-magicsymbols-repl.scala b/test/files/run/reflection-magicsymbols-repl.scala new file mode 100644 index 0000000000..26127b8661 --- /dev/null +++ b/test/files/run/reflection-magicsymbols-repl.scala @@ -0,0 +1,23 @@ +import scala.tools.partest.ReplTest + +object Test extends ReplTest { + def code = """ + |import scala.reflect.runtime.universe._ + |class A { + | def foo1(x: Int*) = ??? + | def foo2(x: => Int) = ??? + | def foo3(x: Any) = ??? + | def foo4(x: AnyRef) = ??? + | def foo5(x: AnyVal) = ??? + | def foo6(x: Null) = ??? + | def foo7(x: Nothing) = ??? + | def foo8(x: Singleton) = ??? + |} + |def test(n: Int): Unit = { + | val sig = typeOf[A] member newTermName("foo" + n) typeSignature + | val x = sig.asInstanceOf[MethodType].params.head + | println(x.typeSignature) + |} + |for (i <- 1 to 8) test(i) + |""".stripMargin +} diff --git a/test/files/run/reflection-magicsymbols-vanilla.check b/test/files/run/reflection-magicsymbols-vanilla.check new file mode 100644 index 0000000000..4f4e8d94a9 --- /dev/null +++ b/test/files/run/reflection-magicsymbols-vanilla.check @@ -0,0 +1,8 @@ +Int* +=> Int +Any +AnyRef +AnyVal +Null +Nothing +Singleton diff --git a/test/files/run/reflection-magicsymbols-vanilla.scala b/test/files/run/reflection-magicsymbols-vanilla.scala new file mode 100644 index 0000000000..32819dcc46 --- /dev/null +++ b/test/files/run/reflection-magicsymbols-vanilla.scala @@ -0,0 +1,20 @@ +class A { + def foo1(x: Int*) = ??? + def foo2(x: => Int) = ??? + def foo3(x: Any) = ??? + def foo4(x: AnyRef) = ??? + def foo5(x: AnyVal) = ??? + def foo6(x: Null) = ??? + def foo7(x: Nothing) = ??? + def foo8(x: Singleton) = ??? +} + +object Test extends App { + import scala.reflect.runtime.universe._ + def test(n: Int): Unit = { + val sig = typeOf[A] member newTermName("foo" + n) typeSignature + val x = sig.asInstanceOf[MethodType].params.head + println(x.typeSignature) + } + for (i <- 1 to 8) test(i) +} diff --git a/test/files/run/reflection-magicsymbols.check b/test/files/run/reflection-magicsymbols.check deleted file mode 100644 index 2600847d99..0000000000 --- a/test/files/run/reflection-magicsymbols.check +++ /dev/null @@ -1,22 +0,0 @@ -Type in expressions to have them evaluated. -Type :help for more information. - -scala> - -scala> import scala.reflect.runtime.universe._ -import scala.reflect.runtime.universe._ - -scala> class A { def foo(x: Int*) = 1 } -defined class A - -scala> val sig = typeOf[A] member newTermName("foo") typeSignature -warning: there were 1 feature warnings; re-run with -feature for details -sig: reflect.runtime.universe.Type = (x: )scala.Int - -scala> val x = sig.asInstanceOf[MethodType].params.head -x: reflect.runtime.universe.Symbol = value x - -scala> println(x.typeSignature) -scala.Int* - -scala> diff --git a/test/files/run/reflection-magicsymbols.scala b/test/files/run/reflection-magicsymbols.scala deleted file mode 100644 index a40845d6ac..0000000000 --- a/test/files/run/reflection-magicsymbols.scala +++ /dev/null @@ -1,11 +0,0 @@ -import scala.tools.partest.ReplTest - -object Test extends ReplTest { - def code = """ - |import scala.reflect.runtime.universe._ - |class A { def foo(x: Int*) = 1 } - |val sig = typeOf[A] member newTermName("foo") typeSignature - |val x = sig.asInstanceOf[MethodType].params.head - |println(x.typeSignature) - |""".stripMargin -} diff --git a/test/files/run/t6086-repl.check b/test/files/run/t6086-repl.check new file mode 100644 index 0000000000..f868aa18d0 --- /dev/null +++ b/test/files/run/t6086-repl.check @@ -0,0 +1,12 @@ +Type in expressions to have them evaluated. +Type :help for more information. + +scala> + +scala> case class X(s: String) +defined class X + +scala> scala.reflect.runtime.universe.typeOf[X] +res0: reflect.runtime.universe.Type = X + +scala> diff --git a/test/files/run/t6086-repl.scala b/test/files/run/t6086-repl.scala new file mode 100644 index 0000000000..87f94ec9f6 --- /dev/null +++ b/test/files/run/t6086-repl.scala @@ -0,0 +1,8 @@ +import scala.tools.partest.ReplTest + +object Test extends ReplTest { + def code = """ + |case class X(s: String) + |scala.reflect.runtime.universe.typeOf[X] + |""".stripMargin +} diff --git a/test/files/run/t6086-vanilla.check b/test/files/run/t6086-vanilla.check new file mode 100644 index 0000000000..fd66be08d0 --- /dev/null +++ b/test/files/run/t6086-vanilla.check @@ -0,0 +1 @@ +X diff --git a/test/files/run/t6086-vanilla.scala b/test/files/run/t6086-vanilla.scala new file mode 100644 index 0000000000..b4de581ad5 --- /dev/null +++ b/test/files/run/t6086-vanilla.scala @@ -0,0 +1,6 @@ +case class X(s: String) + +object Test extends App { + import scala.reflect.runtime.universe._ + println(typeOf[X]) +} \ No newline at end of file -- cgit v1.2.3 From 32fd97df41bb2e99018f024f41b06490e41bd7ad Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sun, 11 Mar 2012 00:14:16 -0500 Subject: Fix for SI-5385. Nodes which hit EOF with no whitespace afterward had wrong position. --- src/partest/scala/tools/partest/DirectTest.scala | 5 +++-- .../scala/reflect/internal/util/SourceFile.scala | 24 +++++++++++++++------- src/reflect/scala/tools/nsc/io/VirtualFile.scala | 2 +- test/files/neg/t4069.check | 2 +- test/files/neg/t4584.check | 7 +++++-- test/files/neg/unicode-unterminated-quote.check | 5 ++++- test/files/run/t5385.check | 8 ++++++++ test/files/run/t5385.scala | 16 +++++++++++++++ 8 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 test/files/run/t5385.check create mode 100644 test/files/run/t5385.scala diff --git a/src/partest/scala/tools/partest/DirectTest.scala b/src/partest/scala/tools/partest/DirectTest.scala index 4e7f36bdc9..5b4e1b4b25 100644 --- a/src/partest/scala/tools/partest/DirectTest.scala +++ b/src/partest/scala/tools/partest/DirectTest.scala @@ -38,7 +38,8 @@ abstract class DirectTest extends App { // new compiler def newCompiler(args: String*): Global = { val settings = newSettings((CommandLineParser tokenize extraSettings) ++ args.toList) - new Global(settings) + if (settings.Yrangepos.value) new Global(settings) with interactive.RangePositions + else new Global(settings) } def newSources(sourceCodes: String*) = sourceCodes.toList.zipWithIndex map { case (src, idx) => new BatchSourceFile("newSource" + (idx + 1), src) @@ -69,7 +70,7 @@ abstract class DirectTest extends App { /** Constructor/main body **/ try show() - catch { case t => println(t) ; t.printStackTrace ; sys.exit(1) } + catch { case t => println(t.getMessage) ; t.printStackTrace ; sys.exit(1) } /** Debugger interest only below this line **/ protected def isDebug = (sys.props contains "partest.debug") || (sys.env contains "PARTEST_DEBUG") diff --git a/src/reflect/scala/reflect/internal/util/SourceFile.scala b/src/reflect/scala/reflect/internal/util/SourceFile.scala index 7c80ddd37d..df4a3336c3 100644 --- a/src/reflect/scala/reflect/internal/util/SourceFile.scala +++ b/src/reflect/scala/reflect/internal/util/SourceFile.scala @@ -102,17 +102,21 @@ class ScriptSourceFile(underlying: BatchSourceFile, content: Array[Char], overri } /** a file whose contents do not change over time */ -class BatchSourceFile(val file : AbstractFile, val content: Array[Char]) extends SourceFile { - +class BatchSourceFile(val file : AbstractFile, val content0: Array[Char]) extends SourceFile { def this(_file: AbstractFile) = this(_file, _file.toCharArray) def this(sourceName: String, cs: Seq[Char]) = this(new VirtualFile(sourceName), cs.toArray) def this(file: AbstractFile, cs: Seq[Char]) = this(file, cs.toArray) - override def equals(that : Any) = that match { - case that : BatchSourceFile => file.path == that.file.path && start == that.start - case _ => false - } - override def hashCode = file.path.## + start.## + // If non-whitespace tokens run all the way up to EOF, + // positions go wrong because the correct end of the last + // token cannot be used as an index into the char array. + // The least painful way to address this was to add a + // newline to the array. + val content = ( + if (content0.length == 0 || !content0.last.isWhitespace) + content0 :+ '\n' + else content0 + ) val length = content.length def start = 0 def isSelfContained = true @@ -158,4 +162,10 @@ class BatchSourceFile(val file : AbstractFile, val content: Array[Char]) extends lastLine = findLine(0, lines.length, lastLine) lastLine } + + override def equals(that : Any) = that match { + case that : BatchSourceFile => file.path == that.file.path && start == that.start + case _ => false + } + override def hashCode = file.path.## + start.## } diff --git a/src/reflect/scala/tools/nsc/io/VirtualFile.scala b/src/reflect/scala/tools/nsc/io/VirtualFile.scala index b9a946598c..805bc04165 100644 --- a/src/reflect/scala/tools/nsc/io/VirtualFile.scala +++ b/src/reflect/scala/tools/nsc/io/VirtualFile.scala @@ -55,7 +55,7 @@ class VirtualFile(val name: String, override val path: String) extends AbstractF } } - def container: AbstractFile = unsupported + def container: AbstractFile = NoAbstractFile /** Is this abstract file a directory? */ def isDirectory: Boolean = false diff --git a/test/files/neg/t4069.check b/test/files/neg/t4069.check index 91bf882cec..08e937bdfe 100644 --- a/test/files/neg/t4069.check +++ b/test/files/neg/t4069.check @@ -12,5 +12,5 @@ t4069.scala:4: error: I encountered a '}' where I didn't expect one, maybe this ^ t4069.scala:10: error: '}' expected but eof found. } -^ + ^ 5 errors found diff --git a/test/files/neg/t4584.check b/test/files/neg/t4584.check index 060160d76a..419f5704b1 100644 --- a/test/files/neg/t4584.check +++ b/test/files/neg/t4584.check @@ -1,4 +1,7 @@ -t4584.scala:1: error: incomplete unicode escape +t4584.scala:1: error: error in unicode escape +class A { val /u2 + ^ +t4584.scala:1: error: illegal character '/uffff' class A { val /u2 ^ -one error found +two errors found diff --git a/test/files/neg/unicode-unterminated-quote.check b/test/files/neg/unicode-unterminated-quote.check index fc5caa6d7e..5085505fb4 100644 --- a/test/files/neg/unicode-unterminated-quote.check +++ b/test/files/neg/unicode-unterminated-quote.check @@ -1,4 +1,7 @@ unicode-unterminated-quote.scala:2: error: unclosed string literal val x = /u0022 ^ -one error found +unicode-unterminated-quote.scala:2: error: '}' expected but eof found. + val x = /u0022 + ^ +two errors found diff --git a/test/files/run/t5385.check b/test/files/run/t5385.check new file mode 100644 index 0000000000..1df74fcfb5 --- /dev/null +++ b/test/files/run/t5385.check @@ -0,0 +1,8 @@ +[0:9] class Azz +[0:9] class Bzz +[0:9] class Czz +[0:9] class Dzz +[0:11] class Ezz +[0:11] class Fzz +[0:13] class Gzz +[0:13] class Hzz diff --git a/test/files/run/t5385.scala b/test/files/run/t5385.scala new file mode 100644 index 0000000000..b803897e71 --- /dev/null +++ b/test/files/run/t5385.scala @@ -0,0 +1,16 @@ +import scala.tools.partest._ + +object Test extends CompilerTest { + import global._ + override def extraSettings = super.extraSettings + " -Yrangepos" + override def sources = List( + "class Azz", "class Bzz ", "class Czz ", "class Dzz\n", + "class Ezz{}", "class Fzz{} ", "class Gzz { }", "class Hzz { } " + ) + def check(source: String, unit: CompilationUnit) { + unit.body foreach { + case cdef: ClassDef => println("%-15s class %s".format(cdef.pos.show, cdef.name)) + case _ => + } + } +} -- cgit v1.2.3 From ea47260be5daf873ff30234d23732f7732d56d1b Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 17 Jul 2012 11:58:56 +0200 Subject: test case closes SI-6047 The bug is not reproducible both in M4 and in M5. --- test/files/pos/t6047.flags | 1 + test/files/pos/t6047.scala | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 test/files/pos/t6047.flags create mode 100644 test/files/pos/t6047.scala diff --git a/test/files/pos/t6047.flags b/test/files/pos/t6047.flags new file mode 100644 index 0000000000..cd66464f2f --- /dev/null +++ b/test/files/pos/t6047.flags @@ -0,0 +1 @@ +-language:experimental.macros \ No newline at end of file diff --git a/test/files/pos/t6047.scala b/test/files/pos/t6047.scala new file mode 100644 index 0000000000..66b52b285f --- /dev/null +++ b/test/files/pos/t6047.scala @@ -0,0 +1,20 @@ +import scala.reflect.makro.Context +import java.io.InputStream + +object Macros { + def unpack[A](input: InputStream): A = macro unpack_impl[A] + + def unpack_impl[A: c.TypeTag](c: Context)(input: c.Expr[InputStream]): c.Expr[A] = { + import c.universe._ + + def unpackcode(tpe: c.Type): c.Expr[_] = { + if (tpe <:< implicitly[c.AbsTypeTag[Traversable[_]]].tpe) { + + } + ??? + } + + unpackcode(c.typeOf[A]) + ??? + } + } \ No newline at end of file -- cgit v1.2.3 From dac4e8f543a8e2e3bacf447d327fc8a7e99acb49 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Mon, 16 Jul 2012 23:12:35 +0200 Subject: SI-5784 Scaladoc: {Abstract,Alias} type templates Normally scaladoc won't generate template pages for anything other than packages, classes, traits and objects. But using the @template annotation on {abstract,alias} types, they get their own page and take part as full members in the diagrams. Furthermore, when looking for the companion object, if a value of type T is in scope, T will be taken as the companion object (even though it might be a class) All templates, including types are listed on the left navigation pane, so now adding @template to String can get scaladoc to generate (a no-comments) page for java.lang.String. The {abstract, alias} type icons need to be updated -- I just took the class icons and added a small x to them -- but they shoud be something else (maybe an underscore?)i TO USE THIS PATCH:
    /** @contentDiagram */ // tells scaladoc to create a diagram of the
                           // templates contained in trait Base
    trait Base {
      /** @template */ // tells scaladoc to create a page for Foo
      type T < Foo
      trait Foo { def foo: Int }
    }
    
    /** @contentDiagram */
    trait Api extends Base {
      /** @template */
      override type T <: FooApi
      trait FooApi extends Foo { def bar: String }
    }
    
    --- src/compiler/scala/tools/ant/Scaladoc.scala | 2 +- src/compiler/scala/tools/nsc/doc/Settings.scala | 8 +- .../scala/tools/nsc/doc/html/HtmlFactory.scala | 7 +- .../scala/tools/nsc/doc/html/HtmlPage.scala | 4 +- .../tools/nsc/doc/html/page/ReferenceIndex.scala | 2 +- .../scala/tools/nsc/doc/html/page/Template.scala | 13 +- .../html/page/diagram/DotDiagramGenerator.scala | 12 ++ .../tools/nsc/doc/html/resource/lib/diagrams.css | 8 + .../scala/tools/nsc/doc/html/resource/lib/index.js | 48 +++--- .../doc/html/resource/lib/object_to_type_big.png | Bin 0 -> 9158 bytes .../scala/tools/nsc/doc/html/resource/lib/type.png | Bin 0 -> 3338 bytes .../tools/nsc/doc/html/resource/lib/type_big.png | Bin 0 -> 7691 bytes .../nsc/doc/html/resource/lib/type_diagram.png | Bin 0 -> 3895 bytes .../doc/html/resource/lib/type_to_object_big.png | Bin 0 -> 9054 bytes .../scala/tools/nsc/doc/model/Entity.scala | 10 +- .../scala/tools/nsc/doc/model/ModelFactory.scala | 181 +++++++++++++-------- .../nsc/doc/model/comment/CommentFactory.scala | 3 +- .../tools/nsc/doc/model/diagram/Diagram.scala | 4 +- .../nsc/doc/model/diagram/DiagramFactory.scala | 10 +- src/library-aux/scala/AnyRef.scala | 1 + .../scala/tools/partest/ScaladocModelTest.scala | 10 +- test/scaladoc/resources/SI-5784.scala | 28 ++++ test/scaladoc/resources/doc-root/AnyRef.scala | 1 + test/scaladoc/run/SI-5533.scala | 4 +- test/scaladoc/run/SI-5784.check | 1 + test/scaladoc/run/SI-5784.scala | 44 +++++ 26 files changed, 281 insertions(+), 120 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/object_to_type_big.png create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/type.png create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/type_big.png create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/type_diagram.png create mode 100644 src/compiler/scala/tools/nsc/doc/html/resource/lib/type_to_object_big.png create mode 100644 test/scaladoc/resources/SI-5784.scala create mode 100644 test/scaladoc/run/SI-5784.check create mode 100644 test/scaladoc/run/SI-5784.scala diff --git a/src/compiler/scala/tools/ant/Scaladoc.scala b/src/compiler/scala/tools/ant/Scaladoc.scala index 61db3c7fa9..03cb770474 100644 --- a/src/compiler/scala/tools/ant/Scaladoc.scala +++ b/src/compiler/scala/tools/ant/Scaladoc.scala @@ -671,7 +671,7 @@ class Scaladoc extends ScalaMatchingTask { exception.printStackTrace() safeBuildError("Document failed because of an internal documenter error (" + exception.getMessage + "); see the error output for details.") - case exception => + case exception : Throwable => exception.printStackTrace() safeBuildError("Document failed because of an internal documenter error " + "(no error message provided); see the error output for details.") diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index 7cb539feee..7662381186 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -183,6 +183,11 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) "" ) + val docExpandAllTypes = BooleanSetting ( + "-expand-all-types", + "Expand all type aliases and abstract types into full template pages. (locally this can be done with the @template annotation)" + ) + // Somewhere slightly before r18708 scaladoc stopped building unless the // self-type check was suppressed. I hijacked the slotted-for-removal-anyway // suppress-vt-warnings option and renamed it for this purpose. @@ -195,7 +200,8 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) docDiagramsDotTimeout, docDiagramsDotRestart, docImplicits, docImplicitsDebug, docImplicitsShowAll, docDiagramsMaxNormalClasses, docDiagramsMaxImplicitClasses, - docNoPrefixes, docNoLinkWarnings, docRawOutput, docSkipPackages + docNoPrefixes, docNoLinkWarnings, docRawOutput, docSkipPackages, + docExpandAllTypes ) val isScaladocSpecific: String => Boolean = scaladocSpecific map (_.name) diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala index 18cc65092b..436425df83 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlFactory.scala @@ -53,11 +53,16 @@ class HtmlFactory(val universe: doc.Universe, index: doc.Index) { "trait.png", "trait_big.png", "trait_diagram.png", + "type.png", + "type_big.png", + "type_diagram.png", "class_to_object_big.png", "object_to_class_big.png", - "object_to_trait_big.png", "trait_to_object_big.png", + "object_to_trait_big.png", + "type_to_object_big.png", + "object_to_type_big.png", "arrow-down.png", "arrow-right.png", diff --git a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala index 226ed49aca..aa2df57967 100644 --- a/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala +++ b/src/compiler/scala/tools/nsc/doc/html/HtmlPage.scala @@ -211,10 +211,12 @@ abstract class HtmlPage extends Page { thisPage => else if (ety.isTrait) "trait_big.png" else if (ety.isClass && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None) "class_to_object_big.png" else if (ety.isClass) "class_big.png" + else if ((ety.isAbstractType || ety.isAliasType) && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None) "type_to_object_big.png" + else if ((ety.isAbstractType || ety.isAliasType)) "type_big.png" else if (ety.isObject && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None && ety.companion.get.isClass) "object_to_class_big.png" else if (ety.isObject && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None && ety.companion.get.isTrait) "object_to_trait_big.png" + else if (ety.isObject && !ety.companion.isEmpty && ety.companion.get.visibility.isPublic && ety.companion.get.inSource != None && (ety.companion.get.isAbstractType || ety.companion.get.isAliasType)) "object_to_trait_big.png" else if (ety.isObject) "object_big.png" else if (ety.isPackage) "package_big.png" else "class_big.png" // FIXME: an entity *should* fall into one of the above categories, but AnyRef is somehow not - } diff --git a/src/compiler/scala/tools/nsc/doc/html/page/ReferenceIndex.scala b/src/compiler/scala/tools/nsc/doc/html/page/ReferenceIndex.scala index a76cc231b4..effaee711d 100755 --- a/src/compiler/scala/tools/nsc/doc/html/page/ReferenceIndex.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/ReferenceIndex.scala @@ -34,7 +34,7 @@ class ReferenceIndex(letter: Char, index: doc.Index, universe: Universe) extends } else { html } - }) + }).toList.distinct
    { diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index d691692920..487ef447e9 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -92,7 +92,7 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp

    { templatesToHtml(tpl.inTemplate.toRoot.reverse.tail, xml.Text(".")) }

    } - +
    { tpl.companion match { @@ -734,20 +734,21 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp } }{ if (isReduced) NodeSeq.Empty else { mbr match { - case tpl: DocTemplateEntity if !tpl.parentTypes.isEmpty => - extends { typeToHtml(tpl.parentTypes.map(_._2), hasLinks) } - case tme: MemberEntity if (tme.isDef || tme.isVal || tme.isLazyVal || tme.isVar) => : { typeToHtml(tme.resultType, hasLinks) } - case abt: AbstractType => + case abt: MemberEntity with AbstractType => val b2s = boundsToHtml(abt.hi, abt.lo, hasLinks) if (b2s != NodeSeq.Empty) { b2s } else NodeSeq.Empty - case alt: AliasType => + case alt: MemberEntity with AliasType => = { typeToHtml(alt.alias, hasLinks) } + + case tpl: MemberTemplateEntity if !tpl.parentTypes.isEmpty => + extends { typeToHtml(tpl.parentTypes.map(_._2), hasLinks) } + case _ => NodeSeq.Empty } }} diff --git a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala index f3454f71b8..bae61f1a3b 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/diagram/DotDiagramGenerator.scala @@ -242,6 +242,8 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { attr ++= classStyle else if(node.isObjectNode) attr ++= objectStyle + else if(node.isTypeNode) + attr ++= typeStyle else attr ++= defaultStyle @@ -254,6 +256,8 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { img = "class_diagram.png" else if(node.isObjectNode) img = "object_diagram.png" + else if(node.isTypeNode) + img = "type_diagram.png" if(!img.equals("")) { img = "" @@ -307,6 +311,8 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { space + "trait" else if (node.isObjectNode) space + "object" + else if (node.isTypeNode) + space + "type" else default @@ -491,6 +497,12 @@ class DotDiagramGenerator(settings: doc.Settings) extends DiagramGenerator { "fontcolor" -> "#ffffff" ) + private val typeStyle = Map( + "color" -> "#115F3B", + "fillcolor" -> "#0A955B", + "fontcolor" -> "#ffffff" + ) + private def flatten(attributes: Map[String, String]) = attributes.map{ case (key, value) => key + "=\"" + value + "\"" }.mkString(", ") private val graphAttributesStr = graphAttributes.map{ case (key, value) => key + "=\"" + value + "\";\n" }.mkString diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/diagrams.css b/src/compiler/scala/tools/nsc/doc/html/resource/lib/diagrams.css index 04d29580b7..5fe33f72f5 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/diagrams.css +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/diagrams.css @@ -119,6 +119,14 @@ svg.class-diagram .node.this.trait.over polygon fill: #235d7b; } +svg.package-diagram .node.type.over polygon, +svg.class-diagram .node.this.type.over polygon +{ + fill: #098552; + fill: #04663e; +} + + svg.package-diagram .node.object.over polygon { fill: #183377; diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.js b/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.js index eb7f752440..f29438edfb 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.js +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/index.js @@ -15,15 +15,15 @@ var lastHash = ""; $(document).ready(function() { $('body').layout({ west__size: '20%' }); - $('#browser').layout({ + $('#browser').layout({ center__paneSelector: ".ui-west-center" //,center__initClosed:true ,north__paneSelector: ".ui-west-north" - }); + }); $('iframe').bind("load", function(){ var subtitle = $(this).contents().find('title').text(); $(document).attr('title', (title ? title + " - " : "") + subtitle); - + setUrlFragmentFromFrameSrc(); }); @@ -81,16 +81,16 @@ function setUrlFragmentFromFrameSrc() { var commonLength = location.pathname.lastIndexOf("/"); var frameLocation = frames["template"].location; var relativePath = frameLocation.pathname.slice(commonLength + 1); - + if(!relativePath || frameLocation.pathname.indexOf("/") < 0) return; - + // Add #, remove ".html" and replace "/" with "." fragment = "#" + relativePath.replace(/\.html$/, "").replace(/\//g, "."); - + // Add the frame's hash after an @ if(frameLocation.hash) fragment += ("@" + frameLocation.hash.slice(1)); - + // Use replace to not add history items lastFragment = fragment; location.replace(fragment); @@ -109,7 +109,7 @@ var Index = {}; if (type == 'object') { href = t['object']; } else { - href = t['class'] || t['trait'] || t['case class']; + href = t['class'] || t['trait'] || t['case class'] || t['type']; } return [ '' : ''; - inner += openLink(template, template['trait'] ? 'trait' : 'class'); + inner += openLink(template, template['trait'] ? 'trait' : template['type'] ? 'type' : 'class'); } else { inner += '
    '; } @@ -245,6 +245,7 @@ function configureEntityList() { function prepareEntityList() { var classIcon = $("#library > img.class"); var traitIcon = $("#library > img.trait"); + var typeIcon = $("#library > img.type"); var objectIcon = $("#library > img.object"); var packageIcon = $("#library > img.package"); @@ -252,6 +253,7 @@ function prepareEntityList() { $('#tpl li.pack').each(function () { $("span.class", this).each(function() { $(this).replaceWith(classIcon.clone()); }); $("span.trait", this).each(function() { $(this).replaceWith(traitIcon.clone()); }); + $("span.type", this).each(function() { $(this).replaceWith(typeIcon.clone()); }); $("span.object", this).each(function() { $(this).replaceWith(objectIcon.clone()); }); $("span.package", this).each(function() { $(this).replaceWith(packageIcon.clone()); }); }); @@ -265,11 +267,11 @@ function keyboardScrolldownLeftPane() { scheduler.add("init", function() { $("#textfilter input").blur(); var $items = $("#tpl li"); - $items.first().addClass('selected'); + $items.first().addClass('selected'); $(window).bind("keydown", function(e) { var $old = $items.filter('.selected'), - $new; + $new; switch ( e.keyCode ) { @@ -286,7 +288,7 @@ function keyboardScrolldownLeftPane() { case 27: // escape $old.removeClass('selected'); $(window).unbind(e); - $("#textfilter input").focus(); + $("#textfilter input").focus(); break; @@ -296,7 +298,7 @@ function keyboardScrolldownLeftPane() { if (!$new.length) { $new = $old.parent().prev(); } - + if ($new.is('ol') && $new.children(':last').is('ol')) { $new = $new.children().children(':last'); } else if ($new.is('ol')) { @@ -313,17 +315,17 @@ function keyboardScrolldownLeftPane() { if ($new.is('ol')) { $new = $new.children(':first'); } - break; - } - + break; + } + if ($new.is('li')) { $old.removeClass('selected'); - $new.addClass('selected'); + $new.addClass('selected'); } else if (e.keyCode == 38) { $(window).unbind(e); $("#textfilter input").focus(); } - }); + }); }); } @@ -342,13 +344,13 @@ function configureTextFilter() { $("#template").contents().find("#mbrsel-input").focus(); input.attr("value", ""); return false; - } + } if (event.keyCode == 40) { // down arrow $(window).unbind("keydown"); keyboardScrolldownLeftPane(); return false; - } - textFilter(); + } + textFilter(); }); input.focus(function(event) { input.select(); }); }); @@ -419,7 +421,7 @@ function textFilter() { }); configureHideFilter(); }; - + scheduler.add('filter', searchLoop); } diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/object_to_type_big.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/object_to_type_big.png new file mode 100644 index 0000000000..7502942eb6 Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/object_to_type_big.png differ diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/type.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type.png new file mode 100644 index 0000000000..366ec4e992 Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type.png differ diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_big.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_big.png new file mode 100644 index 0000000000..df0dc118bf Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_big.png differ diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_diagram.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_diagram.png new file mode 100644 index 0000000000..d6fbb84ff2 Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_diagram.png differ diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_to_object_big.png b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_to_object_big.png new file mode 100644 index 0000000000..1bd2833a63 Binary files /dev/null and b/src/compiler/scala/tools/nsc/doc/html/resource/lib/type_to_object_big.png differ diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index 620aa4253f..0836d7e4da 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -241,6 +241,11 @@ trait MemberTemplateEntity extends TemplateEntity with MemberEntity with HigherK /** The value parameters of this case class, or an empty list if this class is not a case class. As case class value * parameters cannot be curried, the outer list has exactly one element. */ def valueParams: List[List[ValueParam]] + + /** The direct super-type of this template + e.g: {{{class A extends B[C[Int]] with D[E]}}} will have two direct parents: class B and D + NOTE: we are dropping the refinement here! */ + def parentTypes: List[(TemplateEntity, TypeEntity)] } /** A template (class, trait, object or package) for which documentation is available. Only templates for which @@ -259,11 +264,6 @@ trait DocTemplateEntity extends MemberTemplateEntity { * only if the `docsourceurl` setting has been set. */ def sourceUrl: Option[java.net.URL] - /** The direct super-type of this template - e.g: {{{class A extends B[C[Int]] with D[E]}}} will have two direct parents: class B and D - NOTE: we are dropping the refinement here! */ - def parentTypes: List[(TemplateEntity, TypeEntity)] - /** All class, trait and object templates which are part of this template's linearization, in lineratization order. * This template's linearization contains all of its direct and indirect super-classes and super-traits. */ def linearizationTemplates: List[TemplateEntity] diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index fc5fde3239..0ba32fceaa 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -265,6 +265,32 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { override def isNoDocMemberTemplate = true lazy val definitionName = optimize(inDefinitionTemplates.head.qualifiedName + "." + name) def valueParams: List[List[ValueParam]] = Nil /** TODO, these are now only computed for DocTemplates */ + + // Seems unused + // def parentTemplates = + // if (sym.isPackage || sym == AnyClass) + // List() + // else + // sym.tpe.parents.flatMap { tpe: Type => + // val tSym = tpe.typeSymbol + // if (tSym != NoSymbol) + // List(makeTemplate(tSym)) + // else + // List() + // } filter (_.isInstanceOf[DocTemplateEntity]) + + def parentTypes = + if (sym.isPackage || sym == AnyClass) List() else { + val tps = (this match { + case a: AliasType => sym.tpe.dealias.parents + case a: AbstractType => sym.info.bounds match { + case TypeBounds(lo, hi) => List(hi) + case _ => Nil + } + case _ => sym.tpe.parents + }) map { _.asSeenFrom(sym.thisType, sym) } + makeParentTypes(RefinedType(tps, EmptyScope), Some(this), inTpl) + } } /** The instantiation of `TemplateImpl` triggers the creation of the following entities: @@ -306,24 +332,6 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { else None } - def parentTemplates = - if (sym.isPackage || sym == AnyClass) - List() - else - sym.tpe.parents.flatMap { tpe: Type => - val tSym = tpe.typeSymbol - if (tSym != NoSymbol) - List(makeTemplate(tSym)) - else - List() - } filter (_.isInstanceOf[DocTemplateEntity]) - - def parentTypes = - if (sym.isPackage || sym == AnyClass) List() else { - val tps = sym.tpe.parents map { _.asSeenFrom(sym.thisType, sym) } - makeParentTypes(RefinedType(tps, EmptyScope), Some(this), inTpl) - } - protected def linearizationFromSymbol(symbol: Symbol): List[(TemplateEntity, TypeEntity)] = { symbol.ancestors map { ancestor => val typeEntity = makeType(symbol.info.baseType(ancestor), this) @@ -378,7 +386,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // the direct members (methods, values, vars, types and directly contained templates) var memberSymsEager = memberSyms.filter(!memberSymsLazy.contains(_)) // the members generated by the symbols in memberSymsEager - val ownMembers = (memberSyms.flatMap(makeMember(_, None, this))) + val ownMembers = (memberSymsEager.flatMap(makeMember(_, None, this))) // all the members that are documentented PLUS the members inherited by implicit conversions var members: List[MemberImpl] = ownMembers @@ -395,11 +403,13 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { */ def completeModel: Unit = { // DFS completion - for (member <- members) - member match { - case d: DocTemplateImpl => d.completeModel - case _ => - } + // since alias types and abstract types have no own members, there's no reason for them to call completeModel + if (!sym.isAliasType && !sym.isAbstractType) + for (member <- members) + member match { + case d: DocTemplateImpl => d.completeModel + case _ => + } members :::= memberSymsLazy.map(modelCreation.createLazyTemplateMember(_, this)) @@ -432,15 +442,33 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { override def isTemplate = true override def isDocTemplate = true - def companion = sym.companionSymbol match { - case NoSymbol => None - case comSym if !isEmptyJavaObject(comSym) && (comSym.isClass || comSym.isModule) => - makeTemplate(comSym) match { - case d: DocTemplateImpl => Some(d) - case _ => None + private[this] lazy val companionSymbol = + if (sym.isAliasType || sym.isAbstractType) { + inTpl.sym.info.member(sym.name.toTermName) match { + case NoSymbol => NoSymbol + case s => + s.info match { + case ot: OverloadedType => + NoSymbol + case _ => + // that's to navigate from val Foo: FooExtractor to FooExtractor :) + s.info.resultType.typeSymbol + } } - case _ => None - } + } + else + sym.companionSymbol + + def companion = + companionSymbol match { + case NoSymbol => None + case comSym if !isEmptyJavaObject(comSym) && (comSym.isClass || comSym.isModule) => + makeTemplate(comSym) match { + case d: DocTemplateImpl => Some(d) + case _ => None + } + case _ => None + } def constructors: List[MemberImpl with Constructor] = if (isClass) members collect { case d: Constructor => d } else Nil def primaryConstructor: Option[MemberImpl with Constructor] = if (isClass) constructors find { _.isPrimary } else None @@ -523,6 +551,12 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { val name = optimize(sym.nameString) } + private trait AliasImpl { + def sym: Symbol + def inTpl: TemplateImpl + def alias = makeTypeInTemplateContext(sym.tpe.dealias, inTpl, sym) + } + private trait TypeBoundsImpl { def sym: Symbol def inTpl: TemplateImpl @@ -591,13 +625,16 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def createRootPackage: PackageImpl = docTemplatesCache.get(RootPackage) match { case Some(root: PackageImpl) => root - case _ => modelCreation.createTemplate(RootPackage, null).asInstanceOf[PackageImpl] + case _ => modelCreation.createTemplate(RootPackage, null) match { + case Some(root: PackageImpl) => root + case _ => sys.error("Scaladoc: Unable to create root package!") + } } /** * Create a template, either a package, class, trait or object */ - def createTemplate(aSym: Symbol, inTpl: DocTemplateImpl): DocTemplateImpl = { + def createTemplate(aSym: Symbol, inTpl: DocTemplateImpl): Option[MemberImpl] = { // don't call this after the model finished! assert(!modelFinished) @@ -616,11 +653,15 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { def createDocTemplate(bSym: Symbol, inTpl: DocTemplateImpl): DocTemplateImpl = { assert(!modelFinished) // only created BEFORE the model is finished - if (bSym.isModule || (bSym.isAliasType && bSym.tpe.typeSymbol.isModule)) + if (bSym.isAliasType && bSym != AnyRefClass) + new DocTemplateImpl(bSym, inTpl) with AliasImpl with AliasType { override def isAliasType = true } + else if (bSym.isAbstractType) + new DocTemplateImpl(bSym, inTpl) with TypeBoundsImpl with AbstractType { override def isAbstractType = true } + else if (bSym.isModule) new DocTemplateImpl(bSym, inTpl) with Object {} - else if (bSym.isTrait || (bSym.isAliasType && bSym.tpe.typeSymbol.isTrait)) + else if (bSym.isTrait) new DocTemplateImpl(bSym, inTpl) with Trait {} - else if (bSym.isClass || (bSym.isAliasType && bSym.tpe.typeSymbol.isClass)) + else if (bSym.isClass || bSym == AnyRefClass) new DocTemplateImpl(bSym, inTpl) with Class {} else sys.error("'" + bSym + "' isn't a class, trait or object thus cannot be built as a documentable template.") @@ -628,7 +669,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { val bSym = normalizeTemplate(aSym) if (docTemplatesCache isDefinedAt bSym) - return docTemplatesCache(bSym) + return Some(docTemplatesCache(bSym)) /* Three cases of templates: * (1) root package -- special cased for bootstrapping @@ -636,7 +677,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { * (3) class/object/trait */ if (bSym == RootPackage) // (1) - new RootPackageImpl(bSym) { + Some(new RootPackageImpl(bSym) { override lazy val comment = createRootPackageComment override val name = "root" override def inTemplate = this @@ -648,21 +689,28 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { (bSym.info.members ++ EmptyPackage.info.members) filter { s => s != EmptyPackage && s != RootPackage } - } + }) else if (bSym.isPackage) // (2) - inTpl match { - case inPkg: PackageImpl => - val pack = new PackageImpl(bSym, inPkg) {} - if (pack.templates.filter(_.isDocTemplate).isEmpty && pack.memberSymsLazy.isEmpty) - droppedPackages += pack - pack - case _ => - sys.error("'" + bSym + "' must be in a package") - } + if (settings.skipPackage(makeQualifiedName(bSym))) + None + else + inTpl match { + case inPkg: PackageImpl => + val pack = new PackageImpl(bSym, inPkg) {} + // Used to check package pruning works: + //println(pack.qualifiedName) + if (pack.templates.filter(_.isDocTemplate).isEmpty && pack.memberSymsLazy.isEmpty) { + droppedPackages += pack + None + } else + Some(pack) + case _ => + sys.error("'" + bSym + "' must be in a package") + } else { // no class inheritance at this point - assert(inOriginalOwner(bSym, inTpl)) - createDocTemplate(bSym, inTpl) + assert(inOriginalOwner(bSym, inTpl) || bSym.isAbstractType || bSym.isAliasType, bSym + " in " + inTpl) + Some(createDocTemplate(bSym, inTpl)) } } @@ -750,28 +798,16 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { Some(new NonTemplateMemberImpl(bSym, conversion, useCaseOf, inTpl) with Val { override def isVal = true }) - else if (bSym.isAbstractType) + else if (bSym.isAbstractType && !typeShouldDocument(bSym, inTpl)) Some(new MemberTemplateImpl(bSym, inTpl) with TypeBoundsImpl with AbstractType { override def isAbstractType = true }) - else if (bSym.isAliasType && bSym != AnyRefClass) - Some(new MemberTemplateImpl(bSym, inTpl) with AliasType { + else if (bSym.isAliasType && !typeShouldDocument(bSym, inTpl)) + Some(new MemberTemplateImpl(bSym, inTpl) with AliasImpl with AliasType { override def isAliasType = true - def alias = makeTypeInTemplateContext(sym.tpe.dealias, inTpl, sym) }) - else if (bSym.isPackage && !modelFinished) - if (settings.skipPackage(makeQualifiedName(bSym))) None else - inTpl match { - case inPkg: PackageImpl => modelCreation.createTemplate(bSym, inTpl) match { - case p: PackageImpl if droppedPackages contains p => None - case p: PackageImpl => Some(p) - case _ => sys.error("'" + bSym + "' must be a package") - } - case _ => - sys.error("'" + bSym + "' must be in a package") - } - else if (!modelFinished && templateShouldDocument(bSym, inTpl) && inOriginalOwner(bSym, inTpl)) - Some(modelCreation.createTemplate(bSym, inTpl)) + else if (!modelFinished && (bSym.isPackage || bSym.isAliasType || bSym.isAbstractType || templateShouldDocument(bSym, inTpl))) + modelCreation.createTemplate(bSym, inTpl) else None } @@ -915,7 +951,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } /** Get the types of the parents of the current class, ignoring the refinements */ - def makeParentTypes(aType: Type, tpl: Option[DocTemplateImpl], inTpl: TemplateImpl): List[(TemplateEntity, TypeEntity)] = aType match { + def makeParentTypes(aType: Type, tpl: Option[MemberTemplateImpl], inTpl: TemplateImpl): List[(TemplateEntity, TypeEntity)] = aType match { case RefinedType(parents, defs) => val ignoreParents = Set[Symbol](AnyClass, AnyRefClass, ObjectClass) val filtParents = @@ -978,7 +1014,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { normalizeTemplate(aSym.owner) == normalizeTemplate(inTpl.sym) def templateShouldDocument(aSym: Symbol, inTpl: TemplateImpl): Boolean = - (aSym.isClass || aSym.isModule || aSym == AnyRefClass) && + (aSym.isTrait || aSym.isClass || aSym.isModule) && localShouldDocument(aSym) && !isEmptyJavaObject(aSym) && // either it's inside the original owner or we can document it later: @@ -1014,5 +1050,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // the implicit conversions that are excluded from the pages should not appear in the diagram def implicitExcluded(convertorMethod: String): Boolean = settings.hardcoded.commonConversionTargets.contains(convertorMethod) + + // whether or not to create a page for an {abstract,alias} type + def typeShouldDocument(bSym: Symbol, inTpl: DocTemplateImpl) = + (settings.docExpandAllTypes.value && (bSym.sourceFile != null)) || + global.expandedDocComment(bSym, inTpl.sym).contains("@template") } 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 df913555a7..a57ccd36c2 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala @@ -350,7 +350,8 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member case None => List.empty } - val tagsWithoutDiagram = tags.filterNot(pair => pair._1 == inheritDiagramTag || pair._1 == contentDiagramTag) + val stripTags=List(inheritDiagramTag, contentDiagramTag, SimpleTagKey("template")) + val tagsWithoutDiagram = tags.filterNot(pair => stripTags.contains(pair._1)) val bodyTags: mutable.Map[TagKey, List[Body]] = mutable.Map(tagsWithoutDiagram mapValues {tag => tag map (parseWiki(_, pos, inTplOpt))} toSeq: _*) diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala index 902d5da240..c2aa1f17f3 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/Diagram.scala @@ -70,7 +70,8 @@ abstract class Node { def isClassNode = if (tpl.isDefined) (tpl.get.isClass || tpl.get.qualifiedName == "scala.AnyRef") else false def isTraitNode = if (tpl.isDefined) tpl.get.isTrait else false def isObjectNode= if (tpl.isDefined) tpl.get.isObject else false - def isOtherNode = !(isClassNode || isTraitNode || isObjectNode) + def isTypeNode = if (doctpl.isDefined) doctpl.get.isAbstractType || doctpl.get.isAliasType else false + def isOtherNode = !(isClassNode || isTraitNode || isObjectNode || isTypeNode) def isImplicitNode = false def isOutsideNode = false def tooltip: Option[String] @@ -91,6 +92,7 @@ abstract class Node { object Node { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = Some((n.tpe, n.tpl)) } object ClassNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isClassNode) Some((n.tpe, n.tpl)) else None } object TraitNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isTraitNode) Some((n.tpe, n.tpl)) else None } +object TypeNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isTypeNode) Some((n.tpe, n.tpl)) else None } object ObjectNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isObjectNode) Some((n.tpe, n.tpl)) else None } object OutsideNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isOutsideNode) Some((n.tpe, n.tpl)) else None } object OtherNode { def unapply(n: Node): Option[(TypeEntity, Option[TemplateEntity])] = if (n.isOtherNode) Some((n.tpe, n.tpl)) else None } diff --git a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala index e0f6d42a68..2645d8fd14 100644 --- a/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/diagram/DiagramFactory.scala @@ -121,12 +121,10 @@ trait DiagramFactory extends DiagramDirectiveParser { // for each node, add its subclasses for (node <- nodesAll if !classExcluded(node)) { node match { - case dnode: DocTemplateImpl => - var superClasses = dnode.parentTypes.map(_._1) + case dnode: MemberTemplateImpl => + var superClasses = dnode.parentTypes.map(_._1).filter(nodesAll.contains(_)) - superClasses = superClasses.filter(nodesAll.contains(_)) - - // TODO: Everyone should be able to use the @{inherit,content}Diagram annotation to change the diagrams. + // TODO: Everyone should be able to use the @{inherit,content}Diagram annotation to add nodes to diagrams. if (pack.sym == ScalaPackage) if (dnode.sym == NullClass) superClasses = List(makeTemplate(AnyRefClass)) @@ -142,7 +140,7 @@ trait DiagramFactory extends DiagramDirectiveParser { } mapNodes += node -> ( - if (node.inTemplate == pack && !node.isNoDocMemberTemplate) + if (node.inTemplate == pack && (node.isDocTemplate || node.isAbstractType || node.isAliasType)) NormalNode(node.resultType, Some(node))() else OutsideNode(node.resultType, Some(node))() diff --git a/src/library-aux/scala/AnyRef.scala b/src/library-aux/scala/AnyRef.scala index 1eefb0c806..7d8b9f9e76 100644 --- a/src/library-aux/scala/AnyRef.scala +++ b/src/library-aux/scala/AnyRef.scala @@ -10,6 +10,7 @@ package scala /** Class `AnyRef` is the root class of all ''reference types''. * All types except the value types descend from this class. + * @template */ trait AnyRef extends Any { diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala index d8d5dfbbeb..adf7abe11c 100644 --- a/src/partest/scala/tools/partest/ScaladocModelTest.scala +++ b/src/partest/scala/tools/partest/ScaladocModelTest.scala @@ -130,11 +130,17 @@ abstract class ScaladocModelTest extends DirectTest { def _conversion(name: String): ImplicitConversion = getTheFirst(_conversions(name), tpl.qualifiedName + ".conversion(" + name + ")") def _conversions(name: String): List[ImplicitConversion] = tpl.conversions.filter(_.conversionQualifiedName == name) - def _absType(name: String): MemberEntity = getTheFirst(_methods(name), tpl.qualifiedName + ".abstractType(" + name + ")") + def _absType(name: String): MemberEntity = getTheFirst(_absTypes(name), tpl.qualifiedName + ".abstractType(" + name + ")") def _absTypes(name: String): List[MemberEntity] = tpl.members.filter(mbr => mbr.name == name && mbr.isAbstractType) - def _aliasType(name: String): MemberEntity = getTheFirst(_methods(name), tpl.qualifiedName + ".aliasType(" + name + ")") + def _absTypeTpl(name: String): DocTemplateEntity = getTheFirst(_absTypeTpls(name), tpl.qualifiedName + ".abstractType(" + name + ")") + def _absTypeTpls(name: String): List[DocTemplateEntity] = tpl.members.collect({ case dtpl: DocTemplateEntity with AbstractType if dtpl.name == name => dtpl }) + + def _aliasType(name: String): MemberEntity = getTheFirst(_aliasTypes(name), tpl.qualifiedName + ".aliasType(" + name + ")") def _aliasTypes(name: String): List[MemberEntity] = tpl.members.filter(mbr => mbr.name == name && mbr.isAliasType) + + def _aliasTypeTpl(name: String): DocTemplateEntity = getTheFirst(_aliasTypeTpls(name), tpl.qualifiedName + ".aliasType(" + name + ")") + def _aliasTypeTpls(name: String): List[DocTemplateEntity] = tpl.members.collect({ case dtpl: DocTemplateEntity with AliasType if dtpl.name == name => dtpl }) } class PackageAccess(pack: Package) extends TemplateAccess(pack) { diff --git a/test/scaladoc/resources/SI-5784.scala b/test/scaladoc/resources/SI-5784.scala new file mode 100644 index 0000000000..175cc3cf33 --- /dev/null +++ b/test/scaladoc/resources/SI-5784.scala @@ -0,0 +1,28 @@ +package test.templates { + object `package` { + /** @template */ + type String = java.lang.String + val String = new StringCompanion + class StringCompanion { def boo = ??? } + } + + /** @contentDiagram */ + trait Base { + /** @template */ + type String = test.templates.String + /** @template + * @inheritanceDiagram */ + type T <: Foo + val T: FooExtractor + trait Foo { def foo: Int } + trait FooExtractor { def apply(foo: Int); def unapply(t: Foo): Option[Int] } + } + + /** @contentDiagram */ + trait Api extends Base { + /** @template + * @inheritanceDiagram */ + override type T <: FooApi + trait FooApi extends Foo { def bar: String } + } +} diff --git a/test/scaladoc/resources/doc-root/AnyRef.scala b/test/scaladoc/resources/doc-root/AnyRef.scala index 1eefb0c806..7d8b9f9e76 100644 --- a/test/scaladoc/resources/doc-root/AnyRef.scala +++ b/test/scaladoc/resources/doc-root/AnyRef.scala @@ -10,6 +10,7 @@ package scala /** Class `AnyRef` is the root class of all ''reference types''. * All types except the value types descend from this class. + * @template */ trait AnyRef extends Any { diff --git a/test/scaladoc/run/SI-5533.scala b/test/scaladoc/run/SI-5533.scala index e7b5f57860..989d9aa13a 100644 --- a/test/scaladoc/run/SI-5533.scala +++ b/test/scaladoc/run/SI-5533.scala @@ -32,6 +32,8 @@ object Test extends ScaladocModelTest { val B = b._class("B") val D = b._class("D") testDiagram(B, B.contentDiagram, 2, 1) - testDiagram(D, D.contentDiagram, 2, 1) + // unfortunately not all packages, as B1 extends A.this.A1 and it gets the wrong member -- maybe we should model + // things as we do for symbols? + testDiagram(D, D.contentDiagram, 3, 2) } } \ No newline at end of file diff --git a/test/scaladoc/run/SI-5784.check b/test/scaladoc/run/SI-5784.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/SI-5784.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/SI-5784.scala b/test/scaladoc/run/SI-5784.scala new file mode 100644 index 0000000000..318eb78b2a --- /dev/null +++ b/test/scaladoc/run/SI-5784.scala @@ -0,0 +1,44 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def resourceFile: String = "SI-5784.scala" + + // no need for special settings + def scaladocSettings = "-diagrams" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + val main = rootPackage._package("test")._package("templates") + + val String = main._aliasTypeTpl("String") + assert(String.companion.isDefined, "test.templates.String should have a pseudo-companion object") + + val Base = main._trait("Base") + assert(Base.members.filter(_.inDefinitionTemplates.head == Base).length == 5, Base.members.filter(_.inDefinitionTemplates.head == Base).length + " == 5") + assert(Base.members.collect{case d: DocTemplateEntity => d}.length == 4, Base.members.collect{case d: DocTemplateEntity => d}.length == 4) + testDiagram(Base, Base.contentDiagram, 2, 1) + + val BaseT = Base._absTypeTpl("T") + val Foo = Base._trait("Foo") + assert(BaseT.members.filter(_.inDefinitionTemplates.head == Base).length == 0, BaseT.members.filter(_.inDefinitionTemplates.head == Base).length + " == 0") + assert(BaseT.members.map(_.name).sorted == Foo.members.map(_.name).sorted, BaseT.members.map(_.name).sorted + " == " + Foo.members.map(_.name).sorted) + assert(BaseT.companion.isDefined, "test.templates.Base.T should have a pseudo-companion object") + testDiagram(BaseT, BaseT.inheritanceDiagram, 2, 1) + + val Api = main._trait("Api") + assert(Api.members.filter(_.inDefinitionTemplates.head == Api).length == 2, Api.members.filter(_.inDefinitionTemplates.head == Api).length + " == 2") // FooApi and override type T + assert(Api.members.collect{case d: DocTemplateEntity => d}.length == 5, Api.members.collect{case d: DocTemplateEntity => d}.length == 5) + testDiagram(Api, Api.contentDiagram, 3, 2) + + val ApiT = Api._absTypeTpl("T") + val FooApi = Api._trait("FooApi") + assert(ApiT.members.filter(_.inDefinitionTemplates.head == Api).length == 0, ApiT.members.filter(_.inDefinitionTemplates.head == Api).length + " == 0") + assert(ApiT.members.map(_.name).sorted == FooApi.members.map(_.name).sorted, ApiT.members.map(_.name).sorted + " == " + FooApi.members.map(_.name).sorted) + assert(ApiT.companion.isDefined, "test.templates.Api.T should have a pseudo-companion object") + testDiagram(ApiT, ApiT.inheritanceDiagram, 2, 1) + } +} \ No newline at end of file -- cgit v1.2.3 From bab827a5426aeb654006573712eb7aabc047db36 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Wed, 11 Jul 2012 14:46:01 +0200 Subject: SI-5895 fixes FieldMirrors reflectField now accepts getters and setters along with the field symbols, it also checks whether a field has a reasonable binary representation (this is necessary, because ctor parameters that are unused outside of their declaring constructors don't get compiled down to Java fields/methods). --- .../scala/reflect/runtime/JavaMirrors.scala | 25 ++++++++++++------- .../reflection-fieldmirror-accessorsareokay.check | 6 +++++ .../reflection-fieldmirror-accessorsareokay.scala | 29 ++++++++++++++++++++++ .../run/reflection-fieldmirror-ctorparam.check | 4 ++- .../run/reflection-fieldmirror-sanitycheck.check | 6 ----- .../run/reflection-fieldmirror-sanitycheck.scala | 22 ---------------- 6 files changed, 54 insertions(+), 38 deletions(-) create mode 100644 test/files/run/reflection-fieldmirror-accessorsareokay.check create mode 100644 test/files/run/reflection-fieldmirror-accessorsareokay.scala delete mode 100644 test/files/run/reflection-fieldmirror-sanitycheck.check delete mode 100644 test/files/run/reflection-fieldmirror-sanitycheck.scala diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 185621efa4..1c5ea9caba 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -133,15 +133,22 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym def symbol = wholemirror.classSymbol(obj.getClass) def reflectField(field: TermSymbol): FieldMirror = { // [Eugene+++] check whether `field` represents a member of a `symbol` - if (field.isMethod || field.isModule) throw new Error(s""" - |expected a field symbol, you provided a ${field.kind} symbol - |A typical cause of this problem is using a field accessor symbol instead of a field symbol. - |To obtain a field symbol append nme.LOCAL_SUFFIX_STRING to the name of the field, - |when searching for a member with Type.members or Type.declarations. - |This is a temporary inconvenience that will be resolved before 2.10.0-final. - |More information can be found here: https://issues.scala-lang.org/browse/SI-5895. - """.trim.stripMargin) - new JavaFieldMirror(obj, field) + if ((field.isMethod && !field.isAccessor) || field.isModule) throw new Error(s"expected a field or accessor method symbol, you provided a ${field.kind} symbol") + val name = + if (field.isGetter) nme.getterToLocal(field.name) + else if (field.isSetter) nme.getterToLocal(nme.setterToGetter(field.name)) + else field.name + val field1 = (field.owner.info decl name).asTermSymbol + try fieldToJava(field1) + catch { + case _: NoSuchFieldException => + throw new Error(s""" + |this Scala field isn't represented as a Java field, neither it has a Java accessor method + |note that private parameters of class constructors don't get mapped onto fields and/or accessors, + |unless they are used outside of their declaring constructors. + """.trim.stripMargin) + } + new JavaFieldMirror(obj, field1) } def reflectMethod(method: MethodSymbol): MethodMirror = { // [Eugene+++] check whether `method` represents a member of a `symbol` diff --git a/test/files/run/reflection-fieldmirror-accessorsareokay.check b/test/files/run/reflection-fieldmirror-accessorsareokay.check new file mode 100644 index 0000000000..635dcd04ce --- /dev/null +++ b/test/files/run/reflection-fieldmirror-accessorsareokay.check @@ -0,0 +1,6 @@ +true +42 +2 +true +2 +2 diff --git a/test/files/run/reflection-fieldmirror-accessorsareokay.scala b/test/files/run/reflection-fieldmirror-accessorsareokay.scala new file mode 100644 index 0000000000..c586ce9bdd --- /dev/null +++ b/test/files/run/reflection-fieldmirror-accessorsareokay.scala @@ -0,0 +1,29 @@ +import scala.reflect.runtime.universe._ +import scala.reflect.runtime.{currentMirror => cm} + +object Test extends App { + class A { + var x: Int = 42 + } + + val a = new A + + val im: InstanceMirror = cm.reflect(a) + val cs = im.symbol + + def test(f: Symbol) = { + try { + val fm: FieldMirror = im.reflectField(f.asTermSymbol) + println(fm.symbol.isVariable) + println(fm.get) + fm.set(2) + println(fm.get) + } catch { + case ex: Throwable => + println(ex.getMessage) + } + } + + test(cs.typeSignature.declaration(newTermName("x")).asTermSymbol) + test(cs.typeSignature.declaration(newTermName("x_$eq")).asTermSymbol) +} diff --git a/test/files/run/reflection-fieldmirror-ctorparam.check b/test/files/run/reflection-fieldmirror-ctorparam.check index 8b99a6f772..7ae2cec81e 100644 --- a/test/files/run/reflection-fieldmirror-ctorparam.check +++ b/test/files/run/reflection-fieldmirror-ctorparam.check @@ -1 +1,3 @@ -class java.lang.NoSuchFieldException: Test$A$$x +class java.lang.Error: this Scala field isn't represented as a Java field, neither it has a Java accessor method +note that private parameters of class constructors don't get mapped onto fields and/or accessors, +unless they are used outside of their declaring constructors. diff --git a/test/files/run/reflection-fieldmirror-sanitycheck.check b/test/files/run/reflection-fieldmirror-sanitycheck.check deleted file mode 100644 index e5134de4e3..0000000000 --- a/test/files/run/reflection-fieldmirror-sanitycheck.check +++ /dev/null @@ -1,6 +0,0 @@ -expected a field symbol, you provided a method symbol -A typical cause of this problem is using a field accessor symbol instead of a field symbol. -To obtain a field symbol append nme.LOCAL_SUFFIX_STRING to the name of the field, -when searching for a member with Type.members or Type.declarations. -This is a temporary inconvenience that will be resolved before 2.10.0-final. -More information can be found here: https://issues.scala-lang.org/browse/SI-5895. diff --git a/test/files/run/reflection-fieldmirror-sanitycheck.scala b/test/files/run/reflection-fieldmirror-sanitycheck.scala deleted file mode 100644 index 6a992dd282..0000000000 --- a/test/files/run/reflection-fieldmirror-sanitycheck.scala +++ /dev/null @@ -1,22 +0,0 @@ -import scala.reflect.runtime.universe._ -import scala.reflect.runtime.{currentMirror => cm} - -object Test extends App { - class A { - var x: Int = 42 - } - - val a = new A - - val im: InstanceMirror = cm.reflect(a) - val cs = im.symbol - //val f = cs.typeSignature.declaration(newTermName("x" + nme.LOCAL_SUFFIX_STRING)).asTermSymbol - val f = cs.typeSignature.declaration(newTermName("x")).asTermSymbol - try { - val fm: FieldMirror = im.reflectField(f) - println("this indicates a failure") - } catch { - case ex: Throwable => - println(ex.getMessage) - } -} -- cgit v1.2.3 From 9c4b0d0402559921d4f36e9ebdd1af9a818fdde7 Mon Sep 17 00:00:00 2001 From: Dominik Gruntz Date: Tue, 17 Jul 2012 15:38:58 +0200 Subject: SI-5856 enables use of $this in string interpolation This pull request fixes SI-5856. The scanner has been modified to return the correct token if keywords appear in $-expressions. The parser has been modified to issue an error and to only accept $, $this and $block. --- .../scala/tools/nsc/ast/parser/Parsers.scala | 7 ++++- .../scala/tools/nsc/ast/parser/Scanners.scala | 6 ++++- test/files/neg/t5856.check | 31 ++++++++++++++++++++++ test/files/neg/t5856.scala | 11 ++++++++ test/files/run/t5856.scala | 10 +++++++ 5 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 test/files/neg/t5856.check create mode 100644 test/files/neg/t5856.scala create mode 100644 test/files/run/t5856.scala diff --git a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala index 3232bde3b4..656c0fa79a 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Parsers.scala @@ -1169,7 +1169,12 @@ self => if (inPattern) dropAnyBraces(pattern()) else { if (in.token == IDENTIFIER) atPos(in.offset)(Ident(ident())) - else expr() + else if(in.token == LBRACE) expr() + else if(in.token == THIS) { in.nextToken(); atPos(in.offset)(This(tpnme.EMPTY)) } + else { + syntaxErrorOrIncomplete("error in interpolated string: identifier or block expected", true) + EmptyTree + } } } } diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index 6ba273b8ea..db04aedf29 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -729,8 +729,12 @@ trait Scanners extends ScannersCommon { next.token = IDENTIFIER next.name = newTermName(cbuf.toString) cbuf.clear() + val idx = next.name.start - kwOffset + if (idx >= 0 && idx < kwArray.length) { + next.token = kwArray(idx) + } } else { - syntaxError("invalid string interpolation") + syntaxError("invalid string interpolation: $$, $ident or $block expected") } } else { val isUnclosedLiteral = !isUnicodeEscape && (ch == SU || (!multiLine && (ch == CR || ch == LF))) diff --git a/test/files/neg/t5856.check b/test/files/neg/t5856.check new file mode 100644 index 0000000000..d42bcdb524 --- /dev/null +++ b/test/files/neg/t5856.check @@ -0,0 +1,31 @@ +t5856.scala:10: error: invalid string interpolation: $$, $ident or $block expected + val s9 = s"$" + ^ +t5856.scala:10: error: unclosed string literal + val s9 = s"$" + ^ +t5856.scala:2: error: error in interpolated string: identifier or block expected + val s1 = s"$null" + ^ +t5856.scala:3: error: error in interpolated string: identifier or block expected + val s2 = s"$false" + ^ +t5856.scala:4: error: error in interpolated string: identifier or block expected + val s3 = s"$true" + ^ +t5856.scala:5: error: error in interpolated string: identifier or block expected + val s4 = s"$yield" + ^ +t5856.scala:6: error: error in interpolated string: identifier or block expected + val s5 = s"$return" + ^ +t5856.scala:7: error: error in interpolated string: identifier or block expected + val s6 = s"$new" + ^ +t5856.scala:8: error: error in interpolated string: identifier or block expected + val s7 = s"$s1 $null $super" + ^ +t5856.scala:9: error: error in interpolated string: identifier or block expected + val s8 = s"$super" + ^ +10 errors found diff --git a/test/files/neg/t5856.scala b/test/files/neg/t5856.scala new file mode 100644 index 0000000000..2ceee590af --- /dev/null +++ b/test/files/neg/t5856.scala @@ -0,0 +1,11 @@ +object Test { + val s1 = s"$null" + val s2 = s"$false" + val s3 = s"$true" + val s4 = s"$yield" + val s5 = s"$return" + val s6 = s"$new" + val s7 = s"$s1 $null $super" + val s8 = s"$super" + val s9 = s"$" +} \ No newline at end of file diff --git a/test/files/run/t5856.scala b/test/files/run/t5856.scala new file mode 100644 index 0000000000..d1e9bd6e58 --- /dev/null +++ b/test/files/run/t5856.scala @@ -0,0 +1,10 @@ +object Test extends App { + override def toString = "Test" + + assert(s"$this" == "Test") + assert(s"$this$this" == "TestTest") + assert(s"$this$$" == "Test$") + assert(s"$this.##" == "Test.##") + assert(s"$this.toString" == "Test.toString") + assert(s"$this=THIS" == "Test=THIS") +} \ No newline at end of file -- cgit v1.2.3 From d06c73c1f381c346b46ca9499fbec8677d3fab5e Mon Sep 17 00:00:00 2001 From: Dominik Gruntz Date: Tue, 17 Jul 2012 17:27:22 +0200 Subject: changes error message generated by compiler --- src/compiler/scala/tools/nsc/ast/parser/Scanners.scala | 2 +- test/files/neg/t5856.check | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala index db04aedf29..7ccd6785bb 100644 --- a/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala +++ b/src/compiler/scala/tools/nsc/ast/parser/Scanners.scala @@ -734,7 +734,7 @@ trait Scanners extends ScannersCommon { next.token = kwArray(idx) } } else { - syntaxError("invalid string interpolation: $$, $ident or $block expected") + syntaxError("invalid string interpolation: `$$', `$'ident or `$'BlockExpr expected") } } else { val isUnclosedLiteral = !isUnicodeEscape && (ch == SU || (!multiLine && (ch == CR || ch == LF))) diff --git a/test/files/neg/t5856.check b/test/files/neg/t5856.check index d42bcdb524..ac49d4b9ac 100644 --- a/test/files/neg/t5856.check +++ b/test/files/neg/t5856.check @@ -1,4 +1,4 @@ -t5856.scala:10: error: invalid string interpolation: $$, $ident or $block expected +t5856.scala:10: error: invalid string interpolation: `$$', `$'ident or `$'BlockExpr expected val s9 = s"$" ^ t5856.scala:10: error: unclosed string literal -- cgit v1.2.3 From aa6fa4623b54ed21b3246e4c98c720adbbac2473 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 12 Jul 2012 16:46:25 +0200 Subject: SI-5739 store sub-patterns in local vals Also closes SI-5158 (debuggability), SI-6070 (soundness). To improve both debuggability and soundness, we now store the result of an extractor (user-defined and synthetic) in local variables. For the case class case, this also fixes the soundness bug SI-6070, as this prevents post-match mutation of bound variables. The core of the refactoring consisted of introducing the PreserveSubPatBinders trait, which introduces local variables instead of substituting symbols for the RHS of those variables (so this can be seen as reverting the premature optimization of inline the case-class getters into the case body). Since TreeMakerToCond fuses the substitutions performed in a match to find out which symbolic values binders evaluate to, masquerade PreserveSubPatBinders's binding of subPatBinders and subPatRefs as the corresponding substitution. Consider `case class Foo(bar: Int)`, then `case y@Foo(x) => println(x)` gives rise to `{val x = y.bar; println(x)}` (instead of `println(y.bar)`), and `subPatternsAsSubstitution` pretends we still replace `x` by `y.bar`, instead of storing it in a local variable so that the rest of the analysis need not be modified. Misc notes: - correct type for seq-subpattern - more error resilience (ill-typed patterns sometimes slip past the typechecker -- reopened SI-4425) TODO: come up with a more abstract framework for expressing bound symbols and their values --- .../tools/nsc/typechecker/PatternMatching.scala | 81 +++++++++++++++++++--- test/files/neg/t4425.check | 5 +- test/files/run/inline-ex-handlers.check | 80 ++++++++++----------- test/files/run/t5158.check | 1 + test/files/run/t5158.scala | 17 +++++ test/files/run/t6070.check | 1 + test/files/run/t6070.scala | 36 ++++++++++ 7 files changed, 168 insertions(+), 53 deletions(-) create mode 100644 test/files/run/t5158.check create mode 100644 test/files/run/t5158.scala create mode 100644 test/files/run/t6070.check create mode 100644 test/files/run/t6070.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala index 4e4176e531..afe143553c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala @@ -15,6 +15,7 @@ import scala.tools.nsc.transform.Transform import scala.collection.mutable.HashSet import scala.collection.mutable.HashMap import reflect.internal.util.Statistics +import scala.reflect.internal.Types /** Translate pattern matching. * @@ -71,7 +72,15 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL case Match(sel, cases) => val origTp = tree.tpe // setType origTp intended for CPS -- TODO: is it necessary? - localTyper.typed(translator.translateMatch(treeCopy.Match(tree, transform(sel), transformTrees(cases).asInstanceOf[List[CaseDef]]))) setType origTp + val translated = translator.translateMatch(treeCopy.Match(tree, transform(sel), transformTrees(cases).asInstanceOf[List[CaseDef]])) + try { + localTyper.typed(translated) setType origTp + } catch { + case x: (Types#TypeError) => + // TODO: this should never happen; error should've been reported during type checking + unit.error(tree.pos, "error during expansion of this match (this is a scalac bug).\nThe underlying error was: "+ x.msg) + translated + } case Try(block, catches, finalizer) => treeCopy.Try(tree, transform(block), translator.translateTry(transformTrees(catches).asInstanceOf[List[CaseDef]], tree.tpe, tree.pos), transform(finalizer)) case _ => super.transform(tree) @@ -547,7 +556,10 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL if(isSeq) { val TypeRef(pre, SeqClass, args) = seqTp // do repeated-parameter expansion to match up with the expected number of arguments (in casu, subpatterns) - formalTypes(rawSubPatTypes.init :+ typeRef(pre, RepeatedParamClass, args), nbSubPats) + val formalsWithRepeated = rawSubPatTypes.init :+ typeRef(pre, RepeatedParamClass, args) + + if (lastIsStar) formalTypes(formalsWithRepeated, nbSubPats - 1) :+ seqTp + else formalTypes(formalsWithRepeated, nbSubPats) } else rawSubPatTypes protected def rawSubPatTypes: List[Type] @@ -637,7 +649,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // binder has type paramType def treeMaker(binder: Symbol, pos: Position): TreeMaker = { // checks binder ne null before chaining to the next extractor - ProductExtractorTreeMaker(binder, lengthGuard(binder))(Substitution(subPatBinders, subPatRefs(binder))) + ProductExtractorTreeMaker(binder, lengthGuard(binder))(subPatBinders, subPatRefs(binder)) } // reference the (i-1)th case accessor if it exists, otherwise the (i-1)th tuple component @@ -681,7 +693,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // the extractor call (applied to the binder bound by the flatMap corresponding to the previous (i.e., enclosing/outer) pattern) val extractorApply = atPos(pos)(spliceApply(patBinderOrCasted)) val binder = freshSym(pos, pureType(resultInMonad)) // can't simplify this when subPatBinders.isEmpty, since UnitClass.tpe is definitely wrong when isSeq, and resultInMonad should always be correct since it comes directly from the extractor's result type - ExtractorTreeMaker(extractorApply, lengthGuard(binder), binder)(Substitution(subPatBinders, subPatRefs(binder)), resultType.typeSymbol == BooleanClass, checkedLength, patBinderOrCasted) + ExtractorTreeMaker(extractorApply, lengthGuard(binder), binder)(subPatBinders, subPatRefs(binder), resultType.typeSymbol == BooleanClass, checkedLength, patBinderOrCasted) } override protected def seqTree(binder: Symbol): Tree = @@ -837,6 +849,20 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } private[this] var currSub: Substitution = null + /** The substitution that specifies the trees that compute the values of the subpattern binders. + * + * Should not be used to perform actual substitution! + * Only used to reason symbolically about the values the subpattern binders are bound to. + * See TreeMakerToCond#updateSubstitution. + * + * Overridden in PreserveSubPatBinders to pretend it replaces the subpattern binders by subpattern refs + * (Even though we don't do so anymore -- see SI-5158, SI-5739 and SI-6070.) + * + * TODO: clean this up, would be nicer to have some higher-level way to compute + * the binders bound by this tree maker and the symbolic values that correspond to them + */ + def subPatternsAsSubstitution: Substitution = substitution + // build Tree that chains `next` after the current extractor def chainBefore(next: Tree)(casegen: Casegen): Tree } @@ -885,6 +911,23 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL atPos(pos)(casegen.flatMapCond(cond, res, nextBinder, substitution(next))) } + trait PreserveSubPatBinders extends NoNewBinders { + val subPatBinders: List[Symbol] + val subPatRefs: List[Tree] + + /** The substitution that specifies the trees that compute the values of the subpattern binders. + * + * We pretend to replace the subpattern binders by subpattern refs + * (Even though we don't do so anymore -- see SI-5158, SI-5739 and SI-6070.) + */ + override def subPatternsAsSubstitution = + Substitution(subPatBinders, subPatRefs) >> super.subPatternsAsSubstitution + + import CODE._ + def bindSubPats(in: Tree): Tree = + Block((subPatBinders, subPatRefs).zipped.map { case (sym, ref) => VAL(sym) === ref }, in) + } + /** * Make a TreeMaker that will result in an extractor call specified by `extractor` * the next TreeMaker (here, we don't know which it'll be) is chained after this one by flatMap'ing @@ -892,12 +935,23 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL * the function's body is determined by the next TreeMaker * 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)(val localSubstitution: Substitution, extractorReturnsBoolean: Boolean, val checkedLength: Option[Int], val prevBinder: Symbol) extends FunTreeMaker { + case class ExtractorTreeMaker(extractor: Tree, extraCond: Option[Tree], nextBinder: Symbol)( + val subPatBinders: List[Symbol], + val subPatRefs: List[Tree], + extractorReturnsBoolean: Boolean, + val checkedLength: Option[Int], + val prevBinder: Symbol) extends FunTreeMaker with PreserveSubPatBinders { + def chainBefore(next: Tree)(casegen: Casegen): Tree = { - val condAndNext = extraCond map (casegen.ifThenElseZero(_, next)) getOrElse next + val condAndNext = extraCond match { + case Some(cond) => + casegen.ifThenElseZero(substitution(cond), bindSubPats(substitution(next))) + case _ => + bindSubPats(substitution(next)) + } atPos(extractor.pos)( - if (extractorReturnsBoolean) casegen.flatMapCond(extractor, CODE.UNIT, nextBinder, substitution(condAndNext)) - else casegen.flatMap(extractor, nextBinder, substitution(condAndNext)) + if (extractorReturnsBoolean) casegen.flatMapCond(extractor, CODE.UNIT, nextBinder, condAndNext) + else casegen.flatMap(extractor, nextBinder, condAndNext) ) } @@ -905,12 +959,17 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } // TODO: allow user-defined unapplyProduct - case class ProductExtractorTreeMaker(prevBinder: Symbol, extraCond: Option[Tree])(val localSubstitution: Substitution) extends FunTreeMaker { import CODE._ + case class ProductExtractorTreeMaker(prevBinder: Symbol, extraCond: Option[Tree])( + val subPatBinders: List[Symbol], + val subPatRefs: List[Tree]) extends FunTreeMaker with PreserveSubPatBinders { + + import CODE._ val nextBinder = prevBinder // just passing through + def chainBefore(next: Tree)(casegen: Casegen): Tree = { val nullCheck = REF(prevBinder) OBJ_NE NULL val cond = extraCond map (nullCheck AND _) getOrElse nullCheck - casegen.ifThenElseZero(cond, substitution(next)) + casegen.ifThenElseZero(cond, bindSubPats(substitution(next))) } override def toString = "P"+(prevBinder.name, extraCond getOrElse "", localSubstitution) @@ -1541,7 +1600,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL * TODO: don't ignore outer-checks */ def apply(tm: TreeMaker): Cond = { - if (!substitutionComputed) updateSubstitution(tm.substitution) + if (!substitutionComputed) updateSubstitution(tm.subPatternsAsSubstitution) tm match { case ttm@TypeTestTreeMaker(prevBinder, testedBinder, pt, _) => diff --git a/test/files/neg/t4425.check b/test/files/neg/t4425.check index 0f2fe6f2d1..a6a1a1fad4 100644 --- a/test/files/neg/t4425.check +++ b/test/files/neg/t4425.check @@ -1,4 +1,5 @@ -t4425.scala:3: error: isInstanceOf cannot test if value types are references. +t4425.scala:3: error: error during expansion of this match (this is a scalac bug). +The underlying error was: value _1 is not a member of object Foo.X 42 match { case _ X _ => () } - ^ + ^ one error found diff --git a/test/files/run/inline-ex-handlers.check b/test/files/run/inline-ex-handlers.check index 7d96c447b0..25e1b2a4dd 100644 --- a/test/files/run/inline-ex-handlers.check +++ b/test/files/run/inline-ex-handlers.check @@ -34,11 +34,11 @@ < 101 JUMP 4 < < 4: -512c517 +515c520 < blocks: [1,2,3,4,6,7,8,9,10] --- > blocks: [1,2,3,4,6,7,8,9,10,11,12,13] -541c546,551 +544c549,554 < 306 THROW(MyException) --- > ? JUMP 11 @@ -47,7 +47,7 @@ > ? LOAD_LOCAL(variable monitor4) > 305 MONITOR_EXIT > ? JUMP 12 -547c557,563 +550c560,566 < ? THROW(Throwable) --- > ? JUMP 12 @@ -57,7 +57,7 @@ > 304 MONITOR_EXIT > ? STORE_LOCAL(value t) > ? JUMP 13 -553c569,582 +556c572,585 < ? THROW(Throwable) --- > ? STORE_LOCAL(value t) @@ -74,19 +74,19 @@ > 310 CALL_PRIMITIVE(EndConcat) > 310 CALL_METHOD scala.Predef.println (dynamic) > 310 JUMP 2 -577c606 +580c609 < catch (Throwable) in ArrayBuffer(7, 8, 9, 10) starting at: 6 --- > catch (Throwable) in ArrayBuffer(7, 8, 9, 10, 11) starting at: 6 -580c609 +583c612 < catch (Throwable) in ArrayBuffer(4, 6, 7, 8, 9, 10) starting at: 3 --- > catch (Throwable) in ArrayBuffer(4, 6, 7, 8, 9, 10, 11, 12) starting at: 3 -612c641 +615c644 < blocks: [1,2,3,4,5,6,7,9,10] --- > blocks: [1,2,3,4,5,6,7,9,10,11,12] -636c665,671 +639c668,674 < 78 THROW(IllegalArgumentException) --- > ? STORE_LOCAL(value e) @@ -96,7 +96,7 @@ > 81 LOAD_LOCAL(value e) > ? STORE_LOCAL(variable exc1) > ? JUMP 12 -665c700,714 +668c703,717 < 81 THROW(Exception) --- > ? STORE_LOCAL(variable exc1) @@ -114,15 +114,15 @@ > 84 STORE_LOCAL(variable result) > 84 LOAD_LOCAL(variable exc1) > 84 THROW(Throwable) -687c736 +690c739 < catch () in ArrayBuffer(4, 6, 7, 9) starting at: 3 --- > catch () in ArrayBuffer(4, 6, 7, 9, 11) starting at: 3 -713c762 +716c765 < blocks: [1,2,3,4,5,6,9,12,14,17,18,19,22,25,27,28,30,31] --- > blocks: [1,2,3,4,5,6,9,12,14,17,18,19,22,25,27,28,30,31,32,33,34] -737c786,793 +740c789,796 < 172 THROW(MyException) --- > ? STORE_LOCAL(value ex6) @@ -133,12 +133,12 @@ > 170 STORE_LOCAL(value x4) > 170 SCOPE_ENTER value x4 > 170 JUMP 18 -793c849,850 +798c854,855 < 177 THROW(MyException) --- > ? STORE_LOCAL(value ex6) > ? JUMP 33 -797c854,861 +802c859,866 < 170 THROW(Throwable) --- > ? STORE_LOCAL(value ex6) @@ -149,17 +149,17 @@ > 169 STORE_LOCAL(value x4) > 169 SCOPE_ENTER value x4 > 169 JUMP 5 -830c894,895 +837c901,902 < 182 THROW(MyException) --- > ? STORE_LOCAL(variable exc2) > ? JUMP 34 -834c899,900 +841c906,907 < 169 THROW(Throwable) --- > ? STORE_LOCAL(variable exc2) > ? JUMP 34 -835a902,914 +842a909,921 > 34: > 184 LOAD_MODULE object Predef > 184 CONSTANT("finally") @@ -173,19 +173,19 @@ > 185 LOAD_LOCAL(variable exc2) > 185 THROW(Throwable) > -856c935 +863c942 < catch (Throwable) in ArrayBuffer(17, 18, 19, 22, 25, 27, 28, 30) starting at: 4 --- > catch (Throwable) in ArrayBuffer(17, 18, 19, 22, 25, 27, 28, 30, 32) starting at: 4 -859c938 +866c945 < catch () in ArrayBuffer(4, 5, 6, 9, 12, 17, 18, 19, 22, 25, 27, 28, 30) starting at: 3 --- > catch () in ArrayBuffer(4, 5, 6, 9, 12, 17, 18, 19, 22, 25, 27, 28, 30, 32, 33) starting at: 3 -885c964 +892c971 < blocks: [1,2,3,6,7,8,11,14,16,17,19] --- > blocks: [1,2,3,6,7,8,11,14,16,17,19,20] -909c988,995 +916c995,1002 < 124 THROW(MyException) --- > ? STORE_LOCAL(value ex6) @@ -196,15 +196,15 @@ > 122 STORE_LOCAL(value x4) > 122 SCOPE_ENTER value x4 > 122 JUMP 7 -969c1055 +979c1065 < catch (IllegalArgumentException) in ArrayBuffer(6, 7, 8, 11, 14, 16, 17, 19) starting at: 3 --- > catch (IllegalArgumentException) in ArrayBuffer(6, 7, 8, 11, 14, 16, 17, 19, 20) starting at: 3 -995c1081 +1005c1091 < blocks: [1,2,3,4,5,8,11,15,16,17,19] --- > blocks: [1,2,3,5,8,11,15,16,17,19,20] -1019c1105,1114 +1029c1115,1124 < 148 THROW(MyException) --- > ? STORE_LOCAL(value ex6) @@ -217,15 +217,15 @@ > 154 LOAD_LOCAL(value x4) > 154 IS_INSTANCE REF(class MyException) > 154 CZJUMP (BOOL)NE ? 5 : 11 -1040,1042d1134 +1050,1052d1144 < 145 JUMP 4 < < 4: -1275c1367 +1288c1380 < blocks: [1,2,3,4,5,7] --- > blocks: [1,2,3,4,5,7,8] -1299c1391,1398 +1312c1404,1411 < 38 THROW(IllegalArgumentException) --- > ? STORE_LOCAL(value e) @@ -236,16 +236,16 @@ > 42 CONSTANT("IllegalArgumentException") > 42 CALL_METHOD scala.Predef.println (dynamic) > 42 JUMP 2 -1348c1447 +1361c1460 < blocks: [1,2,3,4,5,8,11,13,14,16,17,19] --- > blocks: [1,2,3,5,8,11,13,14,16,17,19,20] -1372c1471,1472 +1385c1484,1485 < 203 THROW(MyException) --- > ? STORE_LOCAL(value ex6) > ? JUMP 20 -1392c1492,1501 +1405c1505,1514 < 209 THROW(MyException) --- > ? STORE_LOCAL(value ex6) @@ -258,15 +258,15 @@ > 212 LOAD_LOCAL(value x4) > 212 IS_INSTANCE REF(class MyException) > 212 CZJUMP (BOOL)NE ? 5 : 11 -1405,1407d1513 +1418,1420d1526 < 200 JUMP 4 < < 4: -1467c1573 +1483c1589 < blocks: [1,2,3,4,5,7] --- > blocks: [1,2,3,4,5,7,8] -1491c1597,1604 +1507c1613,1620 < 58 THROW(IllegalArgumentException) --- > ? STORE_LOCAL(value e) @@ -277,11 +277,11 @@ > 62 CONSTANT("RuntimeException") > 62 CALL_METHOD scala.Predef.println (dynamic) > 62 JUMP 2 -1540c1653 +1556c1669 < blocks: [1,2,3,4] --- > blocks: [1,2,3,4,5] -1560c1673,1678 +1576c1689,1694 < 229 THROW(MyException) --- > ? JUMP 5 @@ -290,19 +290,19 @@ > ? LOAD_LOCAL(variable monitor1) > 228 MONITOR_EXIT > 228 THROW(Throwable) -1566c1684 +1582c1700 < ? THROW(Throwable) --- > 228 THROW(Throwable) -1594c1712 +1610c1728 < locals: value args, variable result, variable monitor2, variable monitorResult1 --- > locals: value exception$1, value args, variable result, variable monitor2, variable monitorResult1 -1596c1714 +1612c1730 < blocks: [1,2,3,4] --- > blocks: [1,2,3,4,5] -1619c1737,1745 +1635c1753,1761 < 245 THROW(MyException) --- > ? STORE_LOCAL(value exception$1) @@ -314,7 +314,7 @@ > ? LOAD_LOCAL(variable monitor2) > 244 MONITOR_EXIT > 244 THROW(Throwable) -1625c1751 +1641c1767 < ? THROW(Throwable) --- > 244 THROW(Throwable) diff --git a/test/files/run/t5158.check b/test/files/run/t5158.check new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/test/files/run/t5158.check @@ -0,0 +1 @@ +0 diff --git a/test/files/run/t5158.scala b/test/files/run/t5158.scala new file mode 100644 index 0000000000..3028ffa9e0 --- /dev/null +++ b/test/files/run/t5158.scala @@ -0,0 +1,17 @@ +case class B(var x: Int) { + def succ() { + x = x + 1 + } +} + +object Test { + def main(args: Array[String]) { + val b = B(0) + b match { + case B(x) => + //println(x) + b.succ() + println(x) + } + } +} \ No newline at end of file diff --git a/test/files/run/t6070.check b/test/files/run/t6070.check new file mode 100644 index 0000000000..00750edc07 --- /dev/null +++ b/test/files/run/t6070.check @@ -0,0 +1 @@ +3 diff --git a/test/files/run/t6070.scala b/test/files/run/t6070.scala new file mode 100644 index 0000000000..b6af48ef21 --- /dev/null +++ b/test/files/run/t6070.scala @@ -0,0 +1,36 @@ +abstract class Bomb { + type T + val x: T + + def size(that: T): Int +} + +class StringBomb extends Bomb { + type T = String + val x = "abc" + def size(that: String): Int = that.length +} + +class IntBomb extends Bomb { + type T = Int + val x = 10 + + def size(that: Int) = x + that +} + +case class Mean(var bomb: Bomb) + +object Test extends App { + def foo(x: Mean) = x match { + case Mean(b) => + // BUG: b is assumed to be a stable identifier, but it can actually be mutated + println(b.size({ mutate(); b.x })) + } + + def mutate() { + m.bomb = new IntBomb + } + + val m = Mean(new StringBomb) + foo(m) // should print 3 +} \ No newline at end of file -- cgit v1.2.3 From 776105a43ef7a7a7c32be8111b496a6762a8ac68 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Fri, 13 Jul 2012 16:28:42 +0200 Subject: SI-5892 allow implicit views in annotation args the problem was that lazy annotations got completed in phase pickler. the `inferView` method in Typers bails out if `isPastTyper`. now the lazy annotations completes `atPhase(typerPhase)`. test case in `pos`. the second test case in `neg` is for another bug that is discussed in a comment of SI-5892. when type checking arguments of type parameter annotations, the class members should not be in scope. this was alreay fixed in 9129cfe9. --- .../scala/tools/nsc/typechecker/Contexts.scala | 6 ++++++ .../scala/tools/nsc/typechecker/Namers.scala | 4 ++-- test/files/neg/t5892.check | 17 +++++++++++++++ test/files/neg/t5892.scala | 25 ++++++++++++++++++++++ test/files/pos/t5892.scala | 5 +++++ 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 test/files/neg/t5892.check create mode 100644 test/files/neg/t5892.scala create mode 100644 test/files/pos/t5892.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index bcf529ecd2..805f60ba87 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -636,6 +636,12 @@ trait Contexts { self: Analyzer => collect(imp.tree.selectors) } + /* SI-5892 / SI-4270: `implicitss` can return results which are not accessible at the + * point where implicit search is triggered. Example: implicits in (annotations of) + * class type parameters (SI-5892). The `context.owner` is the class symbol, therefore + * `implicitss` will return implicit conversions defined inside the class. These are + * filtered out later by `eligibleInfos` (SI-4270 / 9129cfe9), as they don't type-check. + */ def implicitss: List[List[ImplicitInfo]] = { if (implicitsRunId != currentRunId) { implicitsRunId = currentRunId diff --git a/src/compiler/scala/tools/nsc/typechecker/Namers.scala b/src/compiler/scala/tools/nsc/typechecker/Namers.scala index 9580cd5676..fa34bce82c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Namers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Namers.scala @@ -1212,8 +1212,8 @@ trait Namers extends MethodSynthesis { if (!annotated.isInitialized) tree match { case defn: MemberDef => val ainfos = defn.mods.annotations filterNot (_ eq null) map { ann => - // need to be lazy, #1782 - AnnotationInfo lazily (typer typedAnnotation ann) + // need to be lazy, #1782. beforeTyper to allow inferView in annotation args, SI-5892. + AnnotationInfo lazily beforeTyper(typer typedAnnotation ann) } if (ainfos.nonEmpty) { annotated setAnnotations ainfos diff --git a/test/files/neg/t5892.check b/test/files/neg/t5892.check new file mode 100644 index 0000000000..839bf9de23 --- /dev/null +++ b/test/files/neg/t5892.check @@ -0,0 +1,17 @@ +t5892.scala:5: error: type mismatch; + found : Boolean(false) + required: String +class C[@annot(false) X] { + ^ +t5892.scala:9: error: not found: value b2s +class D[@annot(b2s(false)) X] { + ^ +t5892.scala:13: error: type mismatch; + found : Boolean(false) + required: String +@annot(false) class E { + ^ +t5892.scala:17: error: not found: value b2s +@annot(b2s(false)) class F { + ^ +four errors found diff --git a/test/files/neg/t5892.scala b/test/files/neg/t5892.scala new file mode 100644 index 0000000000..5e3b2f313e --- /dev/null +++ b/test/files/neg/t5892.scala @@ -0,0 +1,25 @@ +import language.implicitConversions + +class annot(a: String) extends annotation.StaticAnnotation + +class C[@annot(false) X] { + implicit def b2s(b: Boolean): String = "" +} + +class D[@annot(b2s(false)) X] { + implicit def b2s(b: Boolean): String = "" +} + +@annot(false) class E { + implicit def b2s(b: Boolean): String = "" +} + +@annot(b2s(false)) class F { + implicit def b2s(b: Boolean): String = "" +} + +object T { + implicit def b2s(b: Boolean): String = "" + @annot(false) val x = 0 + @annot(b2s(false)) val y = 0 +} diff --git a/test/files/pos/t5892.scala b/test/files/pos/t5892.scala new file mode 100644 index 0000000000..241e59860a --- /dev/null +++ b/test/files/pos/t5892.scala @@ -0,0 +1,5 @@ +class foo(a: String) extends annotation.StaticAnnotation +object o { + implicit def i2s(i: Int) = "" + @foo(1: String) def blerg { } +} -- cgit v1.2.3 From 38aaa3a24ae8081c7552eede4f805e8ff063b49d Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 17 Jul 2012 20:35:53 +0200 Subject: SI-5695 removes Context.enclosingApplication Apparently it's impossible to find out the enclosing Apply node if you're an argument being typechecked (because the arguments are typechecked separately from the enclosing Apply). This functionality is by far not a core feature of macros, so I'm removing it. --- src/compiler/scala/reflect/makro/runtime/Enclosures.scala | 1 - src/reflect/scala/reflect/makro/Enclosures.scala | 4 ---- test/pending/run/t5695.check | 2 -- test/pending/run/t5695/part_1.scala | 12 ------------ test/pending/run/t5695/part_2.scala | 8 -------- 5 files changed, 27 deletions(-) delete mode 100644 test/pending/run/t5695.check delete mode 100644 test/pending/run/t5695/part_1.scala delete mode 100644 test/pending/run/t5695/part_2.scala diff --git a/src/compiler/scala/reflect/makro/runtime/Enclosures.scala b/src/compiler/scala/reflect/makro/runtime/Enclosures.scala index 80c35d22ff..360a4b8e8a 100644 --- a/src/compiler/scala/reflect/makro/runtime/Enclosures.scala +++ b/src/compiler/scala/reflect/makro/runtime/Enclosures.scala @@ -14,7 +14,6 @@ trait Enclosures { // vals are eager to simplify debugging // after all we wouldn't save that much time by making them lazy val macroApplication: Tree = expandee - val enclosingApplication: Tree = enclTrees collectFirst { case t: Apply => t } getOrElse EmptyTree val enclosingClass: Tree = site.enclClass.tree val enclosingImplicits: List[(Type, Tree)] = site.openImplicits val enclosingMacros: List[Context] = this :: universe.analyzer.openMacros // include self diff --git a/src/reflect/scala/reflect/makro/Enclosures.scala b/src/reflect/scala/reflect/makro/Enclosures.scala index 69bd8d09c7..ff5c13a785 100644 --- a/src/reflect/scala/reflect/makro/Enclosures.scala +++ b/src/reflect/scala/reflect/makro/Enclosures.scala @@ -36,10 +36,6 @@ trait Enclosures { */ val enclosingPosition: Position - /** Tree that corresponds to the enclosing application, or EmptyTree if not applicable. - */ - val enclosingApplication: Tree - /** Tree that corresponds to the enclosing method, or EmptyTree if not applicable. */ val enclosingMethod: Tree diff --git a/test/pending/run/t5695.check b/test/pending/run/t5695.check deleted file mode 100644 index d50069ab4f..0000000000 --- a/test/pending/run/t5695.check +++ /dev/null @@ -1,2 +0,0 @@ -.. -.. diff --git a/test/pending/run/t5695/part_1.scala b/test/pending/run/t5695/part_1.scala deleted file mode 100644 index b8e8f8e52f..0000000000 --- a/test/pending/run/t5695/part_1.scala +++ /dev/null @@ -1,12 +0,0 @@ -import language.experimental.macros -import scala.reflect.makro.Context - -object Defs { - - def mkInt = macro mkIntImpl - def mkIntImpl(c: Context): c.Expr[Any] = { - println(c.enclosingApplication) - c.reify{ 23 } - } - -} diff --git a/test/pending/run/t5695/part_2.scala b/test/pending/run/t5695/part_2.scala deleted file mode 100644 index d34219437d..0000000000 --- a/test/pending/run/t5695/part_2.scala +++ /dev/null @@ -1,8 +0,0 @@ -import Defs._ - -object Test extends App { - - val i1 = mkInt - val i2 = identity(mkInt) - -} -- cgit v1.2.3 From fa1dc5afea96d5917bc8a4883e3ee2d23db4744e Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Tue, 17 Jul 2012 18:55:35 +0200 Subject: SI-6089 better tail position analysis for matches we mistakenly went into apply nodes to look for matchEnd-labeldefs in tail positions -- only apply nodes that jump to labels in tailpos should be traversed (this arises for nested matches) --- src/compiler/scala/tools/nsc/transform/TailCalls.scala | 16 +++++++++++----- test/files/run/t6089.check | 1 + test/files/run/t6089.scala | 13 +++++++++++++ 3 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 test/files/run/t6089.check create mode 100644 test/files/run/t6089.scala diff --git a/src/compiler/scala/tools/nsc/transform/TailCalls.scala b/src/compiler/scala/tools/nsc/transform/TailCalls.scala index 4c9d855413..d5bbc578fc 100644 --- a/src/compiler/scala/tools/nsc/transform/TailCalls.scala +++ b/src/compiler/scala/tools/nsc/transform/TailCalls.scala @@ -373,7 +373,7 @@ abstract class TailCalls extends Transform { // 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]() + val tailLabels = new collection.mutable.HashSet[Symbol]() private var maybeTail: Boolean = true // since we start in the rhs of a DefDef @@ -388,9 +388,15 @@ abstract class TailCalls extends Transform { 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)` + // we're looking for label(x){x} in tail position, since that means `a` is in tail position in a call `label(a)` + case LabelDef(_, List(arg), body@Ident(_)) if arg.symbol == body.symbol => if (maybeTail) tailLabels += tree.symbol + // jumps to matchEnd are transparent; need this case for nested matches + // (and the translated match case below does things in reverse for this case's sake) + case Apply(fun, arg :: Nil) if hasSynthCaseSymbol(fun) && tailLabels(fun.symbol) => + traverse(arg) + // a translated casedef case LabelDef(_, _, body) if hasSynthCaseSymbol(tree) => traverse(body) @@ -400,9 +406,9 @@ abstract class TailCalls extends Transform { // 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) + traverseTrees(cases.reverse) // reverse so that we enter the matchEnd LabelDef before we see jumps to it + traverseTreesNoTail(prologue) // selector (may be absent) case CaseDef(pat, guard, body) => traverse(body) @@ -426,7 +432,7 @@ abstract class TailCalls extends Transform { traverseTreesNoTail(catches) traverseNoTail(finalizer) - case EmptyTree | Super(_, _) | This(_) | Select(_, _) | Ident(_) | Literal(_) | Function(_, _) | TypeTree() => + case Apply(_, _) | EmptyTree | Super(_, _) | This(_) | Select(_, _) | Ident(_) | Literal(_) | Function(_, _) | TypeTree() => case _ => super.traverse(tree) } } diff --git a/test/files/run/t6089.check b/test/files/run/t6089.check new file mode 100644 index 0000000000..a8d4424106 --- /dev/null +++ b/test/files/run/t6089.check @@ -0,0 +1 @@ +scala.MatchError: Foo(0) (of class Foo) diff --git a/test/files/run/t6089.scala b/test/files/run/t6089.scala new file mode 100644 index 0000000000..c72d7ba792 --- /dev/null +++ b/test/files/run/t6089.scala @@ -0,0 +1,13 @@ +case class Foo(x: Int) + +object Test { + def bippo(result: Boolean): Boolean = result + def bungus(m: Foo): Boolean = + bippo(m match { case Foo(2) => bungus(m) }) + + def main(args: Array[String]): Unit = try { + bungus(Foo(0)) + } catch { + case x: MatchError => println(x) + } +} \ No newline at end of file -- cgit v1.2.3 From d8fbd9a54d7c7e923df4978d6368951d55360e3e Mon Sep 17 00:00:00 2001 From: Aleksandar Prokopec Date: Wed, 18 Jul 2012 11:41:03 +0200 Subject: Fix SI-5937. --- .../scala/collection/immutable/Vector.scala | 26 +++++++++++++--------- test/files/run/t5937.scala | 12 ++++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 test/files/run/t5937.scala diff --git a/src/library/scala/collection/immutable/Vector.scala b/src/library/scala/collection/immutable/Vector.scala index d100bf93df..1a0ef5eee7 100644 --- a/src/library/scala/collection/immutable/Vector.scala +++ b/src/library/scala/collection/immutable/Vector.scala @@ -18,8 +18,14 @@ import scala.collection.parallel.immutable.ParVector /** Companion object to the Vector class */ object Vector extends SeqFactory[Vector] { + private[collection] class VectorReusableCBF extends GenericCanBuildFrom[Nothing] { + override def apply() = newBuilder[Nothing] + } + + lazy val VectorReusableCBF: GenericCanBuildFrom[Nothing] = new VectorReusableCBF + @inline implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Vector[A]] = - ReusableCBF.asInstanceOf[CanBuildFrom[Coll, A, Vector[A]]] + VectorReusableCBF.asInstanceOf[CanBuildFrom[Coll, A, Vector[A]]] def newBuilder[A]: Builder[A, Vector[A]] = new VectorBuilder[A] private[immutable] val NIL = new Vector[Nothing](0, 0, 0) @inline override def empty[A]: Vector[A] = NIL @@ -140,19 +146,19 @@ override def companion: GenericCompanion[Vector] = Vector // SeqLike api - @inline override def updated[B >: A, That](index: Int, elem: B)(implicit bf: CanBuildFrom[Vector[A], B, That]): That = { - // just ignore bf - updateAt(index, elem).asInstanceOf[That] + @inline override def updated[B >: A, That](index: Int, elem: B)(implicit bf: CanBuildFrom[Vector[A], B, That]): That = bf match { + case _: Vector.VectorReusableCBF => updateAt(index, elem).asInstanceOf[That] // just ignore bf + case _ => super.updated(index, elem)(bf) } - @inline override def +:[B >: A, That](elem: B)(implicit bf: CanBuildFrom[Vector[A], B, That]): That = { - // just ignore bf - appendFront(elem).asInstanceOf[That] + @inline override def +:[B >: A, That](elem: B)(implicit bf: CanBuildFrom[Vector[A], B, That]): That = bf match { + case _: Vector.VectorReusableCBF => appendFront(elem).asInstanceOf[That] // just ignore bf + case _ => super.+:(elem)(bf) } - @inline override def :+[B >: A, That](elem: B)(implicit bf: CanBuildFrom[Vector[A], B, That]): That = { - // just ignore bf - appendBack(elem).asInstanceOf[That] + @inline override def :+[B >: A, That](elem: B)(implicit bf: CanBuildFrom[Vector[A], B, That]): That = bf match { + case _: Vector.VectorReusableCBF => appendBack(elem).asInstanceOf[That] // just ignore bf + case _ => super.:+(elem)(bf) } override def take(n: Int): Vector[A] = { diff --git a/test/files/run/t5937.scala b/test/files/run/t5937.scala new file mode 100644 index 0000000000..e5bf6617af --- /dev/null +++ b/test/files/run/t5937.scala @@ -0,0 +1,12 @@ + + + +import collection._ + + + +object Test extends App { + + val list: List[Int] = (immutable.Vector(1, 2, 3) :+ 4)(breakOut) + +} -- cgit v1.2.3 From 01be1b1c201d6908522d7254075fd1cdf633809a Mon Sep 17 00:00:00 2001 From: Iulian Dragos Date: Tue, 17 Jul 2012 10:50:59 +0200 Subject: Fixed SI-6092. Fixed leaky annotations, and relaxed the conditions under which a try-catch is lifted out to an inner method. Less known fact: lazy values null-out their dependent values is their accessed only from their initializer. The analysis is not context-dependent (meaning the owner where a reference happens needs to be exactly that lazy value). * Removed no-op code around positions in `LazyAnnotationInfo` * Don't lift expressions that have no `catch` clause The two changes combined fix a memory leak that's been plaguing the IDE: an annotation (even when forced) would hang on to a namer, through the outer field of its call-by-name parameter. The test for the memory leak is in the IDE project (couldn't find a simple way to reproduce it outside the IDE), but there's a test checking that the field is null after initialization. --- .../scala/tools/nsc/transform/UnCurry.scala | 15 +++++++++ .../scala/reflect/internal/AnnotationInfos.scala | 7 +---- test/files/run/nullable-lazyvals.check | 3 ++ test/files/run/nullable-lazyvals.scala | 36 ++++++++++++++++++++++ 4 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 test/files/run/nullable-lazyvals.check create mode 100644 test/files/run/nullable-lazyvals.scala diff --git a/src/compiler/scala/tools/nsc/transform/UnCurry.scala b/src/compiler/scala/tools/nsc/transform/UnCurry.scala index de0650b2ea..efc3d25ed0 100644 --- a/src/compiler/scala/tools/nsc/transform/UnCurry.scala +++ b/src/compiler/scala/tools/nsc/transform/UnCurry.scala @@ -33,6 +33,14 @@ import language.postfixOps * - convert implicit method types to method types * - convert non-trivial catches in try statements to matches * - convert non-local returns to throws with enclosing try statements. + * - convert try-catch expressions in contexts where there might be values on the stack to + * a local method and a call to it (since an exception empties the evaluation stack): + * + * meth(x_1,..., try { x_i } catch { ..}, .. x_b0) ==> + * { + * def liftedTry$1 = try { x_i } catch { .. } + * meth(x_1, .., liftedTry$1(), .. ) + * } */ /* */ abstract class UnCurry extends InfoTransform @@ -634,6 +642,13 @@ abstract class UnCurry extends InfoTransform case ret @ Return(_) if (isNonLocalReturn(ret)) => withNeedLift(true) { super.transform(ret) } + case Try(_, Nil, _) => + // try-finally does not need lifting: lifting is needed only for try-catch + // expressions that are evaluated in a context where the stack might not be empty. + // `finally` does not attempt to continue evaluation after an exception, so the fact + // that values on the stack are 'lost' does not matter + super.transform(tree) + case Try(block, catches, finalizer) => if (needTryLift || shouldBeLiftedAnyway(tree)) transform(liftTree(tree)) else super.transform(tree) diff --git a/src/reflect/scala/reflect/internal/AnnotationInfos.scala b/src/reflect/scala/reflect/internal/AnnotationInfos.scala index c283ae408e..2bcc95b9a8 100644 --- a/src/reflect/scala/reflect/internal/AnnotationInfos.scala +++ b/src/reflect/scala/reflect/internal/AnnotationInfos.scala @@ -160,12 +160,7 @@ trait AnnotationInfos extends api.AnnotationInfos { self: SymbolTable => */ final class LazyAnnotationInfo(lazyInfo: => AnnotationInfo) extends AnnotationInfo { private var forced = false - private lazy val forcedInfo = - try { - val result = lazyInfo - if (result.pos == NoPosition) result setPos pos - result - } finally forced = true + private lazy val forcedInfo = try lazyInfo finally forced = true def atp: Type = forcedInfo.atp def args: List[Tree] = forcedInfo.args diff --git a/test/files/run/nullable-lazyvals.check b/test/files/run/nullable-lazyvals.check new file mode 100644 index 0000000000..4db5783257 --- /dev/null +++ b/test/files/run/nullable-lazyvals.check @@ -0,0 +1,3 @@ + +param1: null +param2: null diff --git a/test/files/run/nullable-lazyvals.scala b/test/files/run/nullable-lazyvals.scala new file mode 100644 index 0000000000..c201e74e75 --- /dev/null +++ b/test/files/run/nullable-lazyvals.scala @@ -0,0 +1,36 @@ + +/** Test that call-by-name parameters are set to null if + * they are used only to initialize a lazy value, after the + * value has been initialized. + */ + +class Foo(param1: => Object, param2: => String) { + lazy val field1 = param1 + lazy val field2 = try param2 finally println("") +} + +object Test extends App { + val foo = new Foo(new Object, "abc") + + foo.field1 + foo.field2 + + for (f <- foo.getClass.getDeclaredFields) { + f.setAccessible(true) + if (f.getName.startsWith("param")) { + println("%s: %s".format(f.getName, f.get(foo))) + } + } + + // test that try-finally does not generated a liftedTry + // helper. This would already fail the first part of the test, + // but this check will help diganose it (if the single access to a + // private field does not happen directly in the lazy val, it won't + // be nulled). + for (f <- foo.getClass.getDeclaredMethods) { + f.setAccessible(true) + if (f.getName.startsWith("lifted")) { + println("not expected: %s".format(f)) + } + } +} -- cgit v1.2.3 From 892ee3df93a10ffe24fb11b37ad7c3a9cb93d5de Mon Sep 17 00:00:00 2001 From: Aleksandar Prokopec Date: Tue, 10 Jul 2012 20:54:23 +0200 Subject: Implement @static annotation on singleton object fields. This commit introduces the `@static` annotation on fields of static singleton objects. A static singleton object is a top-level singleton object or an object nested within a static singleton object. The field annotated with `@static` is generated as a true static field in the companion class of the object. The initialization of the field is done in the static initializer of the companion class, instead of the object itself. Here's an example. This: object Foo { @static val FIELD = 123 } class Foo generates : object Foo { def FIELD(): Int = Foo.FIELD } class Foo { val FIELD = 123 } The accessor in `object Foo` is changed to return the static field in `class Foo` (the companion class is generated if it is not already present, and the same is done for getters if `FIELD` is a `var`). Furthermore, all the callsites to accessor `Foo.FIELD` are rewritten to access the static field directly. It is illegal to annotate a field in the singleton object as `@static` if there is already a member of the same name in `class Foo`. This allows better Java interoperability with frameworks which rely on static fields being present in the class. The `AtomicReferenceFieldUpdater`s are one such example. Instead of having a Java base class holding the `volatile` mutable field `f` and a static field containing a reference to the `AtomicReferenceFieldUpdater` that mutates `f` atomically, and extending this Java base class from Scala, we can now simply do: object Foo { @static val arfu = AtomicReferenceUpdater.newUpdater( classOf[Foo], classOf[String], "f" ) } class Foo { @volatile var f = null import Foo._ def CAS(ov: String, nv: String) = arfu.compareAndSet(this, null, "") } In summary, this commit introduces the following: - adds the `@static` annotation - for objects without a companion class and `@static` members, creates a companion class (this is done in `CleanUp`) - checks for conflicting names and if `@static` is used on static singleton object member - creates the `static` field in the companion class for `@static` members - moves the field initialization from the companion object to the static initializer of the class (the initialization of `@static` members is bypassed in the `Constructors` phase, and added to static ctors in `CleanUp`) - all callsites to the accessors of `@static` are rewritten to access the static fields directly (this is done in `GenICode`) - getters and setters to `@static` fields access the static field directly, and the field in the singleton object is not emitted (this is done in `GenICode`) - changes in `GenJVM`, `GenASM` - now computing local var indices in static initializers as well - this was an oversight before, now is necessary Future work: allow the `@static` annotation on methods as well - this will be added in a future commit. Review by @odersky, @dragos, @paulp, @heathermiller. --- .../scala/tools/nsc/backend/icode/GenICode.scala | 83 +++++++-- .../scala/tools/nsc/backend/jvm/GenASM.scala | 5 +- .../scala/tools/nsc/backend/jvm/GenJVM.scala | 12 +- .../scala/tools/nsc/transform/CleanUp.scala | 138 +++++++++++++- .../scala/tools/nsc/transform/Constructors.scala | 7 +- src/library/scala/annotation/static.scala | 20 ++ .../scala/reflect/internal/Definitions.scala | 1 + src/reflect/scala/reflect/internal/StdNames.scala | 1 + src/reflect/scala/reflect/internal/Symbols.scala | 1 + test/files/neg/static-annot.check | 10 + test/files/neg/static-annot.scala | 33 ++++ test/files/run/static-annot/field.scala | 205 +++++++++++++++++++++ 12 files changed, 479 insertions(+), 37 deletions(-) create mode 100644 src/library/scala/annotation/static.scala create mode 100644 test/files/neg/static-annot.check create mode 100644 test/files/neg/static-annot.scala create mode 100644 test/files/run/static-annot/field.scala diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala index b638745327..9ec212b084 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala @@ -113,26 +113,42 @@ abstract class GenICode extends SubComponent { m.native = m.symbol.hasAnnotation(definitions.NativeAttr) if (!m.isAbstractMethod && !m.native) { - ctx1 = genLoad(rhs, ctx1, m.returnType); - - // reverse the order of the local variables, to match the source-order - m.locals = m.locals.reverse - - rhs match { - case Block(_, Return(_)) => () - case Return(_) => () - case EmptyTree => - globalError("Concrete method has no definition: " + tree + ( - if (settings.debug.value) "(found: " + m.symbol.owner.info.decls.toList.mkString(", ") + ")" - else "") - ) - case _ => if (ctx1.bb.isEmpty) - ctx1.bb.closeWith(RETURN(m.returnType), rhs.pos) - else + if (m.symbol.isAccessor && m.symbol.accessed.hasStaticAnnotation) { + // in companion object accessors to @static fields, we access the static field directly + val hostClass = m.symbol.owner.companionClass + val staticfield = hostClass.info.decls.find(_.name.toString.trim == m.symbol.accessed.name.toString.trim) + + if (m.symbol.isGetter) { + ctx1.bb.emit(LOAD_FIELD(staticfield.get, true) setHostClass hostClass, tree.pos) ctx1.bb.closeWith(RETURN(m.returnType)) + } else if (m.symbol.isSetter) { + ctx1.bb.emit(LOAD_LOCAL(m.locals.head), tree.pos) + ctx1.bb.emit(STORE_FIELD(staticfield.get, true), tree.pos) + ctx1.bb.closeWith(RETURN(m.returnType)) + } else assert(false, "unreachable") + } else { + ctx1 = genLoad(rhs, ctx1, m.returnType); + + // reverse the order of the local variables, to match the source-order + m.locals = m.locals.reverse + + rhs match { + case Block(_, Return(_)) => () + case Return(_) => () + case EmptyTree => + globalError("Concrete method has no definition: " + tree + ( + if (settings.debug.value) "(found: " + m.symbol.owner.info.decls.toList.mkString(", ") + ")" + else "") + ) + case _ => + if (ctx1.bb.isEmpty) + ctx1.bb.closeWith(RETURN(m.returnType), rhs.pos) + else + ctx1.bb.closeWith(RETURN(m.returnType)) + } + if (!ctx1.bb.closed) ctx1.bb.close + prune(ctx1.method) } - if (!ctx1.bb.closed) ctx1.bb.close - prune(ctx1.method) } else ctx1.method.setCode(NoCode) ctx1 @@ -854,9 +870,32 @@ abstract class GenICode extends SubComponent { generatedType = toTypeKind(fun.symbol.tpe.resultType) ctx1 + case app @ Apply(fun @ Select(qual, _), args) + if !ctx.method.symbol.isStaticConstructor + && fun.symbol.isAccessor && fun.symbol.accessed.hasStaticAnnotation => + // bypass the accessor to the companion object and load the static field directly + // the only place were this bypass is not done, is the static intializer for the static field itself + val sym = fun.symbol + generatedType = toTypeKind(sym.accessed.info) + val hostClass = qual.tpe.typeSymbol.orElse(sym.owner).companionClass + val staticfield = hostClass.info.decls.find(_.name.toString.trim == sym.accessed.name.toString.trim) + + if (sym.isGetter) { + ctx.bb.emit(LOAD_FIELD(staticfield.get, true) setHostClass hostClass, tree.pos) + ctx + } else if (sym.isSetter) { + val ctx1 = genLoadArguments(args, sym.info.paramTypes, ctx) + ctx1.bb.emit(STORE_FIELD(staticfield.get, true), tree.pos) + ctx1.bb.emit(CONSTANT(Constant(false)), tree.pos) + ctx1 + } else { + assert(false, "supposedly unreachable") + ctx + } + case app @ Apply(fun, args) => val sym = fun.symbol - + if (sym.isLabel) { // jump to a label val label = ctx.labels.getOrElse(sym, { // it is a forward jump, scan for labels @@ -1623,8 +1662,12 @@ abstract class GenICode extends SubComponent { * backend emits them as static). * No code is needed for this module symbol. */ - for (f <- cls.info.decls ; if !f.isMethod && f.isTerm && !f.isModule) + for ( + f <- cls.info.decls; + if !f.isMethod && f.isTerm && !f.isModule && !(f.owner.isModuleClass && f.hasStaticAnnotation) + ) { ctx.clazz addField new IField(f) + } } /** diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 756d90bc53..ade99267f8 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -1170,7 +1170,9 @@ abstract class GenASM extends SubComponent with BytecodeWriters { log("No forwarder for " + m + " due to conflict with " + linkedClass.info.member(m.name)) else { log("Adding static forwarder for '%s' from %s to '%s'".format(m, jclassName, moduleClass)) - addForwarder(isRemoteClass, jclass, moduleClass, m) + if (m.isAccessor && m.accessed.hasStaticAnnotation) { + log("@static: accessor " + m + ", accessed: " + m.accessed) + } else addForwarder(isRemoteClass, jclass, moduleClass, m) } } } @@ -1675,6 +1677,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { jmethod = clinitMethod jMethodName = CLASS_CONSTRUCTOR_NAME jmethod.visitCode() + computeLocalVarsIndex(m) genCode(m, false, true) jmethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments jmethod.visitEnd() diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala index 912a5b0e90..0ae2adac84 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenJVM.scala @@ -1021,6 +1021,8 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with method = m jmethod = clinitMethod + + computeLocalVarsIndex(m) genCode(m) case None => legacyStaticInitializer(cls, clinit) @@ -1114,7 +1116,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with linkedClass.info.members collect { case sym if sym.name.isTermName => sym.name } toSet } debuglog("Potentially conflicting names for forwarders: " + conflictingNames) - + for (m <- moduleClass.info.membersBasedOnFlags(ExcludedForwarderFlags, Flags.METHOD)) { if (m.isType || m.isDeferred || (m.owner eq ObjectClass) || m.isConstructor) debuglog("No forwarder for '%s' from %s to '%s'".format(m, className, moduleClass)) @@ -1122,7 +1124,9 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with log("No forwarder for " + m + " due to conflict with " + linkedClass.info.member(m.name)) else { log("Adding static forwarder for '%s' from %s to '%s'".format(m, className, moduleClass)) - addForwarder(jclass, moduleClass, m) + if (m.isAccessor && m.accessed.hasStaticAnnotation) { + log("@static: accessor " + m + ", accessed: " + m.accessed) + } else addForwarder(jclass, moduleClass, m) } } } @@ -1304,7 +1308,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with jclass.getType()) } } - + style match { case Static(true) => dbg("invokespecial"); jcode.emitINVOKESPECIAL(jowner, jname, jtype) case Static(false) => dbg("invokestatic"); jcode.emitINVOKESTATIC(jowner, jname, jtype) @@ -1885,7 +1889,7 @@ abstract class GenJVM extends SubComponent with GenJVMUtil with GenAndroid with */ def computeLocalVarsIndex(m: IMethod) { var idx = if (m.symbol.isStaticMember) 0 else 1; - + for (l <- m.params) { debuglog("Index value for " + l + "{" + l.## + "}: " + idx) l.index = idx diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala index bbdf10a021..1d8d58ccf7 100644 --- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala +++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala @@ -23,9 +23,12 @@ abstract class CleanUp extends Transform with ast.TreeDSL { new CleanUpTransformer(unit) class CleanUpTransformer(unit: CompilationUnit) extends Transformer { - private val newStaticMembers = mutable.Buffer.empty[Tree] - private val newStaticInits = mutable.Buffer.empty[Tree] - private val symbolsStoredAsStatic = mutable.Map.empty[String, Symbol] + private val newStaticMembers = mutable.Buffer.empty[Tree] + private val newStaticInits = mutable.Buffer.empty[Tree] + private val symbolsStoredAsStatic = mutable.Map.empty[String, Symbol] + private val staticBodies = mutable.Map.empty[(Symbol, Symbol), Tree] + private val syntheticClasses = mutable.Map.empty[Symbol, mutable.Set[Tree]] // package and trees + private val classNames = mutable.Map.empty[Symbol, Set[Name]] private def clearStatics() { newStaticMembers.clear() newStaticInits.clear() @@ -45,15 +48,16 @@ abstract class CleanUp extends Transform with ast.TreeDSL { result } private def transformTemplate(tree: Tree) = { - val Template(parents, self, body) = tree + val t @ Template(parents, self, body) = tree clearStatics() + val newBody = transformTrees(body) val templ = deriveTemplate(tree)(_ => transformTrees(newStaticMembers.toList) ::: newBody) try addStaticInits(templ) // postprocess to include static ctors finally clearStatics() } private def mkTerm(prefix: String): TermName = unit.freshTermName(prefix) - + /** Kludge to provide a safe fix for #4560: * If we generate a reference in an implementation class, we * watch out for embedded This(..) nodes that point to the interface. @@ -555,7 +559,62 @@ abstract class CleanUp extends Transform with ast.TreeDSL { else tree } - + + case ValDef(mods, name, tpt, rhs) if tree.symbol.hasStaticAnnotation => + log("moving @static valdef field: " + name + ", in: " + tree.symbol.owner) + val sym = tree.symbol + val owner = sym.owner + + val staticBeforeLifting = atPhase(currentRun.erasurePhase) { owner.isStatic } + if (!owner.isModuleClass || !staticBeforeLifting) { + if (!sym.isSynthetic) { + reporter.error(tree.pos, "Only members of top-level objects and their nested objects can be annotated with @static.") + tree.symbol.removeAnnotation(StaticClass) + } + super.transform(tree) + } else if (owner.isModuleClass) { + val linkedClass = owner.companionClass match { + case NoSymbol => + // create the companion class if it does not exist + val enclosing = owner.owner + val compclass = enclosing.newClass(newTypeName(owner.name.toString)) + compclass setInfo ClassInfoType(List(ObjectClass.tpe), newScope, compclass) + enclosing.info.decls enter compclass + + val compclstree = ClassDef(compclass, NoMods, List(List()), List(List()), List(), tree.pos) + + syntheticClasses.getOrElseUpdate(enclosing, mutable.Set()) += compclstree + + compclass + case comp => comp + } + + // create a static field in the companion class for this @static field + val stfieldSym = linkedClass.newVariable(newTermName(name), tree.pos, STATIC | SYNTHETIC | FINAL) setInfo sym.tpe + stfieldSym.addAnnotation(StaticClass) + + val names = classNames.getOrElseUpdate(linkedClass, linkedClass.info.decls.collect { + case sym if sym.name.isTermName => sym.name + } toSet) + if (names(stfieldSym.name)) { + reporter.error( + tree.pos, + "@static annotated field " + tree.symbol.name + " has the same name as a member of class " + linkedClass.name + ) + } else { + linkedClass.info.decls enter stfieldSym + + val initializerBody = rhs + + // static field was previously initialized in the companion object itself, like this: + // staticBodies((linkedClass, stfieldSym)) = Select(This(owner), sym.getter(owner)) + // instead, we move the initializer to the static ctor of the companion class + // we save the entire ValDef/DefDef to extract the rhs later + staticBodies((linkedClass, stfieldSym)) = tree + } + } + super.transform(tree) + /* MSIL requires that the stack is empty at the end of a try-block. * Hence, we here rewrite all try blocks with a result != {Unit, All} such that they * store their result in a local variable. The catch blocks are adjusted as well. @@ -665,6 +724,11 @@ abstract class CleanUp extends Transform with ast.TreeDSL { if (newStaticInits.isEmpty) template else { + val ctorBody = newStaticInits.toList flatMap { + case Block(stats, expr) => stats :+ expr + case t => List(t) + } + val newCtor = findStaticCtor(template) match { // in case there already were static ctors - augment existing ones // currently, however, static ctors aren't being generated anywhere else @@ -673,22 +737,76 @@ abstract class CleanUp extends Transform with ast.TreeDSL { deriveDefDef(ctor) { case block @ Block(stats, expr) => // need to add inits to existing block - treeCopy.Block(block, newStaticInits.toList ::: stats, expr) + treeCopy.Block(block, ctorBody ::: stats, expr) case term: TermTree => // need to create a new block with inits and the old term - treeCopy.Block(term, newStaticInits.toList, term) + treeCopy.Block(term, ctorBody, term) } case _ => // create new static ctor val staticCtorSym = currentClass.newStaticConstructor(template.pos) - val rhs = Block(newStaticInits.toList, Literal(Constant(()))) + val rhs = Block(ctorBody, Literal(Constant(()))) localTyper.typedPos(template.pos)(DefDef(staticCtorSym, rhs)) } deriveTemplate(template)(newCtor :: _) } } - + + private def addStaticDeclarations(tree: Template, clazz: Symbol) { + // add static field initializer statements for each static field in clazz + if (!clazz.isModuleClass) for { + staticSym <- clazz.info.decls + if staticSym.hasStaticAnnotation + } staticSym match { + case stfieldSym if stfieldSym.isVariable => + val valdef = staticBodies((clazz, stfieldSym)) + val ValDef(_, _, _, rhs) = valdef + val fixedrhs = rhs.changeOwner((valdef.symbol, clazz.info.decl(nme.CONSTRUCTOR))) + + val stfieldDef = localTyper.typedPos(tree.pos)(VAL(stfieldSym) === EmptyTree) + val flattenedInit = fixedrhs match { + case Block(stats, expr) => Block(stats, safeREF(stfieldSym) === expr) + case rhs => safeREF(stfieldSym) === rhs + } + val stfieldInit = localTyper.typedPos(tree.pos)(flattenedInit) + + // add field definition to new defs + newStaticMembers append stfieldDef + newStaticInits append stfieldInit + } + } + + + + override def transformStats(stats: List[Tree], exprOwner: Symbol): List[Tree] = { + super.transformStats(stats, exprOwner) ++ { + // flush pending synthetic classes created in this owner + val synthclassdefs = syntheticClasses.get(exprOwner).toList.flatten + syntheticClasses -= exprOwner + synthclassdefs map { + cdef => localTyper.typedPos(cdef.pos)(cdef) + } + } map { + case clsdef @ ClassDef(mods, name, tparams, t @ Template(parent, self, body)) => + // process all classes in the package again to add static initializers + clearStatics() + + addStaticDeclarations(t, clsdef.symbol) + + val templ = deriveTemplate(t)(_ => transformTrees(newStaticMembers.toList) ::: body) + val ntempl = + try addStaticInits(templ) + finally clearStatics() + + val derived = deriveClassDef(clsdef)(_ => ntempl) + classNames.remove(clsdef.symbol) + derived + + case stat => stat + } + } + } // CleanUpTransformer } diff --git a/src/compiler/scala/tools/nsc/transform/Constructors.scala b/src/compiler/scala/tools/nsc/transform/Constructors.scala index e5119eac71..70bd0bd21b 100644 --- a/src/compiler/scala/tools/nsc/transform/Constructors.scala +++ b/src/compiler/scala/tools/nsc/transform/Constructors.scala @@ -186,12 +186,15 @@ abstract class Constructors extends Transform with ast.TreeDSL { // before the superclass constructor call, otherwise it goes after. // Lazy vals don't get the assignment in the constructor. if (!stat.symbol.tpe.isInstanceOf[ConstantType]) { - if (rhs != EmptyTree && !stat.symbol.isLazy) { + if (stat.symbol.hasStaticAnnotation) { + debuglog("@static annotated field initialization skipped.") + defBuf += deriveValDef(stat)(tree => tree) + } else if (rhs != EmptyTree && !stat.symbol.isLazy) { val rhs1 = intoConstructor(stat.symbol, rhs); (if (canBeMoved(stat)) constrPrefixBuf else constrStatBuf) += mkAssign( stat.symbol, rhs1) + defBuf += deriveValDef(stat)(_ => EmptyTree) } - defBuf += deriveValDef(stat)(_ => EmptyTree) } case ClassDef(_, _, _, _) => // classes are treated recursively, and left in the template diff --git a/src/library/scala/annotation/static.scala b/src/library/scala/annotation/static.scala new file mode 100644 index 0000000000..f2955c756c --- /dev/null +++ b/src/library/scala/annotation/static.scala @@ -0,0 +1,20 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2011, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +package scala.annotation + +/** + * An annotation that marks a member in the companion object as static + * and ensures that the compiler generates static fields/methods for it. + * This is important for Java interoperability and performance reasons. + * + * @since 2.10 + */ +final class static extends StaticAnnotation { + // TODO document exact semantics above! +} diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index cd243b9df0..f5172eb9aa 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -899,6 +899,7 @@ trait Definitions extends api.StandardDefinitions { lazy val SwitchClass = requiredClass[scala.annotation.switch] lazy val TailrecClass = requiredClass[scala.annotation.tailrec] lazy val VarargsClass = requiredClass[scala.annotation.varargs] + lazy val StaticClass = requiredClass[scala.annotation.static] lazy val uncheckedStableClass = requiredClass[scala.annotation.unchecked.uncheckedStable] lazy val uncheckedVarianceClass = requiredClass[scala.annotation.unchecked.uncheckedVariance] diff --git a/src/reflect/scala/reflect/internal/StdNames.scala b/src/reflect/scala/reflect/internal/StdNames.scala index 165e04863c..d41670591c 100644 --- a/src/reflect/scala/reflect/internal/StdNames.scala +++ b/src/reflect/scala/reflect/internal/StdNames.scala @@ -246,6 +246,7 @@ trait StdNames { final val BeanPropertyAnnot: NameType = "BeanProperty" final val BooleanBeanPropertyAnnot: NameType = "BooleanBeanProperty" final val bridgeAnnot: NameType = "bridge" + final val staticAnnot: NameType = "static" // Classfile Attributes final val AnnotationDefaultATTR: NameType = "AnnotationDefault" diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index fab5c5a2e7..82e4f98028 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -938,6 +938,7 @@ trait Symbols extends api.Symbols { self: SymbolTable => || hasAnnotation(SerializableAttr) // last part can be removed, @serializable annotation is deprecated ) def hasBridgeAnnotation = hasAnnotation(BridgeClass) + def hasStaticAnnotation = hasAnnotation(StaticClass) def isDeprecated = hasAnnotation(DeprecatedAttr) def deprecationMessage = getAnnotation(DeprecatedAttr) flatMap (_ stringArg 0) def deprecationVersion = getAnnotation(DeprecatedAttr) flatMap (_ stringArg 1) diff --git a/test/files/neg/static-annot.check b/test/files/neg/static-annot.check new file mode 100644 index 0000000000..80aebcd406 --- /dev/null +++ b/test/files/neg/static-annot.check @@ -0,0 +1,10 @@ +static-annot.scala:8: error: Only members of top-level objects and their nested objects can be annotated with @static. + @static val bar = 1 + ^ +static-annot.scala:27: error: @static annotated field bar has the same name as a member of class Conflicting + @static val bar = 1 + ^ +static-annot.scala:14: error: Only members of top-level objects and their nested objects can be annotated with @static. + @static val blah = 2 + ^ +three errors found \ No newline at end of file diff --git a/test/files/neg/static-annot.scala b/test/files/neg/static-annot.scala new file mode 100644 index 0000000000..b8c4651076 --- /dev/null +++ b/test/files/neg/static-annot.scala @@ -0,0 +1,33 @@ + + +import annotation.static + + + +class StaticInClass { + @static val bar = 1 +} + + +class NestedObjectInClass { + object Nested { + @static val blah = 2 + } +} + + +object NestedObjectInObject { + object Nested { + @static val succeed = 3 + } +} + + +object Conflicting { + @static val bar = 1 +} + + +class Conflicting { + val bar = 45 +} diff --git a/test/files/run/static-annot/field.scala b/test/files/run/static-annot/field.scala new file mode 100644 index 0000000000..0677082cf6 --- /dev/null +++ b/test/files/run/static-annot/field.scala @@ -0,0 +1,205 @@ + + + +import java.lang.reflect.Modifier +import annotation.static +import reflect._ + + + +/* TEST 1 */ + +/* A @static-annotated field in the companion object should yield + * a static field in its companion class. + */ +object Foo { + @static val bar = 17 +} + + +class Foo + + +trait Check { + def checkStatic(cls: Class[_]) { + cls.getDeclaredFields.find(_.getName == "bar") match { + case Some(f) => assert(Modifier.isStatic(f.getModifiers), "no static modifier") + case None => assert(false, "no static field bar in class") + } + } + + def test(): Unit +} + + +object Test1 extends Check { + def test() { + checkStatic(classOf[Foo]) + assert(Foo.bar == 17, "Companion object field should be 17.") + } +} + + +/* TEST 2 */ + +class Foo2 + + +/** The order of declaring the class and its companion is inverted now. */ +object Foo2 { + @static val bar = 199 +} + + +object Test2 extends Check { + def test() { + checkStatic(Class.forName("Foo3")) + assert(Foo3.bar == 1984, "Companion object field should be 1984.") + } +} + + +/* TEST 3 */ + +/** The case where there is no explicit companion class */ +object Foo3 { + @static val bar = 1984 +} + + +object Test3 extends Check { + def test() { + checkStatic(Class.forName("Foo3")) + assert(Foo3.bar == 1984, "Companion object field should be 1984.") + } +} + + +/* TEST 4 */ + +/** We want to be able to generate atomic reference field updaters on the companion object + * so that they are created only once per class declaration, but we want them to actually + * be initialize __in the static initializer of the class itself__. + * This is extremely important, because otherwise the creation of the ARFU fails, since it uses + * trickery to detect the caller and compare it to the owner of the field being modified. + * Previously, this used to be circumvented through the use of Java base classes. A pain. + */ +class ArfuTarget { + @volatile var strfield = ArfuTarget.STR + + def CAS(ov: String, nv: String): Boolean = { + ArfuTarget.arfu.compareAndSet(this, ov, nv) + } +} + + +object ArfuTarget { + @static val arfu = java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater(classOf[ArfuTarget], classOf[String], "strfield") + val STR = "Some string" +} + + +object Test4 extends Check { + def checkArfu() { + val at = new ArfuTarget + assert(at.strfield == ArfuTarget.STR) + at.CAS(ArfuTarget.STR, null) + assert(at.strfield == null) + } + + def test() { + checkArfu() + } +} + + +/* TEST 5 */ + +/** Although our main use-case is to use final static fields, we should be able to use non-final too. + * Here we set the static field of the class by using the setters in the companion object. + * It is legal to do so using the reference to `Foo` directly (in which case the callsites + * are rewritten to access the static field directly), or through an interface `Var` (in + * which case the getter and the setter for `field` access the static field in `Var`). + */ +trait Var { + var field: Int +} + +object VarHolder extends Var { + @static var field = 1 +} + + +object Test5 extends Check { + def test() { + assert(VarHolder.field == 1) + VarHolder.field = 2 + assert(VarHolder.field == 2) + val vh: Var = VarHolder + vh.field = 3 + assert(vh.field == 3) + } +} + + +/* TEST 6 */ + +/** Here we test flattening the static ctor body and changing the owners of local definitions. */ +object Foo6 { + var companionField = 101 + @static val staticField = { + val intermediate = companionField + 1 + intermediate * 2 + } +} + + +object Test6 extends Check { + def test() { + assert(Foo6.staticField == 204) + } +} + + + +/* TEST 7 */ + +/** Here we test objects nested in top-level objects */ +object Foo7 { + object AndHisFriend { + @static val bar = "string" + } + class AndHisFriend +} + + +object Test7 extends Check { + def test() { + checkStatic(classOf[Foo7.AndHisFriend]) + assert(Foo7.AndHisFriend.bar == "string") + } +} + + + +/* main */ + +object Test { + + def main(args: Array[String]) { + Test1.test() + Test2.test() + Test3.test() + Test4.test() + Test5.test() + Test6.test() + Test7.test() + } + +} + + + + + + -- cgit v1.2.3 From b6b360058bc49d7c4308b153b9989bc1aeac4f53 Mon Sep 17 00:00:00 2001 From: Aleksandar Prokopec Date: Wed, 18 Jul 2012 17:30:59 +0200 Subject: Make field strict and private. --- src/library/scala/collection/immutable/Vector.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/library/scala/collection/immutable/Vector.scala b/src/library/scala/collection/immutable/Vector.scala index 1a0ef5eee7..4dfe147a65 100644 --- a/src/library/scala/collection/immutable/Vector.scala +++ b/src/library/scala/collection/immutable/Vector.scala @@ -22,7 +22,7 @@ object Vector extends SeqFactory[Vector] { override def apply() = newBuilder[Nothing] } - lazy val VectorReusableCBF: GenericCanBuildFrom[Nothing] = new VectorReusableCBF + private val VectorReusableCBF: GenericCanBuildFrom[Nothing] = new VectorReusableCBF @inline implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, Vector[A]] = VectorReusableCBF.asInstanceOf[CanBuildFrom[Coll, A, Vector[A]]] -- cgit v1.2.3 From 86f7bc35e5fd9c12913318ed2b31e207d6f261fb Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Wed, 18 Jul 2012 20:00:44 +0200 Subject: SI-6104 support This pattern This(name) is treated just like Ident(name) apparently this pattern was used in 2.9 code, though I'm not sure it's spec'ed --- src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala | 4 ++-- test/files/run/t6104.check | 1 + test/files/run/t6104.scala | 8 ++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 test/files/run/t6104.check create mode 100644 test/files/run/t6104.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala index 4e4176e531..4c4115a561 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala @@ -413,7 +413,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL The pattern matches any value v such that r == v (§12.1). The type of r must conform to the expected type of the pattern. **/ - case Literal(Constant(_)) | Ident(_) | Select(_, _) => + case Literal(Constant(_)) | Ident(_) | Select(_, _) | This(_) => noFurtherSubPats(EqualityTestTreeMaker(patBinder, patTree, pos)) case Alternative(alts) => @@ -430,7 +430,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL patmatDebug("WARNING: Bind tree with unbound symbol "+ patTree) noFurtherSubPats() // there's no symbol -- something's wrong... don't fail here though (or should we?) - // case Star(_) | ArrayValue | This => error("stone age pattern relics encountered!") + // case Star(_) | ArrayValue => error("stone age pattern relics encountered!") case _ => error("unsupported pattern: "+ patTree +"(a "+ patTree.getClass +")") diff --git a/test/files/run/t6104.check b/test/files/run/t6104.check new file mode 100644 index 0000000000..9766475a41 --- /dev/null +++ b/test/files/run/t6104.check @@ -0,0 +1 @@ +ok diff --git a/test/files/run/t6104.scala b/test/files/run/t6104.scala new file mode 100644 index 0000000000..8ab12c7752 --- /dev/null +++ b/test/files/run/t6104.scala @@ -0,0 +1,8 @@ +class A { Self => + val ok = "ok" + this match { + case me@Self => println(me.ok) + } +} + +object Test extends A with App \ No newline at end of file -- cgit v1.2.3 From 0018f9b3649f14d16debc966e7da4e54a9a0a4c3 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Wed, 18 Jul 2012 20:46:03 +0200 Subject: Scaladoc: Typers change as requested by @adriaanm on pull request #925 --- src/compiler/scala/tools/nsc/typechecker/Typers.scala | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala index 2d277603ee..a2ada0bf24 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala @@ -1972,9 +1972,7 @@ trait Typers extends Modes with Adaptations with Tags { case SilentResultValue(tpt) => val alias = enclClass.newAliasType(name.toTypeName, useCase.pos) val tparams = cloneSymbolsAtOwner(tpt.tpe.typeSymbol.typeParams, alias) - /* Unless we treat no-tparams usecases differently they blow up in typeFun - * def typeFun = PolyType(tparams, tpe) // <- which asserts (!tparams.isEmpty) */ - val newInfo = if (tparams.isEmpty) tpt.tpe else typeFun(tparams, appliedType(tpt.tpe, tparams map (_.tpe))) + val newInfo = genPolyType(tparams, appliedType(tpt.tpe, tparams map (_.tpe))) alias setInfo newInfo context.scope.enter(alias) case _ => -- cgit v1.2.3 From 4f3c92e0f8f8c8017a0363836324089f07d05c4c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 18 Jul 2012 23:11:13 +0200 Subject: SI-5939 resident compilation of sources in empty package Empty packages can now be cleanly invalidated. --- .../scala/tools/nsc/symtab/SymbolLoaders.scala | 21 +++++++++++---------- src/reflect/scala/reflect/internal/Mirrors.scala | 3 +-- src/reflect/scala/reflect/internal/Symbols.scala | 5 ----- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala index 0c988ceae4..9b4e793241 100644 --- a/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala +++ b/src/compiler/scala/tools/nsc/symtab/SymbolLoaders.scala @@ -34,8 +34,7 @@ abstract class SymbolLoaders { /** Enter class with given `name` into scope of `root` * and give them `completer` as type. */ - def enterClass(root: Symbol, name: String, completer: SymbolLoader): Symbol = { - val owner = root.ownerOfNewSymbols + def enterClass(owner: Symbol, name: String, completer: SymbolLoader): Symbol = { val clazz = owner.newClass(newTypeName(name)) clazz setInfo completer enterIfNew(owner, clazz, completer) @@ -44,8 +43,7 @@ abstract class SymbolLoaders { /** Enter module with given `name` into scope of `root` * and give them `completer` as type. */ - def enterModule(root: Symbol, name: String, completer: SymbolLoader): Symbol = { - val owner = root.ownerOfNewSymbols + def enterModule(owner: Symbol, name: String, completer: SymbolLoader): Symbol = { val module = owner.newModule(newTermName(name)) module setInfo completer module.moduleClass setInfo moduleClassLoader @@ -217,15 +215,18 @@ abstract class SymbolLoaders { root.setInfo(new PackageClassInfoType(newScope, root)) val sourcepaths = classpath.sourcepaths - for (classRep <- classpath.classes if platform.doLoad(classRep)) { - initializeFromClassPath(root, classRep) + if (!root.isRoot) { + for (classRep <- classpath.classes if platform.doLoad(classRep)) { + initializeFromClassPath(root, classRep) + } } + if (!root.isEmptyPackageClass) { + for (pkg <- classpath.packages) { + enterPackage(root, pkg.name, new PackageLoader(pkg)) + } - for (pkg <- classpath.packages) { - enterPackage(root, pkg.name, new PackageLoader(pkg)) + openPackageModule(root) } - - openPackageModule(root) } } diff --git a/src/reflect/scala/reflect/internal/Mirrors.scala b/src/reflect/scala/reflect/internal/Mirrors.scala index 210af661ee..761b993539 100644 --- a/src/reflect/scala/reflect/internal/Mirrors.scala +++ b/src/reflect/scala/reflect/internal/Mirrors.scala @@ -180,7 +180,7 @@ trait Mirrors extends api.Mirrors { // Still fiddling with whether it's cleaner to do some of this setup here // or from constructors. The latter approach tends to invite init order issues. - EmptyPackageClass setInfo ClassInfoType(Nil, newPackageScope(EmptyPackageClass), EmptyPackageClass) + EmptyPackageClass setInfo rootLoader EmptyPackage setInfo EmptyPackageClass.tpe connectModuleToClass(EmptyPackage, EmptyPackageClass) @@ -231,7 +231,6 @@ trait Mirrors extends api.Mirrors { override def isEffectiveRoot = true override def isStatic = true override def isNestedClass = false - override def ownerOfNewSymbols = EmptyPackageClass } // The empty package, which holds all top level types without given packages. final object EmptyPackage extends ModuleSymbol(RootClass, NoPosition, nme.EMPTY_PACKAGE_NAME) with WellKnownSymbol { diff --git a/src/reflect/scala/reflect/internal/Symbols.scala b/src/reflect/scala/reflect/internal/Symbols.scala index fab5c5a2e7..d484617767 100644 --- a/src/reflect/scala/reflect/internal/Symbols.scala +++ b/src/reflect/scala/reflect/internal/Symbols.scala @@ -813,11 +813,6 @@ trait Symbols extends api.Symbols { self: SymbolTable => */ def isEffectiveRoot = false - /** For RootClass, this is EmptyPackageClass. For all other symbols, - * the symbol itself. - */ - def ownerOfNewSymbols = this - final def isLazyAccessor = isLazy && lazyAccessor != NoSymbol final def isOverridableMember = !(isClass || isEffectivelyFinal) && (this ne NoSymbol) && owner.isClass -- cgit v1.2.3 From 4324282f27f2dadf90213882d3f5f346940bc0fd Mon Sep 17 00:00:00 2001 From: Dominik Gruntz Date: Thu, 19 Jul 2012 11:02:46 +0200 Subject: Removes Float.Epsilon and Double.Epsilon Float.Epsilon and Double.Epsilon contain the wrong value. Epsilon should be an upper bound on the relative error due to rounding errors in floating point arithmetic, i.e. it is 1/2 ULP (unit in the last position) which is 2^(-24) for Float and 2^(-53) for Double. This was discussed in detail in https://issues.scala-lang.org/browse/SI-3791 and it was decided to deprecate Epsilon for 2.9 and to remove it for 2.10 and to reintroduce it with the correct value for 2.11. See also the discussion (and comment from Martin) on https://groups.google.com/forum/?fromgroups#!topic/scala-internals/m763WjBdfyo --- src/library/scala/Double.scala | 3 --- src/library/scala/Float.scala | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/library/scala/Double.scala b/src/library/scala/Double.scala index 2ff46c433d..510de92a2a 100644 --- a/src/library/scala/Double.scala +++ b/src/library/scala/Double.scala @@ -370,9 +370,6 @@ object Double extends AnyValCompanion { final val PositiveInfinity = java.lang.Double.POSITIVE_INFINITY final val NegativeInfinity = java.lang.Double.NEGATIVE_INFINITY - @deprecated("use Double.MinPositiveValue instead", "2.9.0") - final val Epsilon = MinPositiveValue - /** The negative number with the greatest (finite) absolute value which is representable * by a Double. Note that it differs from [[java.lang.Double.MIN_VALUE]], which * is the smallest positive value representable by a Double. In Scala that number diff --git a/src/library/scala/Float.scala b/src/library/scala/Float.scala index bd7a07fece..b9c116da0b 100644 --- a/src/library/scala/Float.scala +++ b/src/library/scala/Float.scala @@ -370,9 +370,6 @@ object Float extends AnyValCompanion { final val PositiveInfinity = java.lang.Float.POSITIVE_INFINITY final val NegativeInfinity = java.lang.Float.NEGATIVE_INFINITY - @deprecated("use Float.MinPositiveValue instead", "2.9.0") - final val Epsilon = MinPositiveValue - /** The negative number with the greatest (finite) absolute value which is representable * by a Float. Note that it differs from [[java.lang.Float.MIN_VALUE]], which * is the smallest positive value representable by a Float. In Scala that number -- cgit v1.2.3 From 227239018b38ab7218ee6b30493c9c8e1836c8c9 Mon Sep 17 00:00:00 2001 From: Aleksandar Date: Thu, 19 Jul 2012 14:33:07 +0200 Subject: WIP add private/lazy checks and a few tests. --- .../scala/tools/nsc/transform/CleanUp.scala | 11 +++++++ test/files/neg/static-annot.check | 11 ++++++- test/files/neg/static-annot.scala | 14 ++++++++ test/files/run/static-annot/field.scala | 38 ++++++++++++++++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/transform/CleanUp.scala b/src/compiler/scala/tools/nsc/transform/CleanUp.scala index 1d8d58ccf7..e672f1914a 100644 --- a/src/compiler/scala/tools/nsc/transform/CleanUp.scala +++ b/src/compiler/scala/tools/nsc/transform/CleanUp.scala @@ -566,12 +566,23 @@ abstract class CleanUp extends Transform with ast.TreeDSL { val owner = sym.owner val staticBeforeLifting = atPhase(currentRun.erasurePhase) { owner.isStatic } + val isPrivate = atPhase(currentRun.typerPhase) { sym.getter(owner).hasFlag(PRIVATE) } + val isProtected = atPhase(currentRun.typerPhase) { sym.getter(owner).hasFlag(PROTECTED) } + val isLazy = atPhase(currentRun.typerPhase) { sym.getter(owner).hasFlag(LAZY) } if (!owner.isModuleClass || !staticBeforeLifting) { if (!sym.isSynthetic) { reporter.error(tree.pos, "Only members of top-level objects and their nested objects can be annotated with @static.") tree.symbol.removeAnnotation(StaticClass) } super.transform(tree) + } else if (isPrivate || isProtected) { + reporter.error(tree.pos, "The @static annotation is only allowed on public members.") + tree.symbol.removeAnnotation(StaticClass) + super.transform(tree) + } else if (isLazy) { + reporter.error(tree.pos, "The @static annotation is not allowed on lazy members.") + tree.symbol.removeAnnotation(StaticClass) + super.transform(tree) } else if (owner.isModuleClass) { val linkedClass = owner.companionClass match { case NoSymbol => diff --git a/test/files/neg/static-annot.check b/test/files/neg/static-annot.check index 80aebcd406..66efebdcee 100644 --- a/test/files/neg/static-annot.check +++ b/test/files/neg/static-annot.check @@ -4,7 +4,16 @@ static-annot.scala:8: error: Only members of top-level objects and their nested static-annot.scala:27: error: @static annotated field bar has the same name as a member of class Conflicting @static val bar = 1 ^ +static-annot.scala:37: error: The @static annotation is only allowed on public members. + @static private val bar = 1 + ^ +static-annot.scala:38: error: The @static annotation is only allowed on public members. + @static private val baz = 2 + ^ +static-annot.scala:39: error: The @static annotation is not allowed on lazy members. + @static lazy val bam = 3 + ^ static-annot.scala:14: error: Only members of top-level objects and their nested objects can be annotated with @static. @static val blah = 2 ^ -three errors found \ No newline at end of file +6 errors found \ No newline at end of file diff --git a/test/files/neg/static-annot.scala b/test/files/neg/static-annot.scala index b8c4651076..c6c626d42b 100644 --- a/test/files/neg/static-annot.scala +++ b/test/files/neg/static-annot.scala @@ -31,3 +31,17 @@ object Conflicting { class Conflicting { val bar = 45 } + + +object PrivateProtectedLazy { + @static private val bar = 1 + @static private val baz = 2 + @static lazy val bam = 3 +} + + +class PrivateProtectedLazy { + println(PrivateProtectedLazy.bar) + println(PrivateProtectedLazy.baz) + println(PrivateProtectedLazy.bam) +} diff --git a/test/files/run/static-annot/field.scala b/test/files/run/static-annot/field.scala index 0677082cf6..a7d8158321 100644 --- a/test/files/run/static-annot/field.scala +++ b/test/files/run/static-annot/field.scala @@ -182,6 +182,43 @@ object Test7 extends Check { +/* TEST 8 */ + +object Foo8 { + @static val field = 7 + + val function: () => Int = () => { + field + 1 + } + + val anon = new Runnable { + def run() { + assert(field == 7, "runnable asserting field is 7") + } + } + + @static var mutable = 10 + + val mutation: () => Unit = () => { + mutable += 1 + } +} + +object Test8 { + def test() { + assert(Foo8.function() == 8, "function must return 8") + Foo8.anon.run() + assert(Foo8.mutable == 10, "mutable is 10") + Foo8.mutation() + assert(Foo8.mutable == 11, "mutable is 11") + Foo8.mutation() + assert(Foo8.mutable == 12, "mutable is 12") + } +} + + + + /* main */ object Test { @@ -194,6 +231,7 @@ object Test { Test5.test() Test6.test() Test7.test() + Test8.test() } } -- cgit v1.2.3 From 1158e4f05b0511b31581d3a7f71863060c88ab42 Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Thu, 19 Jul 2012 12:18:25 +0200 Subject: Updated list of targets allowed in Ant's scalac. It looks like scala.tools.ant.Scalac class had a stale list of allowed targets. Made it in sync with what compiler supports. --- src/compiler/scala/tools/ant/Scalac.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/ant/Scalac.scala b/src/compiler/scala/tools/ant/Scalac.scala index e70716885e..c6809fb48e 100644 --- a/src/compiler/scala/tools/ant/Scalac.scala +++ b/src/compiler/scala/tools/ant/Scalac.scala @@ -99,7 +99,7 @@ class Scalac extends ScalaMatchingTask with ScalacShared { /** Defines valid values for the `target` property. */ object Target extends PermissibleValue { - val values = List("jvm-1.5", "jvm-1.6", "jvm-1.7", "msil") + val values = List("jvm-1.5", "jvm-1.5-fjbg", "jvm-1.5-asm", "jvm-1.6", "jvm-1.7", "msil") } /** Defines valid values for the `deprecation` and `unchecked` properties. */ -- cgit v1.2.3 From 45232af4de715ec118f8b9ab9cce25c33540c0d6 Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Thu, 19 Jul 2012 14:51:54 +0200 Subject: Better debugging output in GenASM. When assertion fails, let's provide the context. --- src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala index 756d90bc53..60bb3a43fa 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/GenASM.scala @@ -598,7 +598,7 @@ abstract class GenASM extends SubComponent with BytecodeWriters { reverseJavaName.put(internalName, trackedSym) case Some(oldsym) => assert((oldsym == trackedSym) || (oldsym == RuntimeNothingClass) || (oldsym == RuntimeNullClass), // In contrast, neither NothingClass nor NullClass show up bytecode-level. - "how can getCommonSuperclass() do its job if different class symbols get the same bytecode-level internal name.") + "how can getCommonSuperclass() do its job if different class symbols get the same bytecode-level internal name: " + internalName) } } -- cgit v1.2.3 From 0d367d4794e45ef021d9dfc7eeca186ba9fb632a Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Wed, 18 Jul 2012 20:28:06 +0200 Subject: Scaladoc: Groups Group class members based on their semantic relationship. To do this: - @group on members, only need to do it for the non-overridden members - -groups flag passes to scaladoc, groups="on" in ant - @groupdesc Group Group Description to add descriptions - @groupname Group New name for group - @groupprio Group (lower is better) See test/scaladoc/run/groups.scala for a top-to-bottom example --- build.xml | 37 +++--- src/compiler/scala/tools/ant/Scaladoc.scala | 8 ++ src/compiler/scala/tools/nsc/ast/DocComments.scala | 23 ++-- src/compiler/scala/tools/nsc/doc/Settings.scala | 7 +- .../scala/tools/nsc/doc/html/page/Template.scala | 42 ++++++- .../tools/nsc/doc/html/resource/lib/template.css | 25 ++++ .../tools/nsc/doc/html/resource/lib/template.js | 135 ++++++++++++++++----- .../scala/tools/nsc/doc/model/Entity.scala | 13 +- .../scala/tools/nsc/doc/model/ModelFactory.scala | 21 ++++ .../scala/tools/nsc/doc/model/comment/Body.scala | 2 +- .../tools/nsc/doc/model/comment/Comment.scala | 12 ++ .../nsc/doc/model/comment/CommentFactory.scala | 43 ++++++- src/compiler/scala/tools/nsc/util/DocStrings.scala | 6 + .../scala/tools/partest/ScaladocModelTest.scala | 9 +- test/scaladoc/run/groups.check | 1 + test/scaladoc/run/groups.scala | 119 ++++++++++++++++++ 16 files changed, 436 insertions(+), 67 deletions(-) create mode 100644 test/scaladoc/run/groups.check create mode 100644 test/scaladoc/run/groups.scala diff --git a/build.xml b/build.xml index b5db4ab4f4..304df1caf1 100644 --- a/build.xml +++ b/build.xml @@ -2044,7 +2044,7 @@ BOOTSTRAPPING BUILD (STRAP) LIBRARIES (Forkjoin, FJBG, ASM) ============================================================================ --> - + @@ -2112,9 +2112,10 @@ DOCUMENTATION classpathref="pack.classpath" addparams="${scalac.args.all}" docRootContent="${src.dir}/library/rootdoc.txt" - implicits="on" - diagrams="on" - rawOutput="${scaladoc.raw.output}" + implicits="on" + diagrams="on" + groups="on" + rawOutput="${scaladoc.raw.output}" noPrefixes="${scaladoc.no.prefixes}"> @@ -2199,8 +2200,9 @@ DOCUMENTATION srcdir="${src.dir}/compiler" docRootContent="${src.dir}/compiler/rootdoc.txt" addparams="${scalac.args.all}" - implicits="on" - diagrams="on" + implicits="on" + diagrams="on" + groups="on" rawOutput="${scaladoc.raw.output}" noPrefixes="${scaladoc.no.prefixes}"> @@ -2224,8 +2226,9 @@ DOCUMENTATION classpathref="pack.classpath" srcdir="${src.dir}/jline/src/main/java" addparams="${scalac.args.all}" - implicits="on" - diagrams="on" + implicits="on" + diagrams="on" + groups="on" rawOutput="${scaladoc.raw.output}" noPrefixes="${scaladoc.no.prefixes}"> @@ -2252,7 +2255,8 @@ DOCUMENTATION srcdir="${src.dir}/scalap" addparams="${scalac.args.all}" implicits="on" - diagrams="on" + diagrams="on" + groups="on" rawOutput="${scaladoc.raw.output}" noPrefixes="${scaladoc.no.prefixes}"> @@ -2276,8 +2280,9 @@ DOCUMENTATION classpathref="pack.classpath" srcdir="${src.dir}/partest" addparams="${scalac.args.all}" - implicits="on" - diagrams="on" + implicits="on" + diagrams="on" + groups="on" rawOutput="${scaladoc.raw.output}" noPrefixes="${scaladoc.no.prefixes}"> @@ -2301,8 +2306,9 @@ DOCUMENTATION classpathref="pack.classpath" srcdir="${src.dir}/continuations/plugin" addparams="${scalac.args.all}" - implicits="on" - diagrams="on" + implicits="on" + diagrams="on" + groups="on" rawOutput="${scaladoc.raw.output}" noPrefixes="${scaladoc.no.prefixes}"> @@ -2326,8 +2332,9 @@ DOCUMENTATION classpathref="pack.classpath" srcdir="${src.dir}/actors-migration" addparams="${scalac.args.all}" - implicits="on" - diagrams="on" + implicits="on" + diagrams="on" + groups="on" rawOutput="${scaladoc.raw.output}" noPrefixes="${scaladoc.no.prefixes}"> diff --git a/src/compiler/scala/tools/ant/Scaladoc.scala b/src/compiler/scala/tools/ant/Scaladoc.scala index 03cb770474..6201501a71 100644 --- a/src/compiler/scala/tools/ant/Scaladoc.scala +++ b/src/compiler/scala/tools/ant/Scaladoc.scala @@ -156,6 +156,9 @@ class Scaladoc extends ScalaMatchingTask { /** Instruct the scaladoc not to generate prefixes */ private var docNoPrefixes: Boolean = false + /** Instruct the scaladoc tool to group similar functions together */ + private var docGroups: Boolean = false + /*============================================================================*\ ** Properties setters ** \*============================================================================*/ @@ -435,6 +438,10 @@ class Scaladoc extends ScalaMatchingTask { def setNoPrefixes(input: String) = docNoPrefixes = Flag.getBooleanValue(input, "noPrefixes") + /** Instruct the scaladoc tool to group similar functions together */ + def setGroups(input: String) = + docGroups = Flag.getBooleanValue(input, "groups") + /*============================================================================*\ ** Properties getters ** \*============================================================================*/ @@ -634,6 +641,7 @@ class Scaladoc extends ScalaMatchingTask { docSettings.docDiagramsDebug.value = docDiagramsDebug docSettings.docRawOutput.value = docRawOutput docSettings.docNoPrefixes.value = docNoPrefixes + docSettings.docGroups.value = docGroups if(!docDiagramsDotPath.isEmpty) docSettings.docDiagramsDotPath.value = docDiagramsDotPath.get if (!docgenerator.isEmpty) docSettings.docgenerator.value = docgenerator.get diff --git a/src/compiler/scala/tools/nsc/ast/DocComments.scala b/src/compiler/scala/tools/nsc/ast/DocComments.scala index 19af01bda8..c2530bd5c7 100755 --- a/src/compiler/scala/tools/nsc/ast/DocComments.scala +++ b/src/compiler/scala/tools/nsc/ast/DocComments.scala @@ -171,15 +171,15 @@ trait DocComments { self: Global => * 3. If there is no @return section in `dst` but there is one in `src`, copy it. */ def merge(src: String, dst: String, sym: Symbol, copyFirstPara: Boolean = false): String = { - val srcSections = tagIndex(src) - val dstSections = tagIndex(dst) - val srcParams = paramDocs(src, "@param", srcSections) - val dstParams = paramDocs(dst, "@param", dstSections) - val srcTParams = paramDocs(src, "@tparam", srcSections) - val dstTParams = paramDocs(dst, "@tparam", dstSections) - val out = new StringBuilder - var copied = 0 - var tocopy = startTag(dst, dstSections dropWhile (!isMovable(dst, _))) + val srcSections = tagIndex(src) + val dstSections = tagIndex(dst) + val srcParams = paramDocs(src, "@param", srcSections) + val dstParams = paramDocs(dst, "@param", dstSections) + val srcTParams = paramDocs(src, "@tparam", srcSections) + val dstTParams = paramDocs(dst, "@tparam", dstSections) + val out = new StringBuilder + var copied = 0 + var tocopy = startTag(dst, dstSections dropWhile (!isMovable(dst, _))) if (copyFirstPara) { val eop = // end of comment body (first para), which is delimited by blank line, or tag, or end of comment @@ -209,6 +209,11 @@ trait DocComments { self: Global => for (tparam <- sym.typeParams) mergeSection(srcTParams get tparam.name.toString, dstTParams get tparam.name.toString) mergeSection(returnDoc(src, srcSections), returnDoc(dst, dstSections)) + if (sym.name.toString == "isEmpty") { + println(groupDoc(src, srcSections)) + println(groupDoc(dst, dstSections)) + } + mergeSection(groupDoc(src, srcSections), groupDoc(dst, dstSections)) if (out.length == 0) dst else { diff --git a/src/compiler/scala/tools/nsc/doc/Settings.scala b/src/compiler/scala/tools/nsc/doc/Settings.scala index 7662381186..ad178b8158 100644 --- a/src/compiler/scala/tools/nsc/doc/Settings.scala +++ b/src/compiler/scala/tools/nsc/doc/Settings.scala @@ -188,6 +188,11 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) "Expand all type aliases and abstract types into full template pages. (locally this can be done with the @template annotation)" ) + val docGroups = BooleanSetting ( + "-groups", + "Group similar functions together (based on the @group annotation)" + ) + // Somewhere slightly before r18708 scaladoc stopped building unless the // self-type check was suppressed. I hijacked the slotted-for-removal-anyway // suppress-vt-warnings option and renamed it for this purpose. @@ -201,7 +206,7 @@ class Settings(error: String => Unit, val printMsg: String => Unit = println(_)) docImplicits, docImplicitsDebug, docImplicitsShowAll, docDiagramsMaxNormalClasses, docDiagramsMaxImplicitClasses, docNoPrefixes, docNoLinkWarnings, docRawOutput, docSkipPackages, - docExpandAllTypes + docExpandAllTypes, docGroups ) val isScaladocSpecific: String => Boolean = scaladocSpecific map (_.name) diff --git a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala index 487ef447e9..422ea5ef1c 100644 --- a/src/compiler/scala/tools/nsc/doc/html/page/Template.scala +++ b/src/compiler/scala/tools/nsc/doc/html/page/Template.scala @@ -110,10 +110,26 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp
    - { if (tpl.linearizationTemplates.isEmpty && tpl.conversions.isEmpty) NodeSeq.Empty else + { if (tpl.linearizationTemplates.isEmpty && tpl.conversions.isEmpty && (!universe.settings.docGroups.value || (tpl.members.map(_.group).distinct.length == 1))) + NodeSeq.Empty + else
    Ordering -
    1. Alphabetic
    2. By inheritance
    +
      + { + if (!universe.settings.docGroups.value || (tpl.members.map(_.group).distinct.length == 1)) + NodeSeq.Empty + else +
    1. Grouped
    2. + } +
    3. Alphabetic
    4. + { + if (tpl.linearizationTemplates.isEmpty && tpl.conversions.isEmpty) + NodeSeq.Empty + else +
    5. By inheritance
    6. + } +
    } { if (tpl.linearizationTemplates.isEmpty && tpl.conversions.isEmpty) NodeSeq.Empty else @@ -223,6 +239,25 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp }
    +
    + { + val allGroups = tpl.members.map(_.group).distinct + val orderedGroups = allGroups.map(group => (tpl.groupPriority(group), group)).sorted.map(_._2) + // linearization + NodeSeq fromSeq (for (group <- orderedGroups) yield +
    +

    { tpl.groupName(group) }

    + { + tpl.groupDescription(group) match { + case Some(body) =>
    { bodyToHtml(body) }
    + case _ => NodeSeq.Empty + } + } +
    + ) + } +
    +
    @@ -242,7 +277,8 @@ class Template(universe: doc.Universe, generator: DiagramGenerator, tpl: DocTemp val memberComment = memberToCommentHtml(mbr, inTpl, false)
  5. + fullComment={ if(memberComment.filter(_.label=="div").isEmpty) "no" else "yes" } + group={ mbr.group }> { signature(mbr, false) } { memberComment } diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css index 1bee55313b..6fb7953724 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.css @@ -253,6 +253,17 @@ dl.attributes > dd { color: white; } +#groupedMembers > div.group > h3 { + background: #dadada url("typebg.gif") repeat-x bottom left; /* green */ + height: 17px; + font-size: 12pt; +} + +#groupedMembers > div.group > h3 * { + color: white; +} + + /* Member cells */ div.members > ol { @@ -516,6 +527,20 @@ div.members > ol > li:last-child { /* Comments structured layout */ +.group > div.comment { + padding-top: 5px; + padding-bottom: 5px; + padding-right: 5px; + padding-left: 5px; + border: 1px solid #ddd; + background-color: #eeeee; + margin-top:5px; + margin-bottom:5px; + margin-right:5px; + margin-left:5px; + display: block; +} + p.comment { display: block; margin-left: 14.7em; diff --git a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js index c418c3280b..afd0293fe1 100644 --- a/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js +++ b/src/compiler/scala/tools/nsc/doc/html/resource/lib/template.js @@ -43,20 +43,20 @@ $(document).ready(function(){ case 33: //page up input.val(""); - filter(false); + filter(false); break; case 34: //page down input.val(""); - filter(false); - break; + filter(false); + break; - default: + default: window.scrollTo(0, $("#mbrsel").offset().top); - filter(true); + filter(true); break; - } + } }); input.focus(function(event) { input.select(); @@ -70,7 +70,7 @@ $(document).ready(function(){ if (event.keyCode == 9) { // tab $("#index-input", window.parent.document).focus(); input.attr("value", ""); - return false; + return false; } }); @@ -135,18 +135,21 @@ $(document).ready(function(){ }); $("#order > ol > li.alpha").click(function() { if ($(this).hasClass("out")) { - $(this).removeClass("out").addClass("in"); - $("#order > ol > li.inherit").removeClass("in").addClass("out"); orderAlpha(); }; }) $("#order > ol > li.inherit").click(function() { if ($(this).hasClass("out")) { - $(this).removeClass("out").addClass("in"); - $("#order > ol > li.alpha").removeClass("in").addClass("out"); orderInherit(); }; }); + $("#order > ol > li.group").click(function() { + if ($(this).hasClass("out")) { + orderGroup(); + }; + }); + $("#groupedMembers").hide(); + initInherit(); // Create tooltips @@ -202,9 +205,14 @@ $(document).ready(function(){ // Set parent window title windowTitle(); + + if ($("#order > ol > li.group").length == 1) { orderGroup(); }; }); function orderAlpha() { + $("#order > ol > li.alpha").removeClass("out").addClass("in"); + $("#order > ol > li.inherit").removeClass("in").addClass("out"); + $("#order > ol > li.group").removeClass("in").addClass("out"); $("#template > div.parent").hide(); $("#template > div.conversion").hide(); $("#mbrsel > div[id=ancestors]").show(); @@ -212,12 +220,25 @@ function orderAlpha() { }; function orderInherit() { + $("#order > ol > li.inherit").removeClass("out").addClass("in"); + $("#order > ol > li.alpha").removeClass("in").addClass("out"); + $("#order > ol > li.group").removeClass("in").addClass("out"); $("#template > div.parent").show(); $("#template > div.conversion").show(); $("#mbrsel > div[id=ancestors]").hide(); filter(); }; +function orderGroup() { + $("#order > ol > li.group").removeClass("out").addClass("in"); + $("#order > ol > li.alpha").removeClass("in").addClass("out"); + $("#order > ol > li.inherit").removeClass("in").addClass("out"); + $("#template > div.parent").hide(); + $("#template > div.conversion").hide(); + $("#mbrsel > div[id=ancestors]").show(); + filter(); +}; + /** Prepares the DOM for inheritance-based display. To do so it will: * - hide all statically-generated parents headings; * - copy all members from the value and type members lists (flat members) to corresponding lists nested below the @@ -225,44 +246,74 @@ function orderInherit() { * - initialises a control variable used by the filter method to control whether filtering happens on flat members * or on inheritance-grouped members. */ function initInherit() { - // parents is a map from fully-qualified names to the DOM node of parent headings. - var parents = new Object(); + // inheritParents is a map from fully-qualified names to the DOM node of parent headings. + var inheritParents = new Object(); + var groupParents = new Object(); $("#inheritedMembers > div.parent").each(function(){ - parents[$(this).attr("name")] = $(this); + inheritParents[$(this).attr("name")] = $(this); }); $("#inheritedMembers > div.conversion").each(function(){ - parents[$(this).attr("name")] = $(this); + inheritParents[$(this).attr("name")] = $(this); + }); + $("#groupedMembers > div.group").each(function(){ + groupParents[$(this).attr("name")] = $(this); }); + $("#types > ol > li").each(function(){ var mbr = $(this); this.mbrText = mbr.find("> .fullcomment .cmt").text(); var qualName = mbr.attr("name"); var owner = qualName.slice(0, qualName.indexOf("#")); var name = qualName.slice(qualName.indexOf("#") + 1); - var parent = parents[owner]; - if (parent != undefined) { - var types = $("> .types > ol", parent); + var inheritParent = inheritParents[owner]; + if (inheritParent != undefined) { + var types = $("> .types > ol", inheritParent); + if (types.length == 0) { + inheritParent.append("

    Type Members

      "); + types = $("> .types > ol", inheritParent); + } + var clone = mbr.clone(); + clone[0].mbrText = this.mbrText; + types.append(clone); + } + var group = mbr.attr("group") + var groupParent = groupParents[group]; + if (groupParent != undefined) { + var types = $("> .types > ol", groupParent); if (types.length == 0) { - parent.append("

      Type Members

        "); - types = $("> .types > ol", parent); + groupParent.append("
          "); + types = $("> .types > ol", groupParent); } var clone = mbr.clone(); clone[0].mbrText = this.mbrText; types.append(clone); } }); + $("#values > ol > li").each(function(){ var mbr = $(this); this.mbrText = mbr.find("> .fullcomment .cmt").text(); var qualName = mbr.attr("name"); var owner = qualName.slice(0, qualName.indexOf("#")); var name = qualName.slice(qualName.indexOf("#") + 1); - var parent = parents[owner]; - if (parent != undefined) { - var values = $("> .values > ol", parent); + var inheritParent = inheritParents[owner]; + if (inheritParent != undefined) { + var values = $("> .values > ol", inheritParent); if (values.length == 0) { - parent.append("

          Value Members

            "); - values = $("> .values > ol", parent); + inheritParent.append("

            Value Members

              "); + values = $("> .values > ol", inheritParent); + } + var clone = mbr.clone(); + clone[0].mbrText = this.mbrText; + values.append(clone); + } + var group = mbr.attr("group") + var groupParent = groupParents[group]; + if (groupParent != undefined) { + var values = $("> .values > ol", groupParent); + if (values.length == 0) { + groupParent.append("
                "); + values = $("> .values > ol", groupParent); } var clone = mbr.clone(); clone[0].mbrText = this.mbrText; @@ -275,6 +326,9 @@ function initInherit() { $("#inheritedMembers > div.conversion").each(function() { if ($("> div.members", this).length == 0) { $(this).remove(); }; }); + $("#groupedMembers > div.group").each(function() { + if ($("> div.members", this).length == 0) { $(this).remove(); }; + }); }; /* filter used to take boolean scrollToMember */ @@ -284,26 +338,43 @@ function filter() { var queryRegExp = new RegExp(query, "i"); var privateMembersHidden = $("#visbl > ol > li.public").hasClass("in"); var orderingAlphabetic = $("#order > ol > li.alpha").hasClass("in"); - var hiddenSuperclassElementsLinearization = orderingAlphabetic ? $("#linearization > li.out") : $("#linearization > li:gt(0)"); + var orderingInheritance = $("#order > ol > li.inherit").hasClass("in"); + var orderingGroups = $("#order > ol > li.group").hasClass("in"); + var hiddenSuperclassElementsLinearization = orderingInheritance ? $("#linearization > li:gt(0)") : $("#linearization > li.out"); var hiddenSuperclassesLinearization = hiddenSuperclassElementsLinearization.map(function() { return $(this).attr("name"); }).get(); - var hiddenSuperclassElementsImplicits = orderingAlphabetic ? $("#implicits > li.out") : $("#implicits > li"); + var hiddenSuperclassElementsImplicits = orderingInheritance ? $("#implicits > li") : $("#implicits > li.out"); var hiddenSuperclassesImplicits = hiddenSuperclassElementsImplicits.map(function() { return $(this).attr("name"); }).get(); var hideInheritedMembers; - if(orderingAlphabetic) { + if (orderingAlphabetic) { + $("#allMembers").show(); $("#inheritedMembers").hide(); + $("#groupedMembers").hide(); hideInheritedMembers = true; $("#allMembers > .members").each(filterFunc); - } - else { - $("#inheritedMembers").show(); + } else if (orderingGroups) { + $("#groupedMembers").show(); + $("#inheritedMembers").hide(); + $("#allMembers").hide(); hideInheritedMembers = true; - $("#allMembers > .members").each(filterFunc); + $("#groupedMembers > .group > .members").each(filterFunc); + $("#groupedMembers > div.group").each(function() { + $(this).show(); + if ($("> div.members", this).not(":hidden").length == 0) { + $(this).hide(); + } else { + $(this).show(); + } + }); + } else if (orderingInheritance) { + $("#inheritedMembers").show(); + $("#groupedMembers").hide(); + $("#allMembers").hide(); hideInheritedMembers = false; $("#inheritedMembers > .parent > .members").each(filterFunc); $("#inheritedMembers > .conversion > .members").each(filterFunc); diff --git a/src/compiler/scala/tools/nsc/doc/model/Entity.scala b/src/compiler/scala/tools/nsc/doc/model/Entity.scala index 0836d7e4da..16ade0787e 100644 --- a/src/compiler/scala/tools/nsc/doc/model/Entity.scala +++ b/src/compiler/scala/tools/nsc/doc/model/Entity.scala @@ -116,6 +116,9 @@ trait MemberEntity extends Entity { /** The comment attached to this member, if any. */ def comment: Option[Comment] + /** The group this member is from */ + def group: String + /** The template of which this entity is a member. */ def inTemplate: DocTemplateEntity @@ -218,7 +221,6 @@ trait HigherKinded { /** The type parameters of this entity. */ def typeParams: List[TypeParam] - } @@ -328,6 +330,15 @@ trait DocTemplateEntity extends MemberTemplateEntity { /** If this template contains other templates, such as classes and traits, they will be shown in this diagram */ def contentDiagram: Option[Diagram] + + /** Returns the group description taken either from this template or its linearizationTypes */ + def groupDescription(group: String): Option[Body] + + /** Returns the group description taken either from this template or its linearizationTypes */ + def groupPriority(group: String): Int + + /** Returns the group description taken either from this template or its linearizationTypes */ + def groupName(group: String): String } /** A trait template. */ diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index 0ba32fceaa..eba3e080ef 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -121,6 +121,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } if (inTpl != null) thisFactory.comment(sym, thisTpl, inTpl) else None } + def group = if (comment.isDefined) comment.get.group.getOrElse("No Group") else "No Group" override def inTemplate = inTpl override def toRoot: List[MemberImpl] = this :: inTpl.toRoot def inDefinitionTemplates = this match { @@ -479,9 +480,29 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { case None => List() } else List.empty + // These are generated on-demand, make sure you don't call them more than once def inheritanceDiagram = makeInheritanceDiagram(this) def contentDiagram = makeContentDiagram(this) + + def groupSearch[T](extractor: Comment => T, default: T): T = { + // query this template + if (comment.isDefined) { + val entity = extractor(comment.get) + if (entity != default) return entity + } + // query linearization + if (!sym.isAliasType && !sym.isAbstractType) + for (tpl <- linearizationTemplates.collect{ case dtpl: DocTemplateImpl if dtpl!=this => dtpl}) { + val entity = tpl.groupSearch(extractor, default) + if (entity != default) return entity + } + default + } + + def groupDescription(group: String): Option[Body] = groupSearch(_.groupDesc.get(group), None) + def groupPriority(group: String): Int = groupSearch(_.groupPrio.get(group) match { case Some(prio) => prio; case _ => 0 }, 0) + def groupName(group: String): String = groupSearch(_.groupNames.get(group) match { case Some(name) => name; case _ => group }, group) } abstract class PackageImpl(sym: Symbol, inTpl: PackageImpl) extends DocTemplateImpl(sym, inTpl) with Package { diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala b/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala index 3ab8fc7805..3f0024cb68 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/Body.scala @@ -71,7 +71,7 @@ final case class Monospace(text: Inline) extends Inline final case class Text(text: String) extends Inline abstract class EntityLink(val title: Inline) extends Inline { def link: LinkTo } object EntityLink { - def apply(title: Inline, linkTo: LinkTo) = new EntityLink(title) { def link: LinkTo = linkTo} + def apply(title: Inline, linkTo: LinkTo) = new EntityLink(title) { def link: LinkTo = linkTo } def unapply(el: EntityLink): Option[(Inline, LinkTo)] = Some((el.title, el.link)) } final case class HtmlTag(data: String) extends Inline { diff --git a/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala b/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala index 7b70683db5..c8f4c2f285 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/Comment.scala @@ -114,6 +114,18 @@ abstract class Comment { /** A set of diagram directives for the content diagram */ def contentDiagram: List[String] + /** The group this member is part of */ + def group: Option[String] + + /** Member group descriptions */ + def groupDesc: Map[String,Body] + + /** Member group names (overriding the short tag) */ + def groupNames: Map[String,String] + + /** Member group priorities */ + def groupPrio: Map[String,Int] + override def toString = body.toString + "\n" + (authors map ("@author " + _.toString)).mkString("\n") + 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 a57ccd36c2..ac737b6ee3 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala @@ -114,7 +114,11 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member constructor0: Option[Body] = None, source0: Option[String] = None, inheritDiagram0: List[String] = List.empty, - contentDiagram0: List[String] = List.empty + contentDiagram0: List[String] = List.empty, + group0: Option[Body] = None, + groupDesc0: Map[String,Body] = Map.empty, + groupNames0: Map[String,Body] = Map.empty, + groupPrio0: Map[String,Body] = Map.empty ) : Comment = new Comment{ val body = if(body0 isDefined) body0.get else Body(Seq.empty) val authors = authors0 @@ -133,6 +137,35 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member val source = source0 val inheritDiagram = inheritDiagram0 val contentDiagram = contentDiagram0 + val groupDesc = groupDesc0 + val group = + group0 match { + case Some(Body(List(Paragraph(Chain(List(Summary(Text(groupId)))))))) => Some(groupId.toString.trim) + case _ => None + } + val groupPrio = groupPrio0 flatMap { + case (group, body) => + try { + body match { + case Body(List(Paragraph(Chain(List(Summary(Text(prio))))))) => List(group -> prio.toInt) + case _ => List() + } + } catch { + case _: java.lang.NumberFormatException => List() + } + } + val groupNames = groupNames0 flatMap { + case (group, body) => + try { + body match { + case Body(List(Paragraph(Chain(List(Summary(Text(name))))))) if (!name.trim.contains("\n")) => List(group -> (name.trim)) + case _ => List() + } + } catch { + case _: java.lang.NumberFormatException => List() + } + } + } protected val endOfText = '\u0003' @@ -202,7 +235,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member /** A Scaladoc tag linked to a symbol. Returns the name of the tag, the name * of the symbol, and the rest of the line. */ protected val SymbolTag = - new Regex("""\s*@(param|tparam|throws)\s+(\S*)\s*(.*)""") + new Regex("""\s*@(param|tparam|throws|groupdesc|groupname|groupprio)\s+(\S*)\s*(.*)""") /** The start of a scaladoc code block */ protected val CodeBlockStart = @@ -403,7 +436,11 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member constructor0 = oneTag(SimpleTagKey("constructor")), source0 = Some(clean(src).mkString("\n")), inheritDiagram0 = inheritDiagramText, - contentDiagram0 = contentDiagramText + contentDiagram0 = contentDiagramText, + group0 = oneTag(SimpleTagKey("group")), + groupDesc0 = allSymsOneTag(SimpleTagKey("groupdesc")), + groupNames0 = allSymsOneTag(SimpleTagKey("groupname")), + groupPrio0 = allSymsOneTag(SimpleTagKey("groupprio")) ) for ((key, _) <- bodyTags) diff --git a/src/compiler/scala/tools/nsc/util/DocStrings.scala b/src/compiler/scala/tools/nsc/util/DocStrings.scala index f4ce6d6ef1..c88414c423 100755 --- a/src/compiler/scala/tools/nsc/util/DocStrings.scala +++ b/src/compiler/scala/tools/nsc/util/DocStrings.scala @@ -143,6 +143,12 @@ object DocStrings { } } + /** Optionally start and end index of return section in `str`, or `None` + * if `str` does not have a @group. */ + def groupDoc(str: String, sections: List[(Int, Int)]): Option[(Int, Int)] = + sections find (startsWithTag(str, _, "@group")) + + /** Optionally start and end index of return section in `str`, or `None` * if `str` does not have a @return. */ diff --git a/src/partest/scala/tools/partest/ScaladocModelTest.scala b/src/partest/scala/tools/partest/ScaladocModelTest.scala index adf7abe11c..ffc5e74cc0 100644 --- a/src/partest/scala/tools/partest/ScaladocModelTest.scala +++ b/src/partest/scala/tools/partest/ScaladocModelTest.scala @@ -169,14 +169,19 @@ abstract class ScaladocModelTest extends DirectTest { }).mkString(", ") + "]") } - def extractCommentText(c: Comment) = { + def extractCommentText(c: Any) = { def extractText(body: Any): String = body match { case s: String => s case s: Seq[_] => s.toList.map(extractText(_)).mkString case p: Product => p.productIterator.toList.map(extractText(_)).mkString case _ => "" } - extractText(c.body) + c match { + case c: Comment => + extractText(c.body) + case b: Body => + extractText(b) + } } def countLinks(c: Comment, p: EntityLink => Boolean) = { diff --git a/test/scaladoc/run/groups.check b/test/scaladoc/run/groups.check new file mode 100644 index 0000000000..619c56180b --- /dev/null +++ b/test/scaladoc/run/groups.check @@ -0,0 +1 @@ +Done. diff --git a/test/scaladoc/run/groups.scala b/test/scaladoc/run/groups.scala new file mode 100644 index 0000000000..05324c2ec9 --- /dev/null +++ b/test/scaladoc/run/groups.scala @@ -0,0 +1,119 @@ +import scala.tools.nsc.doc.model._ +import scala.tools.partest.ScaladocModelTest + +object Test extends ScaladocModelTest { + + override def code = """ + package test.scaladoc.groups { + + /** + * The trait A + * @groupdesc A Group A is the group that contains functions starting with f + * For example: + * {{{ + * this is an example + * }}} + * @groupdesc B Group B is the group that contains functions starting with b + * @groupname B Group B has a nice new name and a high priority + * @groupprio B -10 + * @group Traits + * @note This is a note + */ + trait A { + /** foo description + * @group A */ + def foo = 1 + + /** bar description + * @group B */ + def bar = 2 + } + + /** The trait B + * @group Traits + * @groupdesc C Group C is introduced by B + */ + trait B { + /** baz descriptopn + * @group C */ + def baz = 3 + } + + /** The class C which inherits from both A and B + * @group Classes + * @groupdesc B Look ma, I'm overriding group descriptions!!! + * @groupname B And names + */ + class C extends A with B { + /** Oh noes, I lost my group -- or did I?!? */ + override def baz = 4 + } + } + """ + + // no need for special settings + def scaladocSettings = "-feature" + + def testModel(rootPackage: Package) = { + // get the quick access implicit defs in scope (_package(s), _class(es), _trait(s), object(s) _method(s), _value(s)) + import access._ + + // just need to check the member exists, access methods will throw an error if there's a problem + val base = rootPackage._package("test")._package("scaladoc")._package("groups") + + def checkGroup(mbr: MemberEntity, grp: String) = + assert(mbr.group == grp, "Incorrect group for " + mbr.qualifiedName + ": " + mbr.group + " instead of " + grp) + + def checkGroupDesc(dtpl: DocTemplateEntity, grp: String, grpDesc: String) = { + assert(dtpl.groupDescription(grp).isDefined, + "Group description for " + grp + " not defined in " + dtpl.qualifiedName) + assert(extractCommentText(dtpl.groupDescription(grp).get).contains(grpDesc), + "Group description for " + grp + " in " + dtpl.qualifiedName + " does not contain \"" + grpDesc + "\": \"" + + extractCommentText(dtpl.groupDescription(grp).get) + "\"") + } + + def checkGroupName(dtpl: DocTemplateEntity, grp: String, grpName: String) = + // TODO: See why we need trim here, we already do trimming in the CommentFactory + assert(dtpl.groupName(grp) == grpName, + "Group name for " + grp + " in " + dtpl.qualifiedName + " does not equal \"" + grpName + "\": \"" + dtpl.groupName(grp) + "\"") + + def checkGroupPrio(dtpl: DocTemplateEntity, grp: String, grpPrio: Int) = + assert(dtpl.groupPriority(grp) == grpPrio, + "Group priority for " + grp + " in " + dtpl.qualifiedName + " does not equal " + grpPrio + ": " + dtpl.groupPriority(grp)) + + + val A = base._trait("A") + val B = base._trait("B") + val C = base._class("C") + checkGroup(A, "Traits") + checkGroup(B, "Traits") + checkGroup(C, "Classes") + checkGroup(A._method("foo"), "A") + checkGroup(A._method("bar"), "B") + checkGroup(B._method("baz"), "C") + checkGroup(C._method("foo"), "A") + checkGroup(C._method("bar"), "B") + checkGroup(C._method("baz"), "C") + + checkGroupDesc(A, "A", "Group A is the group that contains functions starting with f") + checkGroupName(A, "A", "A") + checkGroupPrio(A, "A", 0) + checkGroupDesc(A, "B", "Group B is the group that contains functions starting with b") + checkGroupName(A, "B", "Group B has a nice new name and a high priority") + checkGroupPrio(A, "B", -10) + + checkGroupDesc(B, "C", "Group C is introduced by B") + checkGroupName(B, "C", "C") + checkGroupPrio(B, "C", 0) + + checkGroupDesc(C, "A", "Group A is the group that contains functions starting with f") + checkGroupName(C, "A", "A") + checkGroupPrio(C, "A", 0) + checkGroupDesc(C, "B", "Look ma, I'm overriding group descriptions!!!") + checkGroupName(C, "B", "And names") + checkGroupPrio(C, "B", -10) + checkGroupDesc(C, "C", "Group C is introduced by B") + checkGroupName(C, "C", "C") + checkGroupPrio(C, "C", 0) + } +} \ No newline at end of file -- cgit v1.2.3 From 6539a9ddc5ecdec65ef1d874999a200cee46b2c4 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Thu, 19 Jul 2012 15:03:13 +0200 Subject: SI-5784 Scaladoc: Type templates @template and @documentable are now synomims. --- src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala | 3 ++- src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala | 2 +- test/scaladoc/resources/SI-5784.scala | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index eba3e080ef..af33911681 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -1075,6 +1075,7 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { // whether or not to create a page for an {abstract,alias} type def typeShouldDocument(bSym: Symbol, inTpl: DocTemplateImpl) = (settings.docExpandAllTypes.value && (bSym.sourceFile != null)) || - global.expandedDocComment(bSym, inTpl.sym).contains("@template") + { val rawComment = global.expandedDocComment(bSym, inTpl.sym) + rawComment.contains("@template") || rawComment.contains("@documentable") } } 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 ac737b6ee3..1a11964e37 100644 --- a/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/comment/CommentFactory.scala @@ -383,7 +383,7 @@ trait CommentFactory { thisFactory: ModelFactory with CommentFactory with Member case None => List.empty } - val stripTags=List(inheritDiagramTag, contentDiagramTag, SimpleTagKey("template")) + val stripTags=List(inheritDiagramTag, contentDiagramTag, SimpleTagKey("template"), SimpleTagKey("documentable")) val tagsWithoutDiagram = tags.filterNot(pair => stripTags.contains(pair._1)) val bodyTags: mutable.Map[TagKey, List[Body]] = diff --git a/test/scaladoc/resources/SI-5784.scala b/test/scaladoc/resources/SI-5784.scala index 175cc3cf33..3731d4998c 100644 --- a/test/scaladoc/resources/SI-5784.scala +++ b/test/scaladoc/resources/SI-5784.scala @@ -8,7 +8,7 @@ package test.templates { /** @contentDiagram */ trait Base { - /** @template */ + /** @documentable */ type String = test.templates.String /** @template * @inheritanceDiagram */ @@ -20,7 +20,7 @@ package test.templates { /** @contentDiagram */ trait Api extends Base { - /** @template + /** @documentable * @inheritanceDiagram */ override type T <: FooApi trait FooApi extends Foo { def bar: String } -- cgit v1.2.3 From b0c7f0b257d86634bb344405273200310f8ba386 Mon Sep 17 00:00:00 2001 From: Vlad Ureche Date: Thu, 19 Jul 2012 16:03:53 +0200 Subject: Scaladoc: Adressed @hubertp's comment on #925 And relaxed the groups name/description/priority search algorithm to work for types. --- .../scala/tools/nsc/doc/model/ModelFactory.scala | 37 ++++++++++++---------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala index af33911681..00e6f3769e 100644 --- a/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala +++ b/src/compiler/scala/tools/nsc/doc/model/ModelFactory.scala @@ -492,11 +492,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { if (entity != default) return entity } // query linearization - if (!sym.isAliasType && !sym.isAbstractType) - for (tpl <- linearizationTemplates.collect{ case dtpl: DocTemplateImpl if dtpl!=this => dtpl}) { - val entity = tpl.groupSearch(extractor, default) - if (entity != default) return entity - } + for (tpl <- linearizationTemplates.collect{ case dtpl: DocTemplateImpl if dtpl!=this => dtpl}) { + val entity = tpl.groupSearch(extractor, default) + if (entity != default) return entity + } default } @@ -601,9 +600,10 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { } /* ============== MAKER METHODS ============== */ - /** This method makes it easier to work with the different kinds of symbols created by scalac + /** This method makes it easier to work with the different kinds of symbols created by scalac by stripping down the + * package object abstraction and placing members directly in the package. * - * Okay, here's the explanation of what happens. The code: + * Here's the explanation of what we do. The code: * * package foo { * object `package` { @@ -612,17 +612,22 @@ class ModelFactory(val global: Global, val settings: doc.Settings) { * } * * will yield this Symbol structure: - * - * +---------------+ +--------------------------+ - * | package foo#1 ----(1)---> module class foo#2 | - * +---------------+ | +----------------------+ | +-------------------------+ - * | | package object foo#3 ------(1)---> module class package#4 | - * | +----------------------+ | | +---------------------+ | - * +--------------------------+ | | class package$Bar#5 | | - * | +---------------------+ | - * +-------------------------+ + * +---------+ (2) + * | | + * +---------------+ +---------- v ------- | ---+ +--------+ (2) + * | package foo#1 <---(1)---- module class foo#2 | | | | + * +---------------+ | +------------------ | -+ | +------------------- v ---+ | + * | | package object foo#3 <-----(1)---- module class package#4 | | + * | +----------------------+ | | +---------------------+ | | + * +--------------------------+ | | class package$Bar#5 | | | + * | +----------------- | -+ | | + * +------------------- | ---+ | + * | | + * +--------+ * (1) sourceModule * (2) you get out of owners with .owner + * + * and normalizeTemplate(Bar.owner) will get us the package, instead of the module class of the package object. */ def normalizeTemplate(aSym: Symbol): Symbol = aSym match { case null | rootMirror.EmptyPackage | NoSymbol => -- cgit v1.2.3 From a40a90fc33b6ba6fbc042cbf2f69f46a66417e2f Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 19 Jul 2012 07:26:54 -0700 Subject: Shield from InterruptedException in partest. Sorry, I'm not in practice with java.util.concurrent. --- src/partest/scala/tools/partest/nest/DirectRunner.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/partest/scala/tools/partest/nest/DirectRunner.scala b/src/partest/scala/tools/partest/nest/DirectRunner.scala index 6c239721c3..86c790f9dd 100644 --- a/src/partest/scala/tools/partest/nest/DirectRunner.scala +++ b/src/partest/scala/tools/partest/nest/DirectRunner.scala @@ -59,8 +59,11 @@ trait DirectRunner { val futures = kindFiles map (f => (f, pool submit callable(manager runTest f))) toMap pool.shutdown() - if (!pool.awaitTermination(4, TimeUnit.HOURS)) + try if (!pool.awaitTermination(4, TimeUnit.HOURS)) NestUI.warning("Thread pool timeout elapsed before all tests were complete!") + catch { case _: InterruptedException => + NestUI.warning("Thread pool was interrupted") + } for ((file, future) <- futures) yield { val state = if (future.isCancelled) TestState.Timeout else future.get -- cgit v1.2.3 From ee0d01785ebc7b72164ad4e0d6a952d99a5f21f2 Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Thu, 19 Jul 2012 16:34:57 +0200 Subject: Deprecate all JVM 1.5 targets and make 1.6 default. Add a check if deprecated target is being used. I put that check into `checkDeprecatedSettings`. I tried to invent some general mechanism for deprecating choices in ChoiceSetting but I gave up eventually. It wasn't worth it the complexity. Also, with current approach I'm able to provide nice, customized deprecation warning. Make `jvm-1.6` a default backend. Altered test for SI-5957 because it crashes the backend. However, the problem is not with backend but with symbol creation. We get two different symbols with the same internal name and both are used in trees that reach GenASM. See SI-6109 for details. Review by @magarciaEPFL and @paulp. --- src/compiler/scala/tools/nsc/Global.scala | 2 ++ src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala | 4 ++-- test/files/pos/t5957/T_1.scala | 6 ++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/compiler/scala/tools/nsc/Global.scala b/src/compiler/scala/tools/nsc/Global.scala index e378d71944..83335c4f62 100644 --- a/src/compiler/scala/tools/nsc/Global.scala +++ b/src/compiler/scala/tools/nsc/Global.scala @@ -1447,6 +1447,8 @@ class Global(var currentSettings: Settings, var reporter: Reporter) settings.userSetSettings filter (_.isDeprecated) foreach { s => unit.deprecationWarning(NoPosition, s.name + " is deprecated: " + s.deprecationMessage.get) } + if (settings.target.value.contains("jvm-1.5")) + unit.deprecationWarning(NoPosition, settings.target.name + ":" + settings.target.value + " is deprecated: use target for Java 1.6 or above.") } /* An iterator returning all the units being compiled in this run */ diff --git a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala index f0ee8b11f3..0991577829 100644 --- a/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala +++ b/src/compiler/scala/tools/nsc/settings/StandardScalaSettings.scala @@ -40,9 +40,9 @@ trait StandardScalaSettings { val nowarn = BooleanSetting ("-nowarn", "Generate no warnings.") val optimise: BooleanSetting // depends on post hook which mutates other settings val print = BooleanSetting ("-print", "Print program with Scala-specific features removed.") - val target = ChoiceSetting ("-target", "target", "Target platform for object files.", + val target = ChoiceSetting ("-target", "target", "Target platform for object files. All JVM 1.5 targets are deprecated.", List("jvm-1.5", "jvm-1.5-fjbg", "jvm-1.5-asm", "jvm-1.6", "jvm-1.7", "msil"), - "jvm-1.5-asm") + "jvm-1.6") val unchecked = BooleanSetting ("-unchecked", "Enable detailed unchecked (erasure) warnings.") val uniqid = BooleanSetting ("-uniqid", "Uniquely tag all identifiers in debugging output.") val usejavacp = BooleanSetting ("-usejavacp", "Utilize the java.class.path in classpath resolution.") diff --git a/test/files/pos/t5957/T_1.scala b/test/files/pos/t5957/T_1.scala index 1db5a3891f..339dcbf0f0 100644 --- a/test/files/pos/t5957/T_1.scala +++ b/test/files/pos/t5957/T_1.scala @@ -1,6 +1,8 @@ abstract class T { - def t1: Test$Bar + // see: SI-6109 + // def t1: Test$Bar def t2: Test#Bar - def t3: Test$Baz + // see: SI-6109 + // def t3: Test$Baz def t4: Test.Baz } -- cgit v1.2.3 From e200a8e3ffe10eb132c2cc0f5038cc42ce89414c Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 19 Jul 2012 07:36:20 -0700 Subject: Print the stack trace. We're not completely blind in there, are we. --- src/partest/scala/tools/partest/nest/DirectRunner.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/partest/scala/tools/partest/nest/DirectRunner.scala b/src/partest/scala/tools/partest/nest/DirectRunner.scala index 86c790f9dd..c254472342 100644 --- a/src/partest/scala/tools/partest/nest/DirectRunner.scala +++ b/src/partest/scala/tools/partest/nest/DirectRunner.scala @@ -61,8 +61,9 @@ trait DirectRunner { pool.shutdown() try if (!pool.awaitTermination(4, TimeUnit.HOURS)) NestUI.warning("Thread pool timeout elapsed before all tests were complete!") - catch { case _: InterruptedException => + catch { case t: InterruptedException => NestUI.warning("Thread pool was interrupted") + t.printStackTrace() } for ((file, future) <- futures) yield { -- cgit v1.2.3 From faf0f3de05e79af3fd7b5cf3bc3f97331e25042e Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Thu, 19 Jul 2012 15:27:28 +0200 Subject: Critical bugfixes/leak fixes/API corrections + ScalaDoc for SIP-14 --- src/library/scala/concurrent/BlockContext.scala | 35 ++- src/library/scala/concurrent/Future.scala | 6 +- .../default/SchedulerImpl.scala.disabled | 44 --- .../concurrent/default/TaskImpl.scala.disabled | 313 --------------------- .../concurrent/impl/ExecutionContextImpl.scala | 10 +- src/library/scala/concurrent/impl/Future.scala | 9 - src/library/scala/concurrent/package.scala | 76 ++--- test/files/jvm/future-spec/FutureTests.scala | 6 + test/files/jvm/future-spec/PromiseTests.scala | 9 +- test/files/jvm/scala-concurrent-tck.scala | 20 +- 10 files changed, 85 insertions(+), 443 deletions(-) delete mode 100644 src/library/scala/concurrent/default/SchedulerImpl.scala.disabled delete mode 100644 src/library/scala/concurrent/default/TaskImpl.scala.disabled diff --git a/src/library/scala/concurrent/BlockContext.scala b/src/library/scala/concurrent/BlockContext.scala index a5b878c546..640560a174 100644 --- a/src/library/scala/concurrent/BlockContext.scala +++ b/src/library/scala/concurrent/BlockContext.scala @@ -12,9 +12,10 @@ import java.lang.Thread import scala.concurrent.util.Duration /** - * A context to be notified by `scala.concurrent.blocking()` when + * A context to be notified by `scala.concurrent.blocking` when * a thread is about to block. In effect this trait provides - * the implementation for `scala.concurrent.blocking()`. `scala.concurrent.blocking()` + * the implementation for `scala.concurrent.Await`. + * `scala.concurrent.Await.result()` and `scala.concurrent.Await.ready()` * locates an instance of `BlockContext` by first looking for one * provided through `BlockContext.withBlockContext()` and failing that, * checking whether `Thread.currentThread` is an instance of `BlockContext`. @@ -27,11 +28,11 @@ import scala.concurrent.util.Duration * {{{ * val oldContext = BlockContext.current * val myContext = new BlockContext { - * override def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = { + * override def blockOn[T](thunk: =>T)(implicit permission: CanAwait): T = { * // you'd have code here doing whatever you need to do * // when the thread is about to block. * // Then you'd chain to the previous context: - * oldContext.internalBlockingCall(awaitable, atMost) + * oldContext.blockOn(thunk) * } * } * BlockContext.withBlockContext(myContext) { @@ -42,35 +43,33 @@ import scala.concurrent.util.Duration */ trait BlockContext { - /** Used internally by the framework; blocks execution for at most - * `atMost` time while waiting for an `awaitable` object to become ready. + /** Used internally by the framework; + * Designates (and eventually executes) a thunk which potentially blocks the calling `Thread`. * - * Clients should use `scala.concurrent.blocking` instead; this is - * the implementation of `scala.concurrent.blocking`, generally - * provided by a `scala.concurrent.ExecutionContext` or `java.util.concurrent.Executor`. + * Clients must use `scala.concurrent.blocking` or `scala.concurrent.Await` instead. */ - def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T + def blockOn[T](thunk: =>T)(implicit permission: CanAwait): T } object BlockContext { private object DefaultBlockContext extends BlockContext { - override def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = - awaitable.result(atMost)(Await.canAwaitEvidence) + override def blockOn[T](thunk: =>T)(implicit permission: CanAwait): T = thunk } - private val contextLocal = new ThreadLocal[BlockContext]() { - override def initialValue = Thread.currentThread match { + private val contextLocal = new ThreadLocal[BlockContext]() + + /** Obtain the current thread's current `BlockContext`. */ + def current: BlockContext = contextLocal.get match { + case null => Thread.currentThread match { case ctx: BlockContext => ctx case _ => DefaultBlockContext } + case some => some } - /** Obtain the current thread's current `BlockContext`. */ - def current: BlockContext = contextLocal.get - /** Pushes a current `BlockContext` while executing `body`. */ def withBlockContext[T](blockContext: BlockContext)(body: => T): T = { - val old = contextLocal.get + val old = contextLocal.get // can be null try { contextLocal.set(blockContext) body diff --git a/src/library/scala/concurrent/Future.scala b/src/library/scala/concurrent/Future.scala index f82b79cb18..d24fdbf005 100644 --- a/src/library/scala/concurrent/Future.scala +++ b/src/library/scala/concurrent/Future.scala @@ -707,11 +707,9 @@ object Future { // doesn't need to create defaultExecutionContext as // a side effect. private[concurrent] object InternalCallbackExecutor extends ExecutionContext { - def execute(runnable: Runnable): Unit = + override def execute(runnable: Runnable): Unit = runnable.run() - def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = - throw new IllegalStateException("bug in scala.concurrent, called blocking() from internal callback") - def reportFailure(t: Throwable): Unit = + override def reportFailure(t: Throwable): Unit = throw new IllegalStateException("problem in scala.concurrent internal callback", t) } } diff --git a/src/library/scala/concurrent/default/SchedulerImpl.scala.disabled b/src/library/scala/concurrent/default/SchedulerImpl.scala.disabled deleted file mode 100644 index 241efa8857..0000000000 --- a/src/library/scala/concurrent/default/SchedulerImpl.scala.disabled +++ /dev/null @@ -1,44 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ Scala API ** -** / __/ __// _ | / / / _ | (c) 2003-2011, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** -** /____/\___/_/ |_/____/_/ | | ** -** |/ ** -\* */ - -package scala.concurrent -package default - -import scala.concurrent.util.Duration - -private[concurrent] final class SchedulerImpl extends Scheduler { - private val timer = - new java.util.Timer(true) // the associated thread runs as a daemon - - def schedule(delay: Duration, frequency: Duration)(thunk: => Unit): Cancellable = ??? - - def scheduleOnce(delay: Duration, task: Runnable): Cancellable = { - val timerTask = new java.util.TimerTask { - def run(): Unit = - task.run() - } - timer.schedule(timerTask, delay.toMillis) - new Cancellable { - def cancel(): Unit = - timerTask.cancel() - } - } - - def scheduleOnce(delay: Duration)(task: => Unit): Cancellable = { - val timerTask = new java.util.TimerTask { - def run(): Unit = - task - } - timer.schedule(timerTask, delay.toMillis) - new Cancellable { - def cancel(): Unit = - timerTask.cancel() - } - } - -} diff --git a/src/library/scala/concurrent/default/TaskImpl.scala.disabled b/src/library/scala/concurrent/default/TaskImpl.scala.disabled deleted file mode 100644 index 8b4eb12d4f..0000000000 --- a/src/library/scala/concurrent/default/TaskImpl.scala.disabled +++ /dev/null @@ -1,313 +0,0 @@ -package scala.concurrent -package default - - - -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater -import scala.concurrent.forkjoin.{ ForkJoinPool, RecursiveAction, ForkJoinWorkerThread } -import scala.util.Try -import scala.util -import scala.concurrent.util.Duration -import scala.annotation.tailrec -import scala.util.control.NonFatal - - -private[concurrent] trait Completable[T] { -self: Future[T] => - - val executor: ExecutionContextImpl - - def newPromise[S]: Promise[S] = executor promise - - type Callback = Try[T] => Any - - def getState: State[T] - - def casState(oldv: State[T], newv: State[T]): Boolean - - protected def dispatch[U](r: Runnable) = executionContext execute r - - protected def processCallbacks(cbs: List[Callback], r: Try[T]) = - for (cb <- cbs) dispatch(new Runnable { - override def run() = cb(r) - }) - - def future: Future[T] = self - - def onComplete[U](callback: Try[T] => U): this.type = { - @tailrec def tryAddCallback(): Try[T] = { - getState match { - case p @ Pending(lst) => - val pt = p.asInstanceOf[Pending[T]] - if (casState(pt, Pending(callback :: pt.callbacks))) null - else tryAddCallback() - case Success(res) => util.Success(res) - case Failure(t) => util.Failure(t) - } - } - - val res = tryAddCallback() - if (res != null) dispatch(new Runnable { - override def run() = - try callback(res) - catch handledFutureException andThen { - t => Console.err.println(t) - } - }) - - this - } - - def isTimedout: Boolean = getState match { - case Failure(ft: FutureTimeoutException) => true - case _ => false - } - -} - -private[concurrent] class PromiseImpl[T](context: ExecutionContextImpl) -extends Promise[T] with Future[T] with Completable[T] { - - val executor: scala.concurrent.default.ExecutionContextImpl = context - - @volatile private var state: State[T] = _ - - val updater = AtomicReferenceFieldUpdater.newUpdater(classOf[PromiseImpl[T]], classOf[State[T]], "state") - - updater.set(this, Pending(List())) - - def casState(oldv: State[T], newv: State[T]): Boolean = { - updater.compareAndSet(this, oldv, newv) - } - - def getState: State[T] = { - updater.get(this) - } - - @tailrec private def tryCompleteState(completed: State[T]): List[Callback] = (getState: @unchecked) match { - case p @ Pending(cbs) => if (!casState(p, completed)) tryCompleteState(completed) else cbs - case _ => null - } - - def tryComplete(r: Try[T]) = r match { - case util.Failure(t) => tryFailure(t) - case util.Success(v) => trySuccess(v) - } - - override def trySuccess(value: T): Boolean = { - val cbs = tryCompleteState(Success(value)) - if (cbs == null) - false - else { - processCallbacks(cbs, util.Success(value)) - this.synchronized { - this.notifyAll() - } - true - } - } - - override def tryFailure(t: Throwable): Boolean = { - val wrapped = wrap(t) - val cbs = tryCompleteState(Failure(wrapped)) - if (cbs == null) - false - else { - processCallbacks(cbs, util.Failure(wrapped)) - this.synchronized { - this.notifyAll() - } - true - } - } - - def await(atMost: Duration)(implicit canawait: scala.concurrent.CanAwait): T = getState match { - case Success(res) => res - case Failure(t) => throw t - case _ => - this.synchronized { - while (true) - getState match { - case Pending(_) => this.wait() - case Success(res) => return res - case Failure(t) => throw t - } - } - sys.error("unreachable") - } - -} - -private[concurrent] class TaskImpl[T](context: ExecutionContextImpl, body: => T) -extends RecursiveAction with Task[T] with Future[T] with Completable[T] { - - val executor: ExecutionContextImpl = context - - @volatile private var state: State[T] = _ - - val updater = AtomicReferenceFieldUpdater.newUpdater(classOf[TaskImpl[T]], classOf[State[T]], "state") - - updater.set(this, Pending(List())) - - def casState(oldv: State[T], newv: State[T]): Boolean = { - updater.compareAndSet(this, oldv, newv) - } - - def getState: State[T] = { - updater.get(this) - } - - @tailrec private def tryCompleteState(completed: State[T]): List[Callback] = (getState: @unchecked) match { - case p @ Pending(cbs) => if (!casState(p, completed)) tryCompleteState(completed) else cbs - } - - def compute(): Unit = { - var cbs: List[Callback] = null - try { - val res = body - processCallbacks(tryCompleteState(Success(res)), util.Success(res)) - } catch { - case t if NonFatal(t) => - processCallbacks(tryCompleteState(Failure(t)), util.Failure(t)) - case t => - val ee = new ExecutionException(t) - processCallbacks(tryCompleteState(Failure(ee)), util.Failure(ee)) - throw t - } - } - - def start(): Unit = { - Thread.currentThread match { - case fj: ForkJoinWorkerThread if fj.getPool eq executor.pool => fork() - case _ => executor.pool.execute(this) - } - } - - // TODO FIXME: handle timeouts - def await(atMost: Duration): this.type = - await - - def await: this.type = { - this.join() - this - } - - def tryCancel(): Unit = - tryUnfork() - - def await(atMost: Duration)(implicit canawait: CanAwait): T = { - join() // TODO handle timeout also - (updater.get(this): @unchecked) match { - case Success(r) => r - case Failure(t) => throw t - } - } - -} - - -private[concurrent] sealed abstract class State[T] - - -case class Pending[T](callbacks: List[Try[T] => Any]) extends State[T] - - -case class Success[T](result: T) extends State[T] - - -case class Failure[T](throwable: Throwable) extends State[T] - - -private[concurrent] final class ExecutionContextImpl extends ExecutionContext { - import ExecutionContextImpl._ - - val pool = { - val p = new ForkJoinPool - p.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler { - def uncaughtException(t: Thread, throwable: Throwable) { - Console.err.println(throwable.getMessage) - throwable.printStackTrace(Console.err) - } - }) - p - } - - @inline - private def executeTask(task: RecursiveAction) { - if (Thread.currentThread.isInstanceOf[ForkJoinWorkerThread]) - task.fork() - else - pool execute task - } - - def execute(task: Runnable) { - val action = new RecursiveAction { def compute() { task.run() } } - executeTask(action) - } - - def execute[U](body: () => U) { - val action = new RecursiveAction { def compute() { body() } } - executeTask(action) - } - - def task[T](body: => T): Task[T] = { - new TaskImpl(this, body) - } - - def future[T](body: => T): Future[T] = { - val t = task(body) - t.start() - t.future - } - - def promise[T]: Promise[T] = - new PromiseImpl[T](this) - - def blocking[T](atMost: Duration)(body: =>T): T = blocking(body2awaitable(body), atMost) - - def blocking[T](awaitable: Awaitable[T], atMost: Duration): T = { - currentExecutionContext.get match { - case null => awaitable.await(atMost)(null) // outside - TODO - fix timeout case - case x if x eq this => this.blockingCall(awaitable) // inside an execution context thread on this executor - case x => x.blocking(awaitable, atMost) - } - } - - private def blockingCall[T](b: Awaitable[T]): T = b match { - case fj: TaskImpl[_] if fj.executor.pool eq pool => - fj.await(Duration.fromNanos(0)) - case _ => - var res: T = null.asInstanceOf[T] - @volatile var blockingDone = false - // TODO add exception handling here! - val mb = new ForkJoinPool.ManagedBlocker { - def block() = { - res = b.await(Duration.fromNanos(0))(CanAwaitEvidence) - blockingDone = true - true - } - def isReleasable = blockingDone - } - ForkJoinPool.managedBlock(mb, true) - res - } - - def reportFailure(t: Throwable): Unit = {} - -} - - -object ExecutionContextImpl { - - private[concurrent] def currentExecutionContext: ThreadLocal[ExecutionContext] = new ThreadLocal[ExecutionContext] { - override protected def initialValue = null - } - -} - - - - - - - diff --git a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala index 98f821652f..875a558887 100644 --- a/src/library/scala/concurrent/impl/ExecutionContextImpl.scala +++ b/src/library/scala/concurrent/impl/ExecutionContextImpl.scala @@ -13,7 +13,7 @@ package scala.concurrent.impl import java.util.concurrent.{ LinkedBlockingQueue, Callable, Executor, ExecutorService, Executors, ThreadFactory, TimeUnit, ThreadPoolExecutor } import java.util.Collection import scala.concurrent.forkjoin._ -import scala.concurrent.{ BlockContext, ExecutionContext, Awaitable, ExecutionContextExecutor, ExecutionContextExecutorService } +import scala.concurrent.{ BlockContext, ExecutionContext, Awaitable, CanAwait, ExecutionContextExecutor, ExecutionContextExecutorService } import scala.concurrent.util.Duration import scala.util.control.NonFatal @@ -37,15 +37,15 @@ private[scala] class ExecutionContextImpl private[impl] (es: Executor, reporter: def newThread(runnable: Runnable): Thread = wire(new Thread(runnable)) def newThread(fjp: ForkJoinPool): ForkJoinWorkerThread = wire(new ForkJoinWorkerThread(fjp) with BlockContext { - override def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = { + override def blockOn[T](thunk: =>T)(implicit permission: CanAwait): T = { var result: T = null.asInstanceOf[T] ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker { @volatile var isdone = false - def block(): Boolean = { - result = try awaitable.result(atMost)(scala.concurrent.Await.canAwaitEvidence) finally { isdone = true } + override def block(): Boolean = { + result = try thunk finally { isdone = true } true } - def isReleasable = isdone + override def isReleasable = isdone }) result } diff --git a/src/library/scala/concurrent/impl/Future.scala b/src/library/scala/concurrent/impl/Future.scala index 132e1d79e7..b32824c0c9 100644 --- a/src/library/scala/concurrent/impl/Future.scala +++ b/src/library/scala/concurrent/impl/Future.scala @@ -22,15 +22,6 @@ private[concurrent] trait Future[+T] extends scala.concurrent.Future[T] with Awa private[concurrent] object Future { - /** Wraps a block of code into an awaitable object. */ - private[concurrent] def body2awaitable[T](body: =>T) = new Awaitable[T] { - def ready(atMost: Duration)(implicit permit: CanAwait) = { - body - this - } - def result(atMost: Duration)(implicit permit: CanAwait) = body - } - def boxedType(c: Class[_]): Class[_] = if (c.isPrimitive) scala.concurrent.Future.toBoxed(c) else c private[impl] class PromiseCompletingRunnable[T](body: => T) diff --git a/src/library/scala/concurrent/package.scala b/src/library/scala/concurrent/package.scala index 76703bf081..a6488b602f 100644 --- a/src/library/scala/concurrent/package.scala +++ b/src/library/scala/concurrent/package.scala @@ -9,6 +9,7 @@ package scala import scala.concurrent.util.Duration +import scala.annotation.implicitNotFound /** This package object contains primitives for concurrent and parallel programming. */ @@ -17,6 +18,41 @@ package object concurrent { type CancellationException = java.util.concurrent.CancellationException type TimeoutException = java.util.concurrent.TimeoutException + @implicitNotFound("Don't call `Awaitable` methods directly, use the `Await` object.") + sealed trait CanAwait + private implicit object AwaitPermission extends CanAwait + + /** + * `Await` is what is used to ensure proper handling of blocking for `Awaitable` instances. + */ + object Await { + /** + * Invokes ready() on the awaitable, properly wrapped by a call to `scala.concurrent.blocking`. + * ready() blocks until the awaitable has completed or the timeout expires. + * + * Throws a TimeoutException if the timeout expires, as that is in the contract of `Awaitable.ready`. + * @param awaitable the `Awaitable` on which `ready` is to be called + * @param atMost the maximum timeout for which to wait + * @return the result of `awaitable.ready` which is defined to be the awaitable itself. + */ + @throws(classOf[TimeoutException]) + def ready[T](awaitable: Awaitable[T], atMost: Duration): awaitable.type = + blocking(awaitable.ready(atMost)) + + /** + * Invokes result() on the awaitable, properly wrapped by a call to `scala.concurrent.blocking`. + * result() blocks until the awaitable has completed or the timeout expires. + * + * Throws a TimeoutException if the timeout expires, or any exception thrown by `Awaitable.result`. + * @param awaitable the `Awaitable` on which `result` is to be called + * @param atMost the maximum timeout for which to wait + * @return the result of `awaitable.result` + */ + @throws(classOf[Exception]) + def result[T](awaitable: Awaitable[T], atMost: Duration): T = + blocking(awaitable.result(atMost)) + } + /** Starts an asynchronous computation and returns a `Future` object with the result of that computation. * * The result becomes available once the asynchronous computation is completed. @@ -36,46 +72,18 @@ package object concurrent { */ def promise[T]()(implicit execctx: ExecutionContext): Promise[T] = Promise[T]() - /** Used to block on a piece of code which potentially blocks. + /** Used to designate a piece of code which potentially blocks, allowing the BlockContext to adjust the runtime's behavior. + * Properly marking blocking code may improve performance or avoid deadlocks. * - * @param body A piece of code which contains potentially blocking or long running calls. - * - * Calling this method may throw the following exceptions: - * - CancellationException - if the computation was cancelled - * - InterruptedException - in the case that a wait within the blockable object was interrupted - * - TimeoutException - in the case that the blockable object timed out - */ - def blocking[T](body: =>T): T = blocking(impl.Future.body2awaitable(body), Duration.Inf) - - /** Blocks on an awaitable object. + * If you have an `Awaitable` then you should use Await.result instead of `blocking`. * - * @param awaitable An object with a `block` method which runs potentially blocking or long running calls. + * @param body A piece of code which contains potentially blocking or long running calls. * * Calling this method may throw the following exceptions: * - CancellationException - if the computation was cancelled * - InterruptedException - in the case that a wait within the blockable object was interrupted * - TimeoutException - in the case that the blockable object timed out */ - def blocking[T](awaitable: Awaitable[T], atMost: Duration): T = - BlockContext.current.internalBlockingCall(awaitable, atMost) -} - -/* concurrency constructs */ -package concurrent { - - sealed trait CanAwait - - object Await { - private[concurrent] implicit val canAwaitEvidence = new CanAwait {} - - def ready[T](awaitable: Awaitable[T], atMost: Duration): awaitable.type = { - blocking(awaitable, atMost) - awaitable - } - - def result[T](awaitable: Awaitable[T], atMost: Duration): T = { - blocking(awaitable, atMost) - } - - } + @throws(classOf[Exception]) + def blocking[T](body: =>T): T = BlockContext.current.blockOn(body) } diff --git a/test/files/jvm/future-spec/FutureTests.scala b/test/files/jvm/future-spec/FutureTests.scala index ca9ff5090f..30e1a722bf 100644 --- a/test/files/jvm/future-spec/FutureTests.scala +++ b/test/files/jvm/future-spec/FutureTests.scala @@ -507,6 +507,12 @@ object FutureTests extends MinimalScalaTest { } Await.ready(complex, defaultTimeout).isCompleted mustBe (true) } + + "should not throw when Await.ready" in { + val expected = try Right(5 / 0) catch { case a: ArithmeticException => Left(a) } + val f = future(5).map(_ / 0) + Await.ready(f, defaultTimeout).value.get.toString mustBe expected.toString + } } diff --git a/test/files/jvm/future-spec/PromiseTests.scala b/test/files/jvm/future-spec/PromiseTests.scala index 49bc642b57..d15bb31f36 100644 --- a/test/files/jvm/future-spec/PromiseTests.scala +++ b/test/files/jvm/future-spec/PromiseTests.scala @@ -78,7 +78,7 @@ object PromiseTests extends MinimalScalaTest { "contain a value" in { f((future, result) => future.value mustBe (Some(Right(result)))) } - "return result with 'blocking'" in { f((future, result) => blocking(future, defaultTimeout) mustBe (result)) } + "return when ready with 'Await.ready'" in { f((future, result) => Await.ready(future, defaultTimeout).isCompleted mustBe (true)) } "return result with 'Await.result'" in { f((future, result) => Await.result(future, defaultTimeout) mustBe (result)) } @@ -163,12 +163,9 @@ object PromiseTests extends MinimalScalaTest { }) } - "throw exception with 'blocking'" in { + "throw not throw exception with 'Await.ready'" in { f { - (future, message) => - intercept[E] { - blocking(future, defaultTimeout) - }.getMessage mustBe (message) + (future, message) => Await.ready(future, defaultTimeout).isCompleted mustBe (true) } } diff --git a/test/files/jvm/scala-concurrent-tck.scala b/test/files/jvm/scala-concurrent-tck.scala index 5c9c71f3f8..1209b710b0 100644 --- a/test/files/jvm/scala-concurrent-tck.scala +++ b/test/files/jvm/scala-concurrent-tck.scala @@ -4,7 +4,9 @@ import scala.concurrent.{ TimeoutException, SyncVar, ExecutionException, - ExecutionContext + ExecutionContext, + CanAwait, + Await } import scala.concurrent.{ future, promise, blocking } import scala.util.{ Try, Success, Failure } @@ -647,7 +649,7 @@ trait FutureProjections extends TestBase { val f = future { throw cause } - assert(blocking(f.failed, Duration(500, "ms")) == cause) + assert(Await.result(f.failed, Duration(500, "ms")) == cause) done() } @@ -655,7 +657,7 @@ trait FutureProjections extends TestBase { done => val f = future { 0 } try { - blocking(f.failed, Duration(500, "ms")) + Await.result(f.failed, Duration(500, "ms")) assert(false) } catch { case nsee: NoSuchElementException => done() @@ -678,7 +680,7 @@ trait Blocking extends TestBase { def testAwaitSuccess(): Unit = once { done => val f = future { 0 } - blocking(f, Duration(500, "ms")) + Await.result(f, Duration(500, "ms")) done() } @@ -689,7 +691,7 @@ trait Blocking extends TestBase { throw cause } try { - blocking(f, Duration(500, "ms")) + Await.result(f, Duration(500, "ms")) assert(false) } catch { case t => @@ -708,7 +710,7 @@ trait BlockContexts extends TestBase { import scala.concurrent.{ Await, Awaitable, BlockContext } private def getBlockContext(body: => BlockContext): BlockContext = { - blocking(Future { body }, Duration(500, "ms")) + Await.result(Future { body }, Duration(500, "ms")) } // test outside of an ExecutionContext @@ -727,8 +729,7 @@ trait BlockContexts extends TestBase { def testPushCustom(): Unit = { val orig = BlockContext.current val customBC = new BlockContext() { - override def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = - orig.internalBlockingCall(awaitable, atMost) + override def blockOn[T](thunk: =>T)(implicit permission: CanAwait): T = orig.blockOn(thunk) } val bc = getBlockContext({ @@ -744,8 +745,7 @@ trait BlockContexts extends TestBase { def testPopCustom(): Unit = { val orig = BlockContext.current val customBC = new BlockContext() { - override def internalBlockingCall[T](awaitable: Awaitable[T], atMost: Duration): T = - orig.internalBlockingCall(awaitable, atMost) + override def blockOn[T](thunk: =>T)(implicit permission: CanAwait): T = orig.blockOn(thunk) } val bc = getBlockContext({ -- cgit v1.2.3 From a6aaee845174c3102d4602d16dab9fd673bf4f77 Mon Sep 17 00:00:00 2001 From: phaller Date: Thu, 19 Jul 2012 16:53:21 +0200 Subject: Clean ups in impl.Future --- src/library/scala/concurrent/impl/Future.scala | 15 +++------------ src/library/scala/concurrent/impl/Promise.scala | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/library/scala/concurrent/impl/Future.scala b/src/library/scala/concurrent/impl/Future.scala index b32824c0c9..098008e958 100644 --- a/src/library/scala/concurrent/impl/Future.scala +++ b/src/library/scala/concurrent/impl/Future.scala @@ -10,22 +10,13 @@ package scala.concurrent.impl -import scala.concurrent.util.Duration -import scala.concurrent.{Awaitable, ExecutionContext, CanAwait} -import scala.collection.mutable.Stack +import scala.concurrent.ExecutionContext import scala.util.control.NonFatal -private[concurrent] trait Future[+T] extends scala.concurrent.Future[T] with Awaitable[T] { - -} private[concurrent] object Future { - - def boxedType(c: Class[_]): Class[_] = if (c.isPrimitive) scala.concurrent.Future.toBoxed(c) else c - - private[impl] class PromiseCompletingRunnable[T](body: => T) - extends Runnable { + class PromiseCompletingRunnable[T](body: => T) extends Runnable { val promise = new Promise.DefaultPromise[T]() override def run() = { @@ -35,7 +26,7 @@ private[concurrent] object Future { } } - def apply[T](body: =>T)(implicit executor: ExecutionContext): Future[T] = { + def apply[T](body: =>T)(implicit executor: ExecutionContext): scala.concurrent.Future[T] = { val runnable = new PromiseCompletingRunnable(body) executor.execute(runnable) runnable.promise.future diff --git a/src/library/scala/concurrent/impl/Promise.scala b/src/library/scala/concurrent/impl/Promise.scala index 84638586cf..c2df9ac296 100644 --- a/src/library/scala/concurrent/impl/Promise.scala +++ b/src/library/scala/concurrent/impl/Promise.scala @@ -18,7 +18,7 @@ import scala.util.control.NonFatal -private[concurrent] trait Promise[T] extends scala.concurrent.Promise[T] with Future[T] { +private[concurrent] trait Promise[T] extends scala.concurrent.Promise[T] with scala.concurrent.Future[T] { def future: this.type = this } -- cgit v1.2.3 From 444bba68cc59e032c7f57087979d0b757ab3e94f Mon Sep 17 00:00:00 2001 From: Grzegorz Kossakowski Date: Thu, 19 Jul 2012 17:17:24 +0200 Subject: Switch to 1.6 target for all javac invocations. Switch to 1.6 target for all javac invocations we perform in build.xml. This way we do not emit java 1.5 byte-code either from scalac or javac. This commit should complete the move off java 1.5 byte-code. --- build.xml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/build.xml b/build.xml index 8fa1b9cd76..f5576b8d1d 100644 --- a/build.xml +++ b/build.xml @@ -499,7 +499,7 @@ LOCAL DEPENDENCY (Adapted ASM) destdir="${build-asm.dir}/classes" classpath="${build-asm.dir}/classes" includes="**/*.java" - target="1.5" source="1.5"> + target="1.6" source="1.5"> @@ -540,7 +540,7 @@ LOCAL DEPENDENCY (FORKJOIN) classpath="${build-libs.dir}/classes/forkjoin" includes="**/*.java" debug="true" - target="1.5" source="1.5"> + target="1.6" source="1.5"> @@ -587,7 +587,7 @@ LOCAL DEPENDENCY (FJBG) classpath="${build-libs.dir}/classes/fjbg" includes="**/*.java" debug="true" - target="1.5" source="1.4"> + target="1.6" source="1.4"> @@ -635,7 +635,7 @@ LOCAL REFERENCE BUILD (LOCKER) srcdir="${src.dir}/library" destdir="${build-locker.dir}/classes/library" includes="**/*.java" - target="1.5" source="1.5"> + target="1.6" source="1.5"> @@ -749,7 +749,7 @@ LOCAL REFERENCE BUILD (LOCKER) includes="**/*.java" excludes="**/tests/**" debug="true" - target="1.5" source="1.4"> + target="1.6" source="1.4"> + target="1.6" source="1.5"> @@ -1020,7 +1020,7 @@ QUICK BUILD (QUICK) srcdir="${src.dir}/actors" destdir="${build-quick.dir}/classes/library" includes="**/*.java" - target="1.5" source="1.5"> + target="1.6" source="1.5"> @@ -1145,7 +1145,7 @@ QUICK BUILD (QUICK) includes="**/*.java" excludes="**/tests/**" debug="true" - target="1.5" source="1.4"> + target="1.6" source="1.4"> + target="1.6" source="1.5"> @@ -1691,7 +1691,7 @@ BOOTSTRAPPING BUILD (STRAP) srcdir="${src.dir}/library" destdir="${build-strap.dir}/classes/library" includes="**/*.java" - target="1.5" source="1.5"> + target="1.6" source="1.5"> @@ -1702,7 +1702,7 @@ BOOTSTRAPPING BUILD (STRAP) srcdir="${src.dir}/actors" destdir="${build-strap.dir}/classes/library" includes="**/*.java" - target="1.5" source="1.5"> + target="1.6" source="1.5"> @@ -1826,7 +1826,7 @@ BOOTSTRAPPING BUILD (STRAP) includes="**/*.java" excludes="**/tests/**" debug="true" - target="1.5" source="1.4"> + target="1.6" source="1.4"> + target="1.6" source="1.5"> -- cgit v1.2.3 From 4fc9cdb64f577e0561e814377ea0f9747245bbbe Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Thu, 19 Jul 2012 19:21:32 +0200 Subject: SI-4897 derive expected value from single type when the type in a type test is, say, `C.this.A.type`, must use the corresponding term `C.this.A` to test for equality if you use the naive REF(.symbol), you'll get a path like `OwnerOfA.this.A`, where `OwnerOfA` might be a superclass of `C`, and explicitouter won't like that --- src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala | 2 +- test/files/run/t4897.check | 1 + test/files/run/t4897.scala | 10 ++++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 test/files/run/t4897.check create mode 100644 test/files/run/t4897.scala diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala index 4e4176e531..eef2c5fd3c 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala @@ -1024,7 +1024,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL else expectedTp match { // TODO: [SPEC] the spec requires `eq` instead of `==` for singleton types // this implies sym.isStable - case SingleType(_, sym) => and(equalsTest(CODE.REF(sym), testedBinder), typeTest(testedBinder, expectedTp.widen)) + case SingleType(_, sym) => and(equalsTest(gen.mkAttributedQualifier(expectedTp), testedBinder), typeTest(testedBinder, expectedTp.widen)) // must use == to support e.g. List() == Nil case ThisType(sym) if sym.isModule => and(equalsTest(CODE.REF(sym), testedBinder), typeTest(testedBinder, expectedTp.widen)) case ConstantType(Constant(null)) if testedBinder.info.widen <:< AnyRefClass.tpe diff --git a/test/files/run/t4897.check b/test/files/run/t4897.check new file mode 100644 index 0000000000..17dda56fe1 --- /dev/null +++ b/test/files/run/t4897.check @@ -0,0 +1 @@ +joepie diff --git a/test/files/run/t4897.scala b/test/files/run/t4897.scala new file mode 100644 index 0000000000..a2ec3de37f --- /dev/null +++ b/test/files/run/t4897.scala @@ -0,0 +1,10 @@ +class CSuper { + object A +} +class C extends CSuper { + def f = (A: AnyRef) match { case _: A.type => "joepie" } +} + +object Test extends C with App { + println(f) +} \ No newline at end of file -- cgit v1.2.3 From 911bbc4fdd889ea8a880b4ae47a490f64c54a2a9 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 17 Jul 2012 17:54:41 +0200 Subject: SI-5984 improves error reporting in JavaMirrors Factors out error raising code and introduces a special exception class for Scala reflection errors. Also adds membership sanity checks to reflectXXX. Previously reflectField, reflectMethod, reflectClass and reflectModule in InstanceMirror didn't check that the symbols being passed to them actually correspond to some member of the related class. --- src/library/scala/reflect/package.scala | 3 ++ .../scala/reflect/runtime/JavaMirrors.scala | 45 ++++++++++++++-------- ...eflection-constructormirror-inner-badpath.check | 2 +- ...flection-constructormirror-nested-badpath.check | 2 +- ...ection-constructormirror-toplevel-badpath.check | 2 +- .../run/reflection-fieldmirror-ctorparam.check | 2 +- .../run/reflection-fieldmirror-getsetval.check | 2 +- .../reflection-modulemirror-inner-badpath.check | 4 +- .../reflection-modulemirror-nested-badpath.check | 4 +- .../reflection-modulemirror-toplevel-badpath.check | 4 +- test/files/run/reflection-sanitychecks.check | 8 ++++ test/files/run/reflection-sanitychecks.scala | 30 +++++++++++++++ 12 files changed, 80 insertions(+), 28 deletions(-) create mode 100644 test/files/run/reflection-sanitychecks.check create mode 100644 test/files/run/reflection-sanitychecks.scala diff --git a/src/library/scala/reflect/package.scala b/src/library/scala/reflect/package.scala index 9f9d4089c4..d97f2ec633 100644 --- a/src/library/scala/reflect/package.scala +++ b/src/library/scala/reflect/package.scala @@ -65,3 +65,6 @@ package object reflect { @deprecated("Use `@scala.beans.ScalaBeanInfo` instead", "2.10.0") type ScalaBeanInfo = scala.beans.ScalaBeanInfo } + +/** An exception that indicates an error during Scala reflection */ +case class ScalaReflectionException(msg: String) extends Exception(msg) diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 1c5ea9caba..e7bebd624c 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -107,15 +107,28 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym // ----------- Implementations of mirror operations and classes ------------------- + private def ErrorInnerClass(wannabe: Symbol) = throw new ScalaReflectionException(s"$wannabe is an inner class, use reflectClass on an InstanceMirror to obtain its ClassMirror") + private def ErrorInnerModule(wannabe: Symbol) = throw new ScalaReflectionException(s"$wannabe is an inner module, use reflectModule on an InstanceMirror to obtain its ModuleMirror") + private def ErrorStaticClass(wannabe: Symbol) = throw new ScalaReflectionException(s"$wannabe is a static class, use reflectClass on a RuntimeMirror to obtain its ClassMirror") + private def ErrorStaticModule(wannabe: Symbol) = throw new ScalaReflectionException(s"$wannabe is a static module, use reflectModule on a RuntimeMirror to obtain its ModuleMirror") + private def ErrorNotMember(wannabe: Symbol, owner: Symbol) = throw new ScalaReflectionException(s"expected a member of $owner, you provided ${wannabe.kind} ${wannabe.fullName}") + private def ErrorNotField(wannabe: Symbol) = throw new ScalaReflectionException(s"expected a field or an accessor method symbol, you provided $wannabe}") + private def ErrorNonExistentField(wannabe: Symbol) = throw new ScalaReflectionException(s""" + |Scala field ${wannabe.name} isn't represented as a Java field, neither it has a Java accessor method + |note that private parameters of class constructors don't get mapped onto fields and/or accessors, + |unless they are used outside of their declaring constructors. + """.trim.stripMargin) + private def ErrorSetImmutableField(wannabe: Symbol) = throw new ScalaReflectionException(s"cannot set an immutable field ${wannabe.name}") + def reflect(obj: Any): InstanceMirror = new JavaInstanceMirror(obj.asInstanceOf[AnyRef]) def reflectClass(cls: ClassSymbol): ClassMirror = { - if (!cls.isStatic) throw new Error("this is an inner class, use reflectClass on an InstanceMirror to obtain its ClassMirror") + if (!cls.isStatic) ErrorInnerClass(cls) new JavaClassMirror(null, cls) } def reflectModule(mod: ModuleSymbol): ModuleMirror = { - if (!mod.isStatic) throw new Error("this is an inner module, use reflectModule on an InstanceMirror to obtain its ModuleMirror") + if (!mod.isStatic) ErrorInnerModule(mod) new JavaModuleMirror(null, mod) } @@ -127,13 +140,16 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym def moduleSymbol(rtcls: RuntimeClass): ModuleSymbol = classToScala(rtcls).companionModule.asModuleSymbol + private def checkMemberOf(wannabe: Symbol, owner: Symbol) = + if (!owner.info.member(wannabe.name).alternatives.contains(wannabe)) ErrorNotMember(wannabe, owner) + private class JavaInstanceMirror(obj: AnyRef) extends InstanceMirror { def instance = obj def symbol = wholemirror.classSymbol(obj.getClass) def reflectField(field: TermSymbol): FieldMirror = { - // [Eugene+++] check whether `field` represents a member of a `symbol` - if ((field.isMethod && !field.isAccessor) || field.isModule) throw new Error(s"expected a field or accessor method symbol, you provided a ${field.kind} symbol") + checkMemberOf(field, symbol) + if ((field.isMethod && !field.isAccessor) || field.isModule) ErrorNotField(field) val name = if (field.isGetter) nme.getterToLocal(field.name) else if (field.isSetter) nme.getterToLocal(nme.setterToGetter(field.name)) @@ -141,27 +157,22 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym val field1 = (field.owner.info decl name).asTermSymbol try fieldToJava(field1) catch { - case _: NoSuchFieldException => - throw new Error(s""" - |this Scala field isn't represented as a Java field, neither it has a Java accessor method - |note that private parameters of class constructors don't get mapped onto fields and/or accessors, - |unless they are used outside of their declaring constructors. - """.trim.stripMargin) + case _: NoSuchFieldException => ErrorNonExistentField(field1) } new JavaFieldMirror(obj, field1) } def reflectMethod(method: MethodSymbol): MethodMirror = { - // [Eugene+++] check whether `method` represents a member of a `symbol` + checkMemberOf(method, symbol) new JavaMethodMirror(obj, method) } def reflectClass(cls: ClassSymbol): ClassMirror = { - // [Eugene+++] check whether `cls` represents a member of a `symbol` - if (cls.isStatic) throw new Error("this is a static class, use reflectClass on a RuntimeMirror to obtain its ClassMirror") + if (cls.isStatic) ErrorStaticClass(cls) + checkMemberOf(cls, symbol) new JavaClassMirror(instance, cls) } def reflectModule(mod: ModuleSymbol): ModuleMirror = { - // [Eugene+++] check whether `mod` represents a member of a `symbol` - if (mod.isStatic) throw new Error("this is a static module, use reflectModule on a RuntimeMirror to obtain its ModuleMirror") + if (mod.isStatic) ErrorStaticModule(mod) + checkMemberOf(mod, symbol) new JavaModuleMirror(instance, mod) } } @@ -175,7 +186,7 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym } def get = jfield.get(receiver) def set(value: Any) = { - if (!symbol.isMutable) throw new Error("cannot set an immutable field") + if (!symbol.isMutable) ErrorSetImmutableField(symbol) jfield.set(receiver, value) } } @@ -193,7 +204,7 @@ trait JavaMirrors extends internal.SymbolTable with api.JavaUniverse { self: Sym case nme.length => jArray.getLength(receiver) case nme.apply => jArray.get(receiver, args(0).asInstanceOf[Int]) case nme.update => jArray.set(receiver, args(0).asInstanceOf[Int], args(1)) - case _ => throw new Error(s"unexpected array method $symbol") + case _ => assert(false, s"unexpected array method: $symbol") } else jmeth.invoke(receiver, args.asInstanceOf[Seq[AnyRef]]: _*) diff --git a/test/files/run/reflection-constructormirror-inner-badpath.check b/test/files/run/reflection-constructormirror-inner-badpath.check index 28b936eca1..2fb0610ad6 100644 --- a/test/files/run/reflection-constructormirror-inner-badpath.check +++ b/test/files/run/reflection-constructormirror-inner-badpath.check @@ -1,2 +1,2 @@ -this is an inner class, use reflectClass on an InstanceMirror to obtain its ClassMirror +class R is an inner class, use reflectClass on an InstanceMirror to obtain its ClassMirror () diff --git a/test/files/run/reflection-constructormirror-nested-badpath.check b/test/files/run/reflection-constructormirror-nested-badpath.check index 9ceb603dc2..acd21df9c0 100644 --- a/test/files/run/reflection-constructormirror-nested-badpath.check +++ b/test/files/run/reflection-constructormirror-nested-badpath.check @@ -1,2 +1,2 @@ -this is a static class, use reflectClass on a RuntimeMirror to obtain its ClassMirror +class R is a static class, use reflectClass on a RuntimeMirror to obtain its ClassMirror () diff --git a/test/files/run/reflection-constructormirror-toplevel-badpath.check b/test/files/run/reflection-constructormirror-toplevel-badpath.check index 9ceb603dc2..acd21df9c0 100644 --- a/test/files/run/reflection-constructormirror-toplevel-badpath.check +++ b/test/files/run/reflection-constructormirror-toplevel-badpath.check @@ -1,2 +1,2 @@ -this is a static class, use reflectClass on a RuntimeMirror to obtain its ClassMirror +class R is a static class, use reflectClass on a RuntimeMirror to obtain its ClassMirror () diff --git a/test/files/run/reflection-fieldmirror-ctorparam.check b/test/files/run/reflection-fieldmirror-ctorparam.check index 7ae2cec81e..31f6491b14 100644 --- a/test/files/run/reflection-fieldmirror-ctorparam.check +++ b/test/files/run/reflection-fieldmirror-ctorparam.check @@ -1,3 +1,3 @@ -class java.lang.Error: this Scala field isn't represented as a Java field, neither it has a Java accessor method +class scala.ScalaReflectionException: Scala field x isn't represented as a Java field, neither it has a Java accessor method note that private parameters of class constructors don't get mapped onto fields and/or accessors, unless they are used outside of their declaring constructors. diff --git a/test/files/run/reflection-fieldmirror-getsetval.check b/test/files/run/reflection-fieldmirror-getsetval.check index 707bbcccce..e1927f68d0 100644 --- a/test/files/run/reflection-fieldmirror-getsetval.check +++ b/test/files/run/reflection-fieldmirror-getsetval.check @@ -1,2 +1,2 @@ 42 -cannot set an immutable field +cannot set an immutable field x diff --git a/test/files/run/reflection-modulemirror-inner-badpath.check b/test/files/run/reflection-modulemirror-inner-badpath.check index d3fe43336e..1e990ec900 100644 --- a/test/files/run/reflection-modulemirror-inner-badpath.check +++ b/test/files/run/reflection-modulemirror-inner-badpath.check @@ -1,2 +1,2 @@ -this is an inner module, use reflectModule on an InstanceMirror to obtain its ModuleMirror -() +object R is an inner module, use reflectModule on an InstanceMirror to obtain its ModuleMirror +() diff --git a/test/files/run/reflection-modulemirror-nested-badpath.check b/test/files/run/reflection-modulemirror-nested-badpath.check index 16a5b10390..f7980b9986 100644 --- a/test/files/run/reflection-modulemirror-nested-badpath.check +++ b/test/files/run/reflection-modulemirror-nested-badpath.check @@ -1,2 +1,2 @@ -this is a static module, use reflectModule on a RuntimeMirror to obtain its ModuleMirror -() +object R is a static module, use reflectModule on a RuntimeMirror to obtain its ModuleMirror +() diff --git a/test/files/run/reflection-modulemirror-toplevel-badpath.check b/test/files/run/reflection-modulemirror-toplevel-badpath.check index 16a5b10390..f7980b9986 100644 --- a/test/files/run/reflection-modulemirror-toplevel-badpath.check +++ b/test/files/run/reflection-modulemirror-toplevel-badpath.check @@ -1,2 +1,2 @@ -this is a static module, use reflectModule on a RuntimeMirror to obtain its ModuleMirror -() +object R is a static module, use reflectModule on a RuntimeMirror to obtain its ModuleMirror +() diff --git a/test/files/run/reflection-sanitychecks.check b/test/files/run/reflection-sanitychecks.check new file mode 100644 index 0000000000..d977e0ed66 --- /dev/null +++ b/test/files/run/reflection-sanitychecks.check @@ -0,0 +1,8 @@ +field: 1 +method: 2 +class: CC +object: java.lang.Error: inner and nested modules are not supported yet +field: scala.ScalaReflectionException: expected a member of class C, you provided value D.foo +method: scala.ScalaReflectionException: expected a member of class C, you provided method D.bar +class: scala.ScalaReflectionException: expected a member of class C, you provided class D.C +object: scala.ScalaReflectionException: expected a member of class C, you provided object D.O diff --git a/test/files/run/reflection-sanitychecks.scala b/test/files/run/reflection-sanitychecks.scala new file mode 100644 index 0000000000..a6a24088a4 --- /dev/null +++ b/test/files/run/reflection-sanitychecks.scala @@ -0,0 +1,30 @@ +class C { + val foo = 1 + def bar = 2 + class C { override def toString = "CC" } + object O { override def toString = "CO" } +} + +class D { + val foo = 3 + def bar = 4 + class C { override def toString = "DC" } + object O { override def toString = "DO" } +} + +object Test extends App { + import scala.reflect.runtime.universe._ + import scala.reflect.runtime.{currentMirror => cm} + val im = cm.reflect(new C) + + def test(tpe: Type): Unit = { + def failsafe(action: => Any): Any = try action catch { case ex: Throwable => ex.toString } + println("field: " + failsafe(im.reflectField(tpe.member(newTermName("foo")).asTermSymbol).get)) + println("method: " + failsafe(im.reflectMethod(tpe.member(newTermName("bar")).asMethodSymbol)())) + println("class: " + failsafe(im.reflectClass(tpe.member(newTypeName("C")).asClassSymbol).reflectConstructor(typeOf[C].member(newTypeName("C")).asClassSymbol.typeSignature.member(newTermName("")).asMethodSymbol)())) + println("object: " + failsafe(im.reflectModule(tpe.member(newTermName("O")).asModuleSymbol).instance)) + } + + test(typeOf[C]) + test(typeOf[D]) +} \ No newline at end of file -- cgit v1.2.3 From baf3d1a2516e69660cd9a3d6ea3120327885fe93 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 17 Jul 2012 16:04:18 +0200 Subject: improves docs of scala.reflect.api.Mirrors --- src/reflect/scala/reflect/api/Mirrors.scala | 144 +++++++++++++++++++++------- 1 file changed, 110 insertions(+), 34 deletions(-) diff --git a/src/reflect/scala/reflect/api/Mirrors.scala b/src/reflect/scala/reflect/api/Mirrors.scala index 348ab3656b..27176a2a2d 100644 --- a/src/reflect/scala/reflect/api/Mirrors.scala +++ b/src/reflect/scala/reflect/api/Mirrors.scala @@ -27,28 +27,63 @@ trait Mirrors { self: Universe => /** The instance value reflected by this mirror */ def instance: Any - /** The symbol corresponding to the run-time class of the reflected instance. */ + /** The symbol corresponding to the run-time class of the reflected instance */ def symbol: ClassSymbol - /** Get value of field in reflected instance. - * @field A field symbol that should represent a field of the instance class. - * @return The value associated with that field in the reflected instance - * @throws ??? + /** Reflects against a field symbol and returns a mirror + * that can be used to get and, if appropriate, set the value of the field. + * + * To get a field symbol by the name of the field you would like to reflect, + * use `.symbol.typeSignature.member(newTermName()).asTermSymbol`. + * For further information about member lookup refer to `Symbol.typeSignature`. + * + * The input symbol can be either private or non-private (Scala reflection transparently deals with visibility). + * It must be a member (declared or inherited) of the class of the instance underlying this mirror. + * + * The input symbol can represent either a field itself or one of the corresponding accessors + * (in all cases the resulting mirror will refer to the field symbol). + * + * If a field symbol doesn't correspond to a reflectable entity of the underlying platform, + * a `ScalaReflectionException` exception will be thrown. This might happen, for example, for primary constructor parameters. + * Typically they produce class fields, however, private parameters that aren't used outside the constructor + * remain plain parameters of a constructor method of the class. */ def reflectField(field: TermSymbol): FieldMirror - /** Invokes a method on the reflected instance. - * @param meth A method symbol that should represent a method of the instance class - * @param args The arguments to pass to the method - * @return The result of invoking `meth(args)` on the reflected instance. - * @throws ??? + /** Reflects against a method symbol and returns a mirror + * that can be used to invoke the method provided. + * + * To get a method symbol by the name of the method you would like to reflect, + * use `.symbol.typeSignature.member(newTermName()).asMethodSymbol`. + * For further information about member lookup refer to `Symbol.typeSignature`. + * + * The input symbol can be either private or non-private (Scala reflection transparently deals with visibility). + * It must be a member (declared or inherited) of the instance underlying this mirror. */ def reflectMethod(method: MethodSymbol): MethodMirror - /** .. */ + /** Reflects against an inner class symbol and returns a mirror + * that can be used to create instances of the class, inspect its companion object or perform further reflections. + * + * To get a class symbol by the name of the class you would like to reflect, + * use `.symbol.typeSignature.member(newTypeName()).asClassSymbol`. + * For further information about member lookup refer to `Symbol.typeSignature`. + * + * The input symbol can be either private or non-private (Scala reflection transparently deals with visibility). + * It must be a member (declared or inherited) of the instance underlying this mirror. + */ def reflectClass(cls: ClassSymbol): ClassMirror - /** .. */ + /** Reflects against an inner module symbol and returns a mirror + * that can be used to get the instance of the object or inspect its companion class. + * + * To get a module symbol by the name of the object you would like to reflect, + * use `.symbol.typeSignature.member(newTermName()).asModuleSymbol`. + * For further information about member lookup refer to `Symbol.typeSignature`. + * + * The input symbol can be either private or non-private (Scala reflection transparently deals with visibility). + * It must be a member (declared or inherited) of the instance underlying this mirror. + */ def reflectModule(mod: ModuleSymbol): ModuleMirror } @@ -58,13 +93,31 @@ trait Mirrors { self: Universe => /** The object containing the field */ def receiver: AnyRef - /** The field symbol representing the field */ + /** The field symbol representing the field. + * + * In Scala `val` and `var` declarations are usually compiled down to a pair of + * a backing field and corresponding accessor/accessors, which means that a single + * declaration might correspond to up to three different symbols. Nevertheless + * the `FieldMirror.symbol` field always points to a backing field symbol. + */ def symbol: TermSymbol - /** Retrieves the value stored in the field */ + /** Retrieves the value stored in the field. + * + * Scala reflection uses reflection capabilities of the underlying platform, + * so `FieldMirror.get` might throw platform-specific exceptions associated + * with getting a field or invoking a getter method of the field. + */ def get: Any - /** Updates the value stored in the field */ + /** Updates the value stored in the field. + * + * If a field is immutable, a `ScalaReflectionException` will be thrown. + * + * Scala reflection uses reflection capabilities of the underlying platform, + * so `FieldMirror.get` might throw platform-specific exceptions associated + * with setting a field or invoking a setter method of the field. + */ def set(value: Any): Unit } @@ -77,8 +130,12 @@ trait Mirrors { self: Universe => /** The method symbol representing the method */ def symbol: MethodSymbol - /** The result of applying the method to the given arguments */ - // [Eugene+++] If it's a constructor, it should account for inner classes + /** The result of applying the method to the given arguments + * + * Scala reflection uses reflection capabilities of the underlying platform, + * so `FieldMirror.get` might throw platform-specific exceptions associated + * with invoking the corresponding method or constructor. + */ def apply(args: Any*): Any } @@ -97,7 +154,7 @@ trait Mirrors { self: Universe => */ def isStatic: Boolean - /** The Scala symbol corresponding to the reflected runtime class or module. */ + /** The Scala symbol corresponding to the reflected runtime class or object */ def symbol: Symbol /** Optionally, the mirror of the companion reflected by this mirror. @@ -116,7 +173,7 @@ trait Mirrors { self: Universe => /** A mirror that reflects a Scala object definition or the static parts of a runtime class */ trait ModuleMirror extends TemplateMirror { - /** The Scala module symbol corresponding to the reflected module. */ + /** The Scala module symbol corresponding to the reflected object */ override def symbol: ModuleSymbol /** If the reflected runtime class corresponds to a Scala object definition, @@ -137,15 +194,18 @@ trait Mirrors { self: Universe => /** A mirror that reflects the instance parts of a runtime class */ trait ClassMirror extends TemplateMirror { - /** The Scala class symbol corresponding to the reflected class. */ + /** The Scala class symbol corresponding to the reflected class */ override def symbol: ClassSymbol - /** Returns a fresh instance of by invoking that constructor. - * @throws InstantiationException if the class does not have a public - * constructor with an empty parameter list. - * @throws IllegalAccessException if the class or its constructor is not accessible. - * @throws ExceptionInInitializerError if the initialization of the constructor fails. - * @throws SecurityException if creating a new instance is not permitted. + /** Reflects against a constructor symbol and returns a mirror + * that can be used to invoke it and construct instances of this mirror's symbols. + * + * To get a constructor symbol you would like to reflect, + * use `.symbol.typeSignature.member(nme.CONSTRUCTOR).asMethodSymbol`. + * For further information about member lookup refer to `Symbol.typeSignature`. + * + * The input symbol can be either private or non-private (Scala reflection transparently deals with visibility). + * It must be a member (declared or inherited) of the class underlying this mirror. */ def reflectConstructor(constructor: MethodSymbol): MethodMirror @@ -161,24 +221,40 @@ trait Mirrors { self: Universe => /** A mirror that reflects instances and static classes */ trait ReflectiveMirror extends MirrorOf[Mirrors.this.type] { - /** A reflective mirror for the given object - * @param obj An arbitrary value - * @return The mirror for `obj`. + /** A reflective mirror for the given object. + * + * Such a mirror can be used to further reflect against the members of the object + * to get/set fields, invoke methods and inspect inner classes and objects. */ def reflect(obj: Any): InstanceMirror - /** .. */ + /** Reflects against a static class symbol and returns a mirror + * that can be used to create instances of the class, inspect its companion object or perform further reflections. + * + * To get a class symbol by the name of the class you would like to reflect, + * use `.classSymbol()`. + * + * The input symbol can be either private or non-private (Scala reflection transparently deals with visibility). + * It must be static, i.e. either top-level or nested within one or several static objects. + */ def reflectClass(cls: ClassSymbol): ClassMirror - /** .. */ + /** Reflects against a static module symbol and returns a mirror + * that can be used to get the instance of the object or inspect its companion class. + * + * To get a module symbol by the name of its companion class you would like to reflect, + * use `.classSymbol().companion.get`. + * + * The input symbol can be either private or non-private (Scala reflection transparently deals with visibility). + * It must be static, i.e. either top-level or nested within one or several static objects. + */ def reflectModule(mod: ModuleSymbol): ModuleMirror } /** The API of a mirror for a reflective universe */ trait RuntimeMirror extends ReflectiveMirror { self => - /** Maps a Scala type to the corresponding Java class object - */ + /** Maps a Scala type to the corresponding Java class object */ def runtimeClass(tpe: Type): RuntimeClass /** Maps a Scala class symbol to the corresponding Java class object @@ -198,7 +274,7 @@ trait Mirrors { self: Universe => def classSymbol(rtcls: RuntimeClass): ClassSymbol /** A module symbol for the specified runtime class. - * @return The module symbol for the runtime class in the current class loader. + * @return The module symbol for the runtime class in the current class loader. * @throws java.lang.ClassNotFoundException if no class with that name exists * @throws scala.reflect.internal.MissingRequirementError if no corresponding symbol exists * to do: throws anything else? -- cgit v1.2.3 From 30f5a36941aa1671849322ba79ebff0881ae7ff0 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 17 Jul 2012 12:36:11 +0200 Subject: SI-5999 removes Context.reify Currently there are discrepancies between the behavior of c.reify and c.universe.reify. First step in fixing these problems is removing the duplication in the API. That's why I'm cutting away the Context.reify shortcut. Context.reify is a magic macro, hardwired in the fast track mechanism, so removing it requires redeploying the starr (because an old starr will crash if launched on sources that don't contain Context.reify). To cleanly redeploy a starr I've left a Context.reify stub in sources, but hidden it behind a `protected` modifier. When starr is redeployed (in a subsequent commit) the stub will be removed. I've also updated the tests to use c.universe.reify instead of c.reify. This will break some of them, because c.universe.reify uses a standard compiler mirror, which unlike a macro mirror doesn't like packageless classes. That's an annoyance, but I think having clean separation of commits is more important that being 100% consistent. --- .../reflect/makro/runtime/ContextReifiers.scala | 26 ---------------------- src/compiler/scala/reflect/reify/Taggers.scala | 26 ++++++++-------------- src/compiler/scala/tools/reflect/FastTrack.scala | 3 --- .../scala/reflect/internal/Definitions.scala | 1 - src/reflect/scala/reflect/makro/Context.scala | 4 ++-- test/files/neg/macro-cyclic.check | 8 +++---- test/files/neg/macro-cyclic/Impls_Macros_1.scala | 2 +- .../run/macro-openmacros/Impls_Macros_1.scala | 2 +- test/files/run/macro-reify-basic/Macros_1.scala | 2 +- test/files/run/macro-reify-freevars/Macros_1.scala | 2 +- .../run/macro-reify-nested-a/Impls_Macros_1.scala | 2 +- .../run/macro-reify-nested-b/Impls_Macros_1.scala | 2 +- .../macro-reify-ref-to-packageless/Impls_1.scala | 2 +- .../run/macro-reify-splice-splice/Macros_1.scala | 4 ++-- test/files/run/macro-reify-tagful-a/Macros_1.scala | 2 +- .../run/macro-reify-tagless-a/Impls_Macros_1.scala | 2 +- test/files/run/macro-reify-unreify/Macros_1.scala | 2 +- test/files/run/macro-settings/Impls_Macros_1.scala | 2 +- .../run/macro-sip19-revised/Impls_Macros_1.scala | 2 +- test/files/run/macro-sip19/Impls_Macros_1.scala | 2 +- .../Impls_Macros_1.scala | 4 ++-- .../Impls_Macros_1.scala | 2 +- test/files/run/t5713/Impls_Macros_1.scala | 6 ++--- .../Impls_1.scala | 2 +- test/pending/run/macro-reify-array/Macros_1.scala | 2 +- .../run/macro-reify-tagful-b/Macros_1.scala | 2 +- .../run/macro-reify-tagless-b/Impls_Macros_1.scala | 2 +- test/pending/run/t5692/Impls_Macros_1.scala | 2 +- 28 files changed, 41 insertions(+), 79 deletions(-) delete mode 100644 src/compiler/scala/reflect/makro/runtime/ContextReifiers.scala diff --git a/src/compiler/scala/reflect/makro/runtime/ContextReifiers.scala b/src/compiler/scala/reflect/makro/runtime/ContextReifiers.scala deleted file mode 100644 index 564148fe6c..0000000000 --- a/src/compiler/scala/reflect/makro/runtime/ContextReifiers.scala +++ /dev/null @@ -1,26 +0,0 @@ -package scala.reflect.makro -package runtime - -abstract class ContextReifiers { self => - val c: Context - - import c.universe._ - import definitions._ - import treeBuild._ - - import scala.reflect.reify.Taggers - import language.implicitConversions - private implicit def context2taggers(c0: Context) : Taggers { val c: c0.type } = new { val c: c0.type = c0 } with Taggers - - private def forMacroContext[T](prefix: Tree)(op: (Tree, Tree) => T): T = { - val universe = gen.mkAttributedSelect(prefix.duplicate, MacroContextUniverse) setType SingleType(prefix.tpe, MacroContextUniverse) - val mirror = TypeApply(Select(Select(prefix.duplicate, nme.mirror), nme.asInstanceOf_), List(Select(Ident(nme.UNIVERSE_SHORT), tpnme.Mirror))) - op(universe, mirror) - } - - def materializeExprForMacroContext(prefix: Tree, expr: Tree): Tree = - forMacroContext(prefix)((universe, mirror) => c.materializeExpr(universe, mirror, expr)) - - def materializeTypeTagForMacroContext(prefix: Tree, tpe: Type, concrete: Boolean): Tree = - forMacroContext(prefix)((universe, mirror) => c.materializeTypeTag(universe, mirror, tpe, concrete)) -} \ No newline at end of file diff --git a/src/compiler/scala/reflect/reify/Taggers.scala b/src/compiler/scala/reflect/reify/Taggers.scala index e09f13a052..576576bc6f 100644 --- a/src/compiler/scala/reflect/reify/Taggers.scala +++ b/src/compiler/scala/reflect/reify/Taggers.scala @@ -37,23 +37,15 @@ abstract class Taggers { } def materializeTypeTag(universe: Tree, mirror: Tree, tpe: Type, concrete: Boolean): Tree = { - if (universe.symbol == MacroContextUniverse && mirror == EmptyTree) { - import scala.reflect.makro.runtime.ContextReifiers - import language.implicitConversions - implicit def context2contextreifiers(c0: Context) : ContextReifiers { val c: c0.type } = new { val c: c0.type = c0 } with ContextReifiers - val Select(prefix, _) = universe - c.materializeTypeTagForMacroContext(prefix, tpe, concrete) - } else { - val tagType = if (concrete) TypeTagClass else AbsTypeTagClass - val unaffiliatedTagTpe = TypeRef(BaseUniverseClass.asTypeConstructor, tagType, List(tpe)) - val unaffiliatedTag = c.inferImplicitValue(unaffiliatedTagTpe, silent = true, withMacrosDisabled = true) - unaffiliatedTag match { - case success if !success.isEmpty => - Apply(Select(success, nme.in), List(mirror orElse mkDefaultMirrorRef(c.universe)(universe, c.callsiteTyper))) - case _ => - val tagModule = if (concrete) TypeTagModule else AbsTypeTagModule - materializeTag(universe, tpe, tagModule, c.reifyType(universe, mirror, tpe, concrete = concrete)) - } + val tagType = if (concrete) TypeTagClass else AbsTypeTagClass + val unaffiliatedTagTpe = TypeRef(BaseUniverseClass.asTypeConstructor, tagType, List(tpe)) + val unaffiliatedTag = c.inferImplicitValue(unaffiliatedTagTpe, silent = true, withMacrosDisabled = true) + unaffiliatedTag match { + case success if !success.isEmpty => + Apply(Select(success, nme.in), List(mirror orElse mkDefaultMirrorRef(c.universe)(universe, c.callsiteTyper))) + case _ => + val tagModule = if (concrete) TypeTagModule else AbsTypeTagModule + materializeTag(universe, tpe, tagModule, c.reifyType(universe, mirror, tpe, concrete = concrete)) } } diff --git a/src/compiler/scala/tools/reflect/FastTrack.scala b/src/compiler/scala/tools/reflect/FastTrack.scala index 63ecfa32b2..237ef813c7 100644 --- a/src/compiler/scala/tools/reflect/FastTrack.scala +++ b/src/compiler/scala/tools/reflect/FastTrack.scala @@ -1,7 +1,6 @@ package scala.tools package reflect -import scala.reflect.makro.runtime.ContextReifiers import scala.reflect.reify.Taggers import scala.tools.nsc.typechecker.{Analyzer, Macros} @@ -16,7 +15,6 @@ trait FastTrack { import language.implicitConversions private implicit def context2taggers(c0: MacroContext): Taggers { val c: c0.type } = new { val c: c0.type = c0 } with Taggers - private implicit def context2contextreifiers(c0: MacroContext): ContextReifiers { val c: c0.type } = new { val c: c0.type = c0 } with ContextReifiers private implicit def context2macroimplementations(c0: MacroContext): MacroImplementations { val c: c0.type } = new { val c: c0.type = c0 } with MacroImplementations implicit def fastTrackEntry2MacroRuntime(entry: FastTrackEntry): MacroRuntime = args => entry.run(args) @@ -41,7 +39,6 @@ trait FastTrack { MacroInternal_materializeAbsTypeTag bindTo { case (c, Apply(TypeApply(_, List(tt)), List(u))) => c.materializeTypeTag(u, EmptyTree, tt.tpe, concrete = false) } MacroInternal_materializeTypeTag bindTo { case (c, Apply(TypeApply(_, List(tt)), List(u))) => c.materializeTypeTag(u, EmptyTree, tt.tpe, concrete = true) } ApiUniverseReify bindTo { case (c, Apply(TypeApply(_, List(tt)), List(expr))) => c.materializeExpr(c.prefix.tree, EmptyTree, expr) } - MacroContextReify bindTo { case (c, Apply(TypeApply(_, List(tt)), List(expr))) => c.materializeExprForMacroContext(c.prefix.tree, expr) } ReflectRuntimeCurrentMirror bindTo { case (c, _) => scala.reflect.runtime.Macros.currentMirror(c).tree } StringContext_f bindTo { case (c, app@Apply(Select(Apply(_, parts), _), args)) => c.macro_StringInterpolation_f(parts, args, app.pos) } registry diff --git a/src/reflect/scala/reflect/internal/Definitions.scala b/src/reflect/scala/reflect/internal/Definitions.scala index cd243b9df0..7284a199b7 100644 --- a/src/reflect/scala/reflect/internal/Definitions.scala +++ b/src/reflect/scala/reflect/internal/Definitions.scala @@ -490,7 +490,6 @@ trait Definitions extends api.StandardDefinitions { def MacroContextPrefixType = if (MacroContextClass != NoSymbol) getMemberType(MacroContextClass, tpnme.PrefixType) else NoSymbol def MacroContextUniverse = if (MacroContextClass != NoSymbol) getMemberMethod(MacroContextClass, nme.universe) else NoSymbol def MacroContextMirror = if (MacroContextClass != NoSymbol) getMemberMethod(MacroContextClass, nme.mirror) else NoSymbol - def MacroContextReify = if (MacroContextClass != NoSymbol) getMemberMethod(MacroContextClass, nme.reify) else NoSymbol lazy val MacroImplAnnotation = requiredClass[scala.reflect.makro.internal.macroImpl] lazy val MacroInternalPackage = getPackageObject("scala.reflect.makro.internal") def MacroInternal_materializeClassTag = getMemberMethod(MacroInternalPackage, nme.materializeClassTag) diff --git a/src/reflect/scala/reflect/makro/Context.scala b/src/reflect/scala/reflect/makro/Context.scala index f9858a063c..41c2791573 100644 --- a/src/reflect/scala/reflect/makro/Context.scala +++ b/src/reflect/scala/reflect/makro/Context.scala @@ -35,6 +35,6 @@ trait Context extends Aliases val prefix: Expr[PrefixType] /** Alias to the underlying mirror's reify */ - // implementation is magically hardwired to `scala.reflect.makro.runtime.ContextReifiers` - def reify[T](expr: T): Expr[T] = macro ??? + // [Eugene] obsoleted. should be removed once I redeploy a starr + protected def reify[T](expr: T): Expr[T] = ??? } diff --git a/test/files/neg/macro-cyclic.check b/test/files/neg/macro-cyclic.check index 608381e0e8..7978ec64a5 100644 --- a/test/files/neg/macro-cyclic.check +++ b/test/files/neg/macro-cyclic.check @@ -1,4 +1,4 @@ -Impls_Macros_1.scala:5: error: could not find implicit value for parameter e: SourceLocation - c.reify { implicitly[SourceLocation] } - ^ -one error found +Impls_Macros_1.scala:5: error: could not find implicit value for parameter e: SourceLocation + c.universe.reify { implicitly[SourceLocation] } + ^ +one error found diff --git a/test/files/neg/macro-cyclic/Impls_Macros_1.scala b/test/files/neg/macro-cyclic/Impls_Macros_1.scala index 1ea06fc968..2ecdc3416e 100644 --- a/test/files/neg/macro-cyclic/Impls_Macros_1.scala +++ b/test/files/neg/macro-cyclic/Impls_Macros_1.scala @@ -2,7 +2,7 @@ import scala.reflect.makro.Context object Macros { def impl(c: Context) = { - c.reify { implicitly[SourceLocation] } + c.universe.reify { implicitly[SourceLocation] } } implicit def sourceLocation: SourceLocation1 = macro impl diff --git a/test/files/run/macro-openmacros/Impls_Macros_1.scala b/test/files/run/macro-openmacros/Impls_Macros_1.scala index ffeccce1e8..6b92834b81 100644 --- a/test/files/run/macro-openmacros/Impls_Macros_1.scala +++ b/test/files/run/macro-openmacros/Impls_Macros_1.scala @@ -16,7 +16,7 @@ object Macros { import c.universe._ val next = if (c.enclosingMacros.length < 3) c.Expr[Unit](Select(Ident(c.mirror.staticModule("Macros")), newTermName("foo"))) else c.literalUnit - c.reify { + c.universe.reify { println(c.literal(normalizePaths(c.enclosingMacros.toString)).splice) next.splice } diff --git a/test/files/run/macro-reify-basic/Macros_1.scala b/test/files/run/macro-reify-basic/Macros_1.scala index 7a43ee58be..8f8598e248 100644 --- a/test/files/run/macro-reify-basic/Macros_1.scala +++ b/test/files/run/macro-reify-basic/Macros_1.scala @@ -4,7 +4,7 @@ object Macros { def foo(s: String) = macro Impls.foo object Impls { - def foo(c: Ctx)(s: c.Expr[String]) = c.reify { + def foo(c: Ctx)(s: c.Expr[String]) = c.universe.reify { println("hello " + s.splice) } } diff --git a/test/files/run/macro-reify-freevars/Macros_1.scala b/test/files/run/macro-reify-freevars/Macros_1.scala index eafc7f9a82..df1473511d 100644 --- a/test/files/run/macro-reify-freevars/Macros_1.scala +++ b/test/files/run/macro-reify-freevars/Macros_1.scala @@ -7,7 +7,7 @@ object QueryableMacros{ : c.Expr[scala.collection.slick.Queryable[S]] = { import c.universe._ val code = EmptyTree - c.reify{ + c.universe.reify{ Queryable.factory[S]( code.asInstanceOf[reflect.runtime.universe.Tree] ) } } diff --git a/test/files/run/macro-reify-nested-a/Impls_Macros_1.scala b/test/files/run/macro-reify-nested-a/Impls_Macros_1.scala index f9a08df90d..b52b962e31 100644 --- a/test/files/run/macro-reify-nested-a/Impls_Macros_1.scala +++ b/test/files/run/macro-reify-nested-a/Impls_Macros_1.scala @@ -30,7 +30,7 @@ object QueryableMacros{ Apply(Select(c.prefix.tree, newTermName( name )), List( projection.tree )) ).asInstanceOf[Tree] ))) - c.reify{ Queryable.factory[S]( foo.splice )} + c.universe.reify{ Queryable.factory[S]( foo.splice )} } def map[T:c.TypeTag, S:c.TypeTag] (c: scala.reflect.makro.Context) diff --git a/test/files/run/macro-reify-nested-b/Impls_Macros_1.scala b/test/files/run/macro-reify-nested-b/Impls_Macros_1.scala index f9a08df90d..b52b962e31 100644 --- a/test/files/run/macro-reify-nested-b/Impls_Macros_1.scala +++ b/test/files/run/macro-reify-nested-b/Impls_Macros_1.scala @@ -30,7 +30,7 @@ object QueryableMacros{ Apply(Select(c.prefix.tree, newTermName( name )), List( projection.tree )) ).asInstanceOf[Tree] ))) - c.reify{ Queryable.factory[S]( foo.splice )} + c.universe.reify{ Queryable.factory[S]( foo.splice )} } def map[T:c.TypeTag, S:c.TypeTag] (c: scala.reflect.makro.Context) diff --git a/test/files/run/macro-reify-ref-to-packageless/Impls_1.scala b/test/files/run/macro-reify-ref-to-packageless/Impls_1.scala index 2f2d05678d..66c0ee1e9b 100644 --- a/test/files/run/macro-reify-ref-to-packageless/Impls_1.scala +++ b/test/files/run/macro-reify-ref-to-packageless/Impls_1.scala @@ -2,5 +2,5 @@ import scala.reflect.makro.{Context => Ctx} object Impls { val `Answer to the Ultimate Question of Life, the Universe, and Everything` = 42 - def foo(c: Ctx) = c.reify { `Answer to the Ultimate Question of Life, the Universe, and Everything` } + def foo(c: Ctx) = c.universe.reify { `Answer to the Ultimate Question of Life, the Universe, and Everything` } } diff --git a/test/files/run/macro-reify-splice-splice/Macros_1.scala b/test/files/run/macro-reify-splice-splice/Macros_1.scala index 4f1b600f63..0de780b5a2 100644 --- a/test/files/run/macro-reify-splice-splice/Macros_1.scala +++ b/test/files/run/macro-reify-splice-splice/Macros_1.scala @@ -4,8 +4,8 @@ object Macros { def foo = macro Impls.foo object Impls { - def foo(c: Ctx) = c.reify { - { c.reify(c.reify("hello world")) }.splice.splice + def foo(c: Ctx) = c.universe.reify { + { c.universe.reify(c.universe.reify("hello world")) }.splice.splice } } } \ No newline at end of file diff --git a/test/files/run/macro-reify-tagful-a/Macros_1.scala b/test/files/run/macro-reify-tagful-a/Macros_1.scala index 63f117220a..32b09bdcdf 100644 --- a/test/files/run/macro-reify-tagful-a/Macros_1.scala +++ b/test/files/run/macro-reify-tagful-a/Macros_1.scala @@ -5,7 +5,7 @@ object Macros { def foo[T](s: T) = macro Impls.foo[T] object Impls { - def foo[T: c.TypeTag](c: Ctx)(s: c.Expr[T]) = c.reify { + def foo[T: c.TypeTag](c: Ctx)(s: c.Expr[T]) = c.universe.reify { List(s.splice) } } diff --git a/test/files/run/macro-reify-tagless-a/Impls_Macros_1.scala b/test/files/run/macro-reify-tagless-a/Impls_Macros_1.scala index 3796ae99cb..77e4b729d3 100644 --- a/test/files/run/macro-reify-tagless-a/Impls_Macros_1.scala +++ b/test/files/run/macro-reify-tagless-a/Impls_Macros_1.scala @@ -4,7 +4,7 @@ object Macros { def foo[T](s: T) = macro Impls.foo[T] object Impls { - def foo[T](c: Ctx)(s: c.Expr[T]) = c.reify { + def foo[T](c: Ctx)(s: c.Expr[T]) = c.universe.reify { List[T](s.splice) } } diff --git a/test/files/run/macro-reify-unreify/Macros_1.scala b/test/files/run/macro-reify-unreify/Macros_1.scala index 14777506d3..3e9033966d 100644 --- a/test/files/run/macro-reify-unreify/Macros_1.scala +++ b/test/files/run/macro-reify-unreify/Macros_1.scala @@ -11,7 +11,7 @@ object Macros { val greeting = c.reifyTree(c.runtimeUniverse, EmptyTree, c.typeCheck(Apply(Select(Literal(Constant("hello ")), newTermName("$plus")), List(c.unreifyTree(world))))) val typedGreeting = c.Expr[String](greeting) - c.reify { + c.universe.reify { println("hello " + s.splice + " = " + typedGreeting.splice) } } diff --git a/test/files/run/macro-settings/Impls_Macros_1.scala b/test/files/run/macro-settings/Impls_Macros_1.scala index 56e28b506c..20dcdc1bd1 100644 --- a/test/files/run/macro-settings/Impls_Macros_1.scala +++ b/test/files/run/macro-settings/Impls_Macros_1.scala @@ -1,7 +1,7 @@ import scala.reflect.makro.Context object Impls { - def impl(c: Context) = c.reify { + def impl(c: Context) = c.universe.reify { println(c.literal(c.settings.toString).splice) } } diff --git a/test/files/run/macro-sip19-revised/Impls_Macros_1.scala b/test/files/run/macro-sip19-revised/Impls_Macros_1.scala index 994421808e..013130d181 100644 --- a/test/files/run/macro-sip19-revised/Impls_Macros_1.scala +++ b/test/files/run/macro-sip19-revised/Impls_Macros_1.scala @@ -11,7 +11,7 @@ object Macros { val fileName = fun.pos.fileInfo.getName val line = fun.pos.line val charOffset = fun.pos.point - c.reify { SourceLocation1(outer.splice, c.literal(fileName).splice, c.literal(line).splice, c.literal(charOffset).splice) } + c.universe.reify { SourceLocation1(outer.splice, c.literal(fileName).splice, c.literal(line).splice, c.literal(charOffset).splice) } } implicit def sourceLocation: SourceLocation1 = macro impl diff --git a/test/files/run/macro-sip19/Impls_Macros_1.scala b/test/files/run/macro-sip19/Impls_Macros_1.scala index c006ceb691..f6636c298c 100644 --- a/test/files/run/macro-sip19/Impls_Macros_1.scala +++ b/test/files/run/macro-sip19/Impls_Macros_1.scala @@ -7,7 +7,7 @@ object Macros { val fileName = fun.pos.fileInfo.getName val line = fun.pos.line val charOffset = fun.pos.point - c.reify { SourceLocation(c.literal(fileName).splice, c.literal(line).splice, c.literal(charOffset).splice) } + c.universe.reify { SourceLocation(c.literal(fileName).splice, c.literal(line).splice, c.literal(charOffset).splice) } } implicit def sourceLocation: SourceLocation = macro impl diff --git a/test/files/run/macro-undetparams-consfromsls/Impls_Macros_1.scala b/test/files/run/macro-undetparams-consfromsls/Impls_Macros_1.scala index b9bb2cfcca..7b921c0e57 100644 --- a/test/files/run/macro-undetparams-consfromsls/Impls_Macros_1.scala +++ b/test/files/run/macro-undetparams-consfromsls/Impls_Macros_1.scala @@ -2,12 +2,12 @@ import scala.reflect.runtime.universe._ import scala.reflect.makro.Context object Macros { - def cons_impl[A: c.AbsTypeTag](c: Context)(x: c.Expr[A], xs: c.Expr[List[A]]): c.Expr[List[A]] = c.reify { + def cons_impl[A: c.AbsTypeTag](c: Context)(x: c.Expr[A], xs: c.Expr[List[A]]): c.Expr[List[A]] = c.universe.reify { println("A = " + c.literal(implicitly[c.AbsTypeTag[A]].toString).splice) x.splice :: xs.splice } - def nil_impl[B: c.AbsTypeTag](c: Context): c.Expr[List[B]] = c.reify { + def nil_impl[B: c.AbsTypeTag](c: Context): c.Expr[List[B]] = c.universe.reify { println("B = " + c.literal(implicitly[c.AbsTypeTag[B]].toString).splice) Nil } diff --git a/test/files/run/macro-undetparams-macroitself/Impls_Macros_1.scala b/test/files/run/macro-undetparams-macroitself/Impls_Macros_1.scala index 9b1dd8e017..fdba40623b 100644 --- a/test/files/run/macro-undetparams-macroitself/Impls_Macros_1.scala +++ b/test/files/run/macro-undetparams-macroitself/Impls_Macros_1.scala @@ -2,7 +2,7 @@ import scala.reflect.runtime.universe._ import scala.reflect.makro.Context object Macros { - def impl[T: c.TypeTag](c: Context)(foo: c.Expr[T]): c.Expr[Unit] = c.reify { println(c.literal(implicitly[c.TypeTag[T]].toString).splice) } + def impl[T: c.TypeTag](c: Context)(foo: c.Expr[T]): c.Expr[Unit] = c.universe.reify { println(c.literal(implicitly[c.TypeTag[T]].toString).splice) } def foo[T](foo: T) = macro impl[T] } \ No newline at end of file diff --git a/test/files/run/t5713/Impls_Macros_1.scala b/test/files/run/t5713/Impls_Macros_1.scala index d16299a0c8..c041d36523 100644 --- a/test/files/run/t5713/Impls_Macros_1.scala +++ b/test/files/run/t5713/Impls_Macros_1.scala @@ -16,13 +16,13 @@ private object LoggerMacros { type LoggerContext = Context { type PrefixType = Logger.type } def error(c: LoggerContext)(message: c.Expr[String]): c.Expr[Unit] = - log(c)(c.reify(Level.Error), message) + log(c)(c.universe.reify(Level.Error), message) private def log(c: LoggerContext)(level: c.Expr[Level.Value], message: c.Expr[String]): c.Expr[Unit] = // was: if (level.splice.id < 4) // TODO Remove hack! if (c.eval(level).id < 4) // TODO Remove hack! - c.reify(()) + c.universe.reify(()) else { - c.reify(println(message.splice)) + c.universe.reify(println(message.splice)) } } \ No newline at end of file diff --git a/test/pending/run/macro-expand-implicit-macro-defeats-type-inference/Impls_1.scala b/test/pending/run/macro-expand-implicit-macro-defeats-type-inference/Impls_1.scala index 1740f40daf..599ddf5ed9 100644 --- a/test/pending/run/macro-expand-implicit-macro-defeats-type-inference/Impls_1.scala +++ b/test/pending/run/macro-expand-implicit-macro-defeats-type-inference/Impls_1.scala @@ -1,7 +1,7 @@ import scala.reflect.makro.Context object Impls { - def foo[T: c.TypeTag](c: Context): c.Expr[List[T]] = c.reify { + def foo[T: c.TypeTag](c: Context): c.Expr[List[T]] = c.universe.reify { println("openImplicits are: " + c.literal(c.openImplicits.toString).splice) println("enclosingImplicits are: " + c.literal(c.enclosingImplicits.toString).splice) println("typetag is: " + c.literal(c.tag[T].toString).splice) diff --git a/test/pending/run/macro-reify-array/Macros_1.scala b/test/pending/run/macro-reify-array/Macros_1.scala index c1bd4187a6..4b4cb05884 100644 --- a/test/pending/run/macro-reify-array/Macros_1.scala +++ b/test/pending/run/macro-reify-array/Macros_1.scala @@ -4,7 +4,7 @@ object Macros { def foo[T](s: String) = macro Impls.foo[T] object Impls { - def foo[T: c.TypeTag](c: Ctx)(s: c.Expr[T]) = c.reify { + def foo[T: c.TypeTag](c: Ctx)(s: c.Expr[T]) = c.universe.reify { Array(s.splice) } } diff --git a/test/pending/run/macro-reify-tagful-b/Macros_1.scala b/test/pending/run/macro-reify-tagful-b/Macros_1.scala index 04cf46d3a5..f730bb51f1 100644 --- a/test/pending/run/macro-reify-tagful-b/Macros_1.scala +++ b/test/pending/run/macro-reify-tagful-b/Macros_1.scala @@ -4,7 +4,7 @@ object Macros { def foo[T](s: T) = macro Impls.foo[List[T]] object Impls { - def foo[T: c.TypeTag](c: Ctx)(s: c.Expr[T]) = c.reify { + def foo[T: c.TypeTag](c: Ctx)(s: c.Expr[T]) = c.universe.reify { List(s.splice) } } diff --git a/test/pending/run/macro-reify-tagless-b/Impls_Macros_1.scala b/test/pending/run/macro-reify-tagless-b/Impls_Macros_1.scala index 04366353eb..2c5079ea54 100644 --- a/test/pending/run/macro-reify-tagless-b/Impls_Macros_1.scala +++ b/test/pending/run/macro-reify-tagless-b/Impls_Macros_1.scala @@ -4,7 +4,7 @@ object Macros { def foo[T](s: T) = macro Impls.foo[List[T]] object Impls { - def foo[T](c: Ctx)(s: c.Expr[T]) = c.reify { + def foo[T](c: Ctx)(s: c.Expr[T]) = c.universe.reify { List(s.splice) } } diff --git a/test/pending/run/t5692/Impls_Macros_1.scala b/test/pending/run/t5692/Impls_Macros_1.scala index f9c1e5f12b..7d0e788bd6 100644 --- a/test/pending/run/t5692/Impls_Macros_1.scala +++ b/test/pending/run/t5692/Impls_Macros_1.scala @@ -1,7 +1,7 @@ import scala.reflect.makro.Context object Impls { - def impl[A](c: reflect.makro.Context) = c.reify(()) + def impl[A](c: reflect.makro.Context) = c.universe.reify(()) } object Macros { -- cgit v1.2.3 From 602a6c55d77d892ea5b37725a416690e769070be Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 17 Jul 2012 13:22:52 +0200 Subject: SI-5999 deploys a new starr That doesn't require Context.reify anymore. --- lib/scala-compiler-src.jar.desired.sha1 | 2 +- lib/scala-compiler.jar.desired.sha1 | 2 +- lib/scala-library-src.jar.desired.sha1 | 2 +- lib/scala-library.jar.desired.sha1 | 2 +- lib/scala-reflect-src.jar.desired.sha1 | 2 +- lib/scala-reflect.jar.desired.sha1 | 2 +- src/reflect/scala/reflect/makro/Context.scala | 4 ---- 7 files changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/scala-compiler-src.jar.desired.sha1 b/lib/scala-compiler-src.jar.desired.sha1 index 48b3d3284e..6840b60528 100644 --- a/lib/scala-compiler-src.jar.desired.sha1 +++ b/lib/scala-compiler-src.jar.desired.sha1 @@ -1 +1 @@ -259fd9f0a50ed6003248a01a366a97a5549aa386 ?scala-compiler-src.jar +6a03de33fb670e1b1a9930234adb6d1d6435323d ?scala-compiler-src.jar diff --git a/lib/scala-compiler.jar.desired.sha1 b/lib/scala-compiler.jar.desired.sha1 index a8dbdb0a38..393820f481 100644 --- a/lib/scala-compiler.jar.desired.sha1 +++ b/lib/scala-compiler.jar.desired.sha1 @@ -1 +1 @@ -42f7367cc6ac59022d098e6091e5425390b9c925 ?scala-compiler.jar +cf6aab754bcf77ab433e2b05d1f452e13f24cedc ?scala-compiler.jar diff --git a/lib/scala-library-src.jar.desired.sha1 b/lib/scala-library-src.jar.desired.sha1 index 3379287733..cefff7eec9 100644 --- a/lib/scala-library-src.jar.desired.sha1 +++ b/lib/scala-library-src.jar.desired.sha1 @@ -1 +1 @@ -e31e38414fd19c10add3e65bf77c2fd7c6c26f7d ?scala-library-src.jar +02dcd8656f99eabbad8d9dce06a33d4f6f8d7c38 ?scala-library-src.jar diff --git a/lib/scala-library.jar.desired.sha1 b/lib/scala-library.jar.desired.sha1 index bef528ce26..f7f2321177 100644 --- a/lib/scala-library.jar.desired.sha1 +++ b/lib/scala-library.jar.desired.sha1 @@ -1 +1 @@ -2418c95bf7db34f87ebda4a5eaa918fe85047afb ?scala-library.jar +6a035e798a94c6ca051e6a7663293cfee3d7136f ?scala-library.jar diff --git a/lib/scala-reflect-src.jar.desired.sha1 b/lib/scala-reflect-src.jar.desired.sha1 index b3a5f03efe..fb09e9d585 100644 --- a/lib/scala-reflect-src.jar.desired.sha1 +++ b/lib/scala-reflect-src.jar.desired.sha1 @@ -1 +1 @@ -51c64d77ad4c4233a06cea7ea80b0fb77e9867c4 ?scala-reflect-src.jar +ecb9dd737935812a6399a3426e9126908ef870b2 ?scala-reflect-src.jar diff --git a/lib/scala-reflect.jar.desired.sha1 b/lib/scala-reflect.jar.desired.sha1 index 4d913d73ab..dc5b7abd0e 100644 --- a/lib/scala-reflect.jar.desired.sha1 +++ b/lib/scala-reflect.jar.desired.sha1 @@ -1 +1 @@ -5656bf2f17bb9f22b3ba61a83393a9794eaa5429 ?scala-reflect.jar +344f3feafa4bd155b13ba7a6ab3efd605f8f8388 ?scala-reflect.jar diff --git a/src/reflect/scala/reflect/makro/Context.scala b/src/reflect/scala/reflect/makro/Context.scala index 41c2791573..f093016a38 100644 --- a/src/reflect/scala/reflect/makro/Context.scala +++ b/src/reflect/scala/reflect/makro/Context.scala @@ -33,8 +33,4 @@ trait Context extends Aliases /** The prefix tree from which the macro is selected */ val prefix: Expr[PrefixType] - - /** Alias to the underlying mirror's reify */ - // [Eugene] obsoleted. should be removed once I redeploy a starr - protected def reify[T](expr: T): Expr[T] = ??? } -- cgit v1.2.3 From dc7cef8ad4d5bbc4c7453eb6c137a220ab16f1f1 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 17 Jul 2012 15:00:05 +0200 Subject: more meaningful name for a missing hook method --- src/reflect/scala/reflect/internal/Mirrors.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reflect/scala/reflect/internal/Mirrors.scala b/src/reflect/scala/reflect/internal/Mirrors.scala index 761b993539..9c81244bc6 100644 --- a/src/reflect/scala/reflect/internal/Mirrors.scala +++ b/src/reflect/scala/reflect/internal/Mirrors.scala @@ -49,9 +49,9 @@ trait Mirrors extends api.Mirrors { protected def mirrorMissingHook(owner: Symbol, name: Name): Symbol = NoSymbol - protected def symbolTableMissingHook(owner: Symbol, name: Name): Symbol = self.missingHook(owner, name) + protected def universeMissingHook(owner: Symbol, name: Name): Symbol = self.missingHook(owner, name) - private[scala] def missingHook(owner: Symbol, name: Name): Symbol = mirrorMissingHook(owner, name) orElse symbolTableMissingHook(owner, name) + private[scala] def missingHook(owner: Symbol, name: Name): Symbol = mirrorMissingHook(owner, name) orElse universeMissingHook(owner, name) /** If you're looking for a class, pass a type name. * If a module, a term name. -- cgit v1.2.3 From 96036b35698f735fd0e91aead8085519b5c3ce43 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 17 Jul 2012 15:21:38 +0200 Subject: SI-5999 removes the macro context mirror It is impossible to enforce macro programmers to use this mirror instead of c.universe.rootMirror, so it has to be let go. As to packageless symbol loading, which was the sole purpose of introducing a special mirror for macro contexts, it will be integrated into the compiler mirror in subsequent commits. --- .../scala/reflect/makro/runtime/Context.scala | 3 +- .../scala/reflect/makro/runtime/Mirrors.scala | 43 ---------------------- 2 files changed, 1 insertion(+), 45 deletions(-) delete mode 100644 src/compiler/scala/reflect/makro/runtime/Mirrors.scala diff --git a/src/compiler/scala/reflect/makro/runtime/Context.scala b/src/compiler/scala/reflect/makro/runtime/Context.scala index c7563e7b67..68964b7abb 100644 --- a/src/compiler/scala/reflect/makro/runtime/Context.scala +++ b/src/compiler/scala/reflect/makro/runtime/Context.scala @@ -8,7 +8,6 @@ abstract class Context extends scala.reflect.makro.Context with CapturedVariables with Infrastructure with Enclosures - with Mirrors with Names with Reifiers with FrontEnds @@ -23,7 +22,7 @@ abstract class Context extends scala.reflect.makro.Context val universe: Global - val mirror: MirrorOf[universe.type] = new ContextMirror + val mirror: MirrorOf[universe.type] = universe.rootMirror val callsiteTyper: universe.analyzer.Typer diff --git a/src/compiler/scala/reflect/makro/runtime/Mirrors.scala b/src/compiler/scala/reflect/makro/runtime/Mirrors.scala deleted file mode 100644 index ec970ee696..0000000000 --- a/src/compiler/scala/reflect/makro/runtime/Mirrors.scala +++ /dev/null @@ -1,43 +0,0 @@ -package scala.reflect.makro -package runtime - -import scala.tools.nsc.util.ScalaClassLoader - -trait Mirrors { - self: Context => - - import universe._ - import definitions._ - - class ContextMirror extends RootsBase(NoSymbol) { - val universe: self.universe.type = self.universe - def rootLoader: LazyType = rootMirror.rootLoader - - val RootPackage = rootMirror.RootPackage - val RootClass = rootMirror.RootClass - val EmptyPackage = rootMirror.EmptyPackage - val EmptyPackageClass = rootMirror.EmptyPackageClass - - // [Eugene++] this still doesn't solve the problem of invoking `c.typeCheck` on the code that refers to packageless symbols - override protected def mirrorMissingHook(owner: Symbol, name: Name): Symbol = { - if (owner.isRoot && isJavaClass(name.toString)) EmptyPackageClass.info decl name - else NoSymbol - } - - private lazy val libraryClasspathLoader: ClassLoader = { - val classpath = platform.classPath.asURLs - ScalaClassLoader.fromURLs(classpath) - } - - private def isJavaClass(path: String): Boolean = - try { - Class.forName(path, true, libraryClasspathLoader) - true - } catch { - case (_: ClassNotFoundException) | (_: NoClassDefFoundError) | (_: IncompatibleClassChangeError) => - false - } - - override def toString = "macro context mirror" - } -} \ No newline at end of file -- cgit v1.2.3 From 476204b03aa333cc91ed9ee31a69967f22c08650 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 17 Jul 2012 21:35:24 +0200 Subject: SI-5949 updates documentation of staticClass --- src/library/scala/reflect/base/MirrorOf.scala | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/library/scala/reflect/base/MirrorOf.scala b/src/library/scala/reflect/base/MirrorOf.scala index 03e035cd81..d030ace96c 100644 --- a/src/library/scala/reflect/base/MirrorOf.scala +++ b/src/library/scala/reflect/base/MirrorOf.scala @@ -15,6 +15,24 @@ abstract class MirrorOf[U <: base.Universe with Singleton] { /** The symbol corresponding to the globally accessible class with the * given fully qualified name `fullName`. + * + * If the name points to a type alias, it's recursively dealiased and its target is returned. + * If you need a symbol that corresponds to the type alias itself, load it directly from the package class: + * + * scala> cm.staticClass("scala.List") + * res0: reflect.runtime.universe.ClassSymbol = class List + * + * scala> res0.fullName + * res1: String = scala.collection.immutable.List + * + * scala> cm.staticModule("scala") + * res2: reflect.runtime.universe.ModuleSymbol = package scala + * + * scala> res2.moduleClass.typeSignature member newTypeName("List") + * res3: reflect.runtime.universe.Symbol = type List + * + * scala> res3.fullName + * res4: String = scala.List */ def staticClass(fullName: String): U#ClassSymbol -- cgit v1.2.3 From 46d6410071a333d665ee3a41565d0d3432f47669 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Tue, 17 Jul 2012 21:37:50 +0200 Subject: SI-5999 staticXXX is now friendly to packageless --- src/reflect/scala/reflect/internal/Mirrors.scala | 124 ++++++++++++++--------- 1 file changed, 77 insertions(+), 47 deletions(-) diff --git a/src/reflect/scala/reflect/internal/Mirrors.scala b/src/reflect/scala/reflect/internal/Mirrors.scala index 9c81244bc6..100168a69d 100644 --- a/src/reflect/scala/reflect/internal/Mirrors.scala +++ b/src/reflect/scala/reflect/internal/Mirrors.scala @@ -29,14 +29,24 @@ trait Mirrors extends api.Mirrors { else definitions.findNamedMember(segs.tail, RootClass.info member segs.head) } + /** If you're looking for a class, pass a type name. + * If a module, a term name. + * + * `tryPackageless` tells this function to search for requested a requested symbol in EmptyPackageClass as well. + * Compiler might ignore them, but they should be loadable with macros if the programmer wishes. + * More info here: http://groups.google.com/group/scala-internals/browse_thread/thread/5146021fd7c0cec + */ + private def getModuleOrClass(path: Name, tryPackageless: Boolean): Symbol = getModuleOrClass(path, path.length, tryPackageless) + /** Todo: organize similar to mkStatic in reflect.Base */ - private def getModuleOrClass(path: Name, len: Int): Symbol = { + private def getModuleOrClass(path: Name, len: Int, tryPackageless: Boolean): Symbol = { val point = path lastPos('.', len - 1) val owner = - if (point > 0) getModuleOrClass(path.toTermName, point) + if (point > 0) getModuleOrClass(path.toTermName, point, tryPackageless) else RootClass val name = path subName (point + 1, len) - val sym = owner.info member name + var sym = owner.info member name + if (sym == NoSymbol && owner == RootClass && tryPackageless) sym = EmptyPackageClass.info decl name val result = if (path.isTermName) sym.suchThat(_ hasFlag MODULE) else sym if (result != NoSymbol) result else { @@ -53,17 +63,12 @@ trait Mirrors extends api.Mirrors { private[scala] def missingHook(owner: Symbol, name: Name): Symbol = mirrorMissingHook(owner, name) orElse universeMissingHook(owner, name) - /** If you're looking for a class, pass a type name. - * If a module, a term name. - */ - private def getModuleOrClass(path: Name): Symbol = getModuleOrClass(path, path.length) - - override def staticClass(fullName: String): ClassSymbol = getRequiredClass(fullName) + // todo: get rid of most the methods here and keep just staticClass/Module/Package - // todo: get rid of most creation methods and keep just staticClass/Module/Package + /************************ loaders of class symbols ************************/ - def getClassByName(fullname: Name): ClassSymbol = { - var result = getModuleOrClass(fullname.toTypeName) + private def getClassImpl(fullname: TypeName, tryPackageless: Boolean): ClassSymbol = { + var result = getModuleOrClass(fullname.toTypeName, tryPackageless) while (result.isAliasType) result = result.info.typeSymbol result match { case x: ClassSymbol => x @@ -71,45 +76,50 @@ trait Mirrors extends api.Mirrors { } } - override def staticModule(fullName: String): ModuleSymbol = getRequiredModule(fullName) + @deprecated("Use getClassByName", "2.10.0") + def getClass(fullname: Name): ClassSymbol = + getClassByName(fullname) - def getModule(fullname: Name): ModuleSymbol = - // [Eugene++] should be a ClassCastException instead? - getModuleOrClass(fullname.toTermName) match { - case x: ModuleSymbol => x - case _ => MissingRequirementError.notFound("object " + fullname) - } + def getClassByName(fullname: Name): ClassSymbol = + getClassImpl(fullname.toTypeName, tryPackageless = false) - def getPackage(fullname: Name): ModuleSymbol = getModule(fullname) + def getRequiredClass(fullname: String): ClassSymbol = + getClassByName(newTypeNameCached(fullname)) - def getRequiredPackage(fullname: String): ModuleSymbol = - getPackage(newTermNameCached(fullname)) + def requiredClass[T: ClassTag] : ClassSymbol = + getRequiredClass(erasureName[T]) - @deprecated("Use getClassByName", "2.10.0") - def getClass(fullname: Name): ClassSymbol = getClassByName(fullname) + def getClassIfDefined(fullname: String): Symbol = + getClassIfDefined(newTypeName(fullname)) - def getRequiredClass(fullname: String): ClassSymbol = - getClassByName(newTypeNameCached(fullname)) match { - case x: ClassSymbol => x - case _ => MissingRequirementError.notFound("class " + fullname) - } + def getClassIfDefined(fullname: Name): Symbol = + wrapMissing(getClassByName(fullname.toTypeName)) - def getRequiredModule(fullname: String): ModuleSymbol = - getModule(newTermNameCached(fullname)) + /** Unlike getClassByName/getRequiredClass this function can also load packageless symbols. + * Compiler might ignore them, but they should be loadable with macros. + */ + override def staticClass(fullname: String): ClassSymbol = + getClassImpl(newTypeNameCached(fullname), tryPackageless = true) - def erasureName[T: ClassTag] : String = { - /** We'd like the String representation to be a valid - * scala type, so we have to decode the jvm's secret language. - */ - def erasureString(clazz: Class[_]): String = { - if (clazz.isArray) "Array[" + erasureString(clazz.getComponentType) + "]" - else clazz.getName + /************************ loaders of module symbols ************************/ + + private def getModuleImpl(fullname: TermName, tryPackageless: Boolean): ModuleSymbol = { + var result = getModuleOrClass(fullname, tryPackageless) + result match { + case x: ModuleSymbol => x + case _ => MissingRequirementError.notFound("object " + fullname) } - erasureString(classTag[T].runtimeClass) } - def requiredClass[T: ClassTag] : ClassSymbol = - getRequiredClass(erasureName[T]) + @deprecated("Use getModuleByName", "2.10.0") + def getModule(fullname: Name): ModuleSymbol = + getModuleByName(fullname) + + def getModuleByName(fullname: Name): ModuleSymbol = + getModuleImpl(fullname.toTermName, tryPackageless = false) + + def getRequiredModule(fullname: String): ModuleSymbol = + getModule(newTermNameCached(fullname)) // TODO: What syntax do we think should work here? Say you have an object // like scala.Predef. You can't say requiredModule[scala.Predef] since there's @@ -121,18 +131,25 @@ trait Mirrors extends api.Mirrors { def requiredModule[T: ClassTag] : ModuleSymbol = getRequiredModule(erasureName[T] stripSuffix "$") - def getClassIfDefined(fullname: String): Symbol = - getClassIfDefined(newTypeName(fullname)) - - def getClassIfDefined(fullname: Name): Symbol = - wrapMissing(getClassByName(fullname.toTypeName)) - def getModuleIfDefined(fullname: String): Symbol = getModuleIfDefined(newTermName(fullname)) def getModuleIfDefined(fullname: Name): Symbol = wrapMissing(getModule(fullname.toTermName)) + /** Unlike getModule/getRequiredModule this function can also load packageless symbols. + * Compiler might ignore them, but they should be loadable with macros. + */ + override def staticModule(fullname: String): ModuleSymbol = + getModuleImpl(newTermNameCached(fullname), tryPackageless = true) + + /************************ loaders of package symbols ************************/ + + def getPackage(fullname: Name): ModuleSymbol = getModule(fullname) + + def getRequiredPackage(fullname: String): ModuleSymbol = + getPackage(newTermNameCached(fullname)) + def getPackageObject(fullname: String): ModuleSymbol = (getModule(newTermName(fullname)).info member nme.PACKAGE) match { case x: ModuleSymbol => x @@ -151,6 +168,19 @@ trait Mirrors extends api.Mirrors { } } + /************************ helpers ************************/ + + def erasureName[T: ClassTag] : String = { + /** We'd like the String representation to be a valid + * scala type, so we have to decode the jvm's secret language. + */ + def erasureString(clazz: Class[_]): String = { + if (clazz.isArray) "Array[" + erasureString(clazz.getComponentType) + "]" + else clazz.getName + } + erasureString(classTag[T].runtimeClass) + } + @inline private def wrapMissing(body: => Symbol): Symbol = try body catch { case _: MissingRequirementError => NoSymbol } -- cgit v1.2.3 From c2aa17a72afa3dbc3a6c967b2fc8ade0a5a34637 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 20 Jul 2012 10:41:40 +0200 Subject: SI-5739 address @retronym's feedback, more docs --- .../tools/nsc/typechecker/PatternMatching.scala | 32 +++++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala index afe143553c..9b3acd7243 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala @@ -924,8 +924,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL Substitution(subPatBinders, subPatRefs) >> super.subPatternsAsSubstitution import CODE._ - def bindSubPats(in: Tree): Tree = - Block((subPatBinders, subPatRefs).zipped.map { case (sym, ref) => VAL(sym) === ref }, in) + def bindSubPats(in: Tree): Tree = Block(map2(subPatBinders, subPatRefs)(VAL(_) === _), in) } /** @@ -933,7 +932,11 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL * the next TreeMaker (here, we don't know which it'll be) is chained after this one by flatMap'ing * a function with binder `nextBinder` over our extractor's result * the function's body is determined by the next TreeMaker - * 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` + * (furthermore, the interpretation of `flatMap` depends on the codegen instance we're using). + * + * The values for the subpatterns, as computed by the extractor call in `extractor`, + * are stored in local variables that re-use the symbols in `subPatBinders`. + * This makes extractor patterns more debuggable (SI-5739). */ case class ExtractorTreeMaker(extractor: Tree, extraCond: Option[Tree], nextBinder: Symbol)( val subPatBinders: List[Symbol], @@ -958,7 +961,28 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL override def toString = "X"+(extractor, nextBinder.name) } - // TODO: allow user-defined unapplyProduct + /** + * An optimized version of ExtractorTreeMaker for Products. + * For now, this is hard-coded to case classes, and we simply extract the case class fields. + * + * The values for the subpatterns, as specified by the case class fields at the time of extraction, + * are stored in local variables that re-use the symbols in `subPatBinders`. + * This makes extractor patterns more debuggable (SI-5739) as well as + * avoiding mutation after the pattern has been matched (SI-5158, SI-6070) + * + * TODO: make this user-definable as follows + * When a companion object defines a method `def unapply_1(x: T): U_1`, but no `def unapply` or `def unapplySeq`, + * the extractor is considered to match any non-null value of type T + * the pattern is expected to have as many sub-patterns as there are `def unapply_I(x: T): U_I` methods, + * and the type of the I'th sub-pattern is `U_I`. + * The same exception for Seq patterns applies: if the last extractor is of type `Seq[U_N]`, + * the pattern must have at least N arguments (exactly N if the last argument is annotated with `: _*`). + * The arguments starting at N (and beyond) are taken from the sequence returned by apply_N, + * and it is checked that that sequence has enough elements to provide values for all expected sub-patterns. + * + * For a case class C, the implementation is assumed to be `def unapply_I(x: C) = x._I`, + * and the extractor call is inlined under that assumption. + */ case class ProductExtractorTreeMaker(prevBinder: Symbol, extraCond: Option[Tree])( val subPatBinders: List[Symbol], val subPatRefs: List[Tree]) extends FunTreeMaker with PreserveSubPatBinders { -- cgit v1.2.3 From 93519ab5049b085c3c13c9f438e516d5dedbfca1 Mon Sep 17 00:00:00 2001 From: Adriaan Moors Date: Fri, 20 Jul 2012 11:45:59 +0200 Subject: SI-6031 customizable budget for patmat analyses read the scalac.patmat.analysisBudget system property to decide how much time to spend on unreachability/exhaustivity the default value is 256, and you can turn it off entirely using -Dscalac.patmat.analysisBudget=off when the budget is exhausted, an unchecked warning is emitted stack overflows in CNF conversion are caught and treated as analysis budget violations --- .../tools/nsc/typechecker/PatternMatching.scala | 56 ++++++++++++++++------ 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala index b54f127417..943f75fec6 100644 --- a/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala +++ b/src/compiler/scala/tools/nsc/typechecker/PatternMatching.scala @@ -54,6 +54,25 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } import debugging.patmatDebug + // to govern how much time we spend analyzing matches for unreachability/exhaustivity + object AnalysisBudget { + import scala.tools.cmd.FromString.IntFromString + val max = sys.props.get("scalac.patmat.analysisBudget").collect(IntFromString.orElse{case "off" => Integer.MAX_VALUE}).getOrElse(256) + + abstract class Exception extends RuntimeException("CNF budget exceeded") { + val advice: String + def warn(pos: Position, kind: String) = currentUnit.uncheckedWarning(pos, s"Cannot check match for $kind.\n$advice") + } + + object exceeded extends Exception { + val advice = s"(The analysis required more space than allowed. Please try with scalac -Dscalac.patmat.analysisBudget=${AnalysisBudget.max*2} or -Dscalac.patmat.analysisBudget=off.)" + } + + object stackOverflow extends Exception { + val advice = "(There was a stack overflow. Please try increasing the stack available to the compiler using e.g., -Xss2m.)" + } + } + def newTransformer(unit: CompilationUnit): Transformer = if (opt.virtPatmat) new MatchTransformer(unit) else noopTransformer @@ -1946,14 +1965,14 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL type Formula def andFormula(a: Formula, b: Formula): Formula - class CNFBudgetExceeded extends RuntimeException("CNF budget exceeded") - // may throw an CNFBudgetExceeded - def propToSolvable(p: Prop) = { + // may throw an AnalysisBudget.Exception + def propToSolvable(p: Prop): Formula = { val (eqAxioms, pure :: Nil) = removeVarEq(List(p), modelNull = false) eqFreePropToSolvable(And(eqAxioms, pure)) } + // may throw an AnalysisBudget.Exception def eqFreePropToSolvable(p: Prop): Formula def cnfString(f: Formula): String @@ -1979,7 +1998,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL type Lit def Lit(sym: Sym, pos: Boolean = true): Lit - // throws an CNFBudgetExceeded when the prop results in a CNF that's too big + // throws an AnalysisBudget.Exception when the prop results in a CNF that's too big def eqFreePropToSolvable(p: Prop): Formula = { // TODO: for now, reusing the normalization from DPLL def negationNormalForm(p: Prop): Prop = p match { @@ -2001,9 +2020,9 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL def lit(s: Sym) = formula(clause(Lit(s))) def negLit(s: Sym) = formula(clause(Lit(s, false))) - def conjunctiveNormalForm(p: Prop, budget: Int = 256): Formula = { + def conjunctiveNormalForm(p: Prop, budget: Int = AnalysisBudget.max): Formula = { def distribute(a: Formula, b: Formula, budget: Int): Formula = - if (budget <= 0) throw new CNFBudgetExceeded + if (budget <= 0) throw AnalysisBudget.exceeded else (a, b) match { // true \/ _ = true @@ -2018,7 +2037,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL big flatMap (c => distribute(formula(c), small, budget - (big.size*small.size))) } - if (budget <= 0) throw new CNFBudgetExceeded + if (budget <= 0) throw AnalysisBudget.exceeded p match { case True => TrueF @@ -2037,9 +2056,17 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL } val start = Statistics.startTimer(patmatCNF) - val res = conjunctiveNormalForm(negationNormalForm(p)) + val res = + try { + conjunctiveNormalForm(negationNormalForm(p)) + } catch { case ex : StackOverflowError => + throw AnalysisBudget.stackOverflow + } + Statistics.stopTimer(patmatCNF, start) - patmatCNFSizes(res.size).value += 1 + + // + if (Statistics.enabled) patmatCNFSizes(res.size).value += 1 // patmatDebug("cnf for\n"+ p +"\nis:\n"+cnfString(res)) res @@ -2440,6 +2467,7 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL // right now hackily implement this by pruning counter-examples // unreachability would also benefit from a more faithful representation + // reachability (dead code) // computes the first 0-based case index that is unreachable (if any) @@ -2508,9 +2536,8 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL if (reachable) None else Some(caseIndex) } catch { - case e : CNFBudgetExceeded => -// debugWarn(util.Position.formatMessage(prevBinder.pos, "Cannot check match for reachability", false)) -// e.printStackTrace() + case ex: AnalysisBudget.Exception => + ex.warn(prevBinder.pos, "unreachability") None // CNF budget exceeded } } @@ -2651,9 +2678,8 @@ trait PatternMatching extends Transform with TypingTransformers with ast.TreeDSL Statistics.stopTimer(patmatAnaExhaust, start) pruned } catch { - case e : CNFBudgetExceeded => - patmatDebug(util.Position.formatMessage(prevBinder.pos, "Cannot check match for exhaustivity", false)) - // e.printStackTrace() + case ex : AnalysisBudget.Exception => + ex.warn(prevBinder.pos, "exhaustivity") Nil // CNF budget exceeded } } -- cgit v1.2.3 From 0b1667b85675ef321587252c12878fc1b52888dd Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Fri, 20 Jul 2012 12:28:58 +0200 Subject: an improvement based on Adriaan's comment """ why use asTypeConstructor for a prefix? shouldn't that be BaseUniverseClass.thisType? also, why does the prefix need to be changed at all? I'm sure there's a valid reason, but it would be good to document it. if you don't need to change the prefix, use appliedType(tagType.typeConstructor, List(tpe)) """ Because we need to look for an implicit of type base.Universe # TypeTag[$tpe]. I figured out that this is exactly the internal representation of that type. --- src/compiler/scala/reflect/reify/Taggers.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/compiler/scala/reflect/reify/Taggers.scala b/src/compiler/scala/reflect/reify/Taggers.scala index 576576bc6f..4e30d0acf8 100644 --- a/src/compiler/scala/reflect/reify/Taggers.scala +++ b/src/compiler/scala/reflect/reify/Taggers.scala @@ -38,7 +38,13 @@ abstract class Taggers { def materializeTypeTag(universe: Tree, mirror: Tree, tpe: Type, concrete: Boolean): Tree = { val tagType = if (concrete) TypeTagClass else AbsTypeTagClass - val unaffiliatedTagTpe = TypeRef(BaseUniverseClass.asTypeConstructor, tagType, List(tpe)) + // what we need here is to compose a type BaseUniverse # TypeTag[$tpe] + // to look for an implicit that conforms to this type + // that's why neither appliedType(tagType, List(tpe)) aka TypeRef(TypeTagsClass.thisType, tagType, List(tpe)) + // nor TypeRef(BaseUniverseClass.thisType, tagType, List(tpe)) won't fit here + // scala> :type -v def foo: scala.reflect.base.Universe#TypeTag[Int] = ??? + // NullaryMethodType(TypeRef(pre = TypeRef(TypeSymbol(Universe)), TypeSymbol(TypeTag), args = List($tpe)))) + val unaffiliatedTagTpe = TypeRef(BaseUniverseClass.typeConstructor, tagType, List(tpe)) val unaffiliatedTag = c.inferImplicitValue(unaffiliatedTagTpe, silent = true, withMacrosDisabled = true) unaffiliatedTag match { case success if !success.isEmpty => -- cgit v1.2.3 From e5161f01111c88de4f890c7541a6ff5e7be58916 Mon Sep 17 00:00:00 2001 From: Eugene Burmako Date: Fri, 20 Jul 2012 12:55:27 +0200 Subject: evicts calls to reify from our codebase Until reflection design is stabilized (i.e. 2.10.0 final is released), it's a good idea to refrain from using reify in our codebase (either directly in quasiquotes, or indirectly via materialized type tags). This increases the percentage of changes to reflection that don't require rebuilding the starr. The change to build.xml will expose reifications going on during our build (by printing out their results to the console, so that they bug everyone), making it easier to spot and fix them. --- build.xml | 2 +- src/compiler/scala/tools/cmd/FromString.scala | 2 +- .../scala/tools/nsc/interpreter/ILoop.scala | 2 +- .../scala/tools/nsc/interpreter/IMain.scala | 2 +- .../scala/tools/reflect/MacroImplementations.scala | 9 ++-- src/compiler/scala/tools/reflect/StdTags.scala | 61 +++++++++++++--------- 6 files changed, 45 insertions(+), 33 deletions(-) diff --git a/build.xml b/build.xml index 8fa1b9cd76..160a699ca2 100644 --- a/build.xml +++ b/build.xml @@ -397,7 +397,7 @@ INITIALISATION - +