diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 004cd9e0ce30..21afdcc85af6 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -982,7 +982,7 @@ object Denotations { final def first: SingleDenotation = this final def last: SingleDenotation = this - final def matches(other: SingleDenotation)(using Context): Boolean = + def matches(other: SingleDenotation)(using Context): Boolean = val d = signature.matchDegree(other.signature) d match @@ -1013,16 +1013,21 @@ object Denotations { end matches def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(using Context): SingleDenotation = - if (hasUniqueSym && prevDenots.containsSym(symbol)) NoDenotation - else if (isType) filterDisjoint(ownDenots).asSeenFrom(pre) + if hasUniqueSym && prevDenots.containsSym(symbol) then NoDenotation + else if isType then filterDisjoint(ownDenots).asSeenFrom(pre) else asSeenFrom(pre).filterDisjoint(ownDenots) - final def filterWithPredicate(p: SingleDenotation => Boolean): SingleDenotation = + def filterWithPredicate(p: SingleDenotation => Boolean): SingleDenotation = if (p(this)) this else NoDenotation - final def filterDisjoint(denots: PreDenotation)(using Context): SingleDenotation = + def filterDisjoint(denots: PreDenotation)(using Context): SingleDenotation = if (denots.exists && denots.matches(this)) NoDenotation else this def filterWithFlags(required: FlagSet, excluded: FlagSet)(using Context): SingleDenotation = - if (required.isEmpty && excluded.isEmpty || compatibleWith(required, excluded)) this else NoDenotation + def symd: SymDenotation = this match + case symd: SymDenotation => symd + case _ => symbol.denot + if !required.isEmpty && !symd.isAllOf(required) + || !excluded.isEmpty && symd.isOneOf(excluded) then NoDenotation + else this def aggregate[T](f: SingleDenotation => T, g: (T, T) => T): T = f(this) type AsSeenFromResult = SingleDenotation @@ -1056,16 +1061,6 @@ object Denotations { if (!owner.membersNeedAsSeenFrom(pre) || symbol.is(NonMember)) this else derived(symbol.info) } - - /** Does this denotation have all the `required` flags but none of the `excluded` flags? - */ - private def compatibleWith(required: FlagSet, excluded: FlagSet)(using Context): Boolean = { - val symd: SymDenotation = this match { - case symd: SymDenotation => symd - case _ => symbol.denot - } - symd.isAllOf(required) && !symd.isOneOf(excluded) - } } abstract class NonSymSingleDenotation(symbol: Symbol, initInfo: Type, override val prefix: Type) extends SingleDenotation(symbol, initInfo) { diff --git a/compiler/src/dotty/tools/dotc/core/Scopes.scala b/compiler/src/dotty/tools/dotc/core/Scopes.scala index 90b27683cdf7..cb5f5c55e337 100644 --- a/compiler/src/dotty/tools/dotc/core/Scopes.scala +++ b/compiler/src/dotty/tools/dotc/core/Scopes.scala @@ -148,12 +148,11 @@ object Scopes { * Symbols occur in the result in reverse order relative to their occurrence * in `this.toList`. */ - final def denotsNamed(name: Name, select: SymDenotation => Boolean = selectAll)(using Context): PreDenotation = { + final def denotsNamed(name: Name)(using Context): PreDenotation = { var syms: PreDenotation = NoDenotation var e = lookupEntry(name) while (e != null) { - val d = e.sym.denot - if (select(d)) syms = syms union d + syms = syms union e.sym.denot e = lookupNextEntry(e) } syms @@ -458,10 +457,6 @@ object Scopes { */ def scopeTransform(owner: Symbol)(op: => MutableScope): MutableScope = op - val selectAll: SymDenotation => Boolean = alwaysTrue - val selectPrivate: SymDenotation => Boolean = d => (d.flagsUNSAFE is Flags.Private) - val selectNonPrivate: SymDenotation => Boolean = d => !(d.flagsUNSAFE is Flags.Private) - /** The empty scope (immutable). */ object EmptyScope extends Scope { diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 57a90b81695a..fc014ec6684f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1564,14 +1564,14 @@ object SymDenotations { initPrivateWithin: Symbol) extends SymDenotation(symbol, maybeOwner, name, initFlags, initInfo, initPrivateWithin) { - import util.LRUCache + import util.HashTable // ----- caches ------------------------------------------------------- private var myTypeParams: List[TypeSymbol] = null private var fullNameCache: SimpleIdentityMap[QualifiedNameKind, Name] = SimpleIdentityMap.Empty - private var myMemberCache: LRUCache[Name, PreDenotation] = null + private var myMemberCache: HashTable[Name, PreDenotation] = null private var myMemberCachePeriod: Period = Nowhere /** A cache from types T to baseType(T, C) */ @@ -1582,9 +1582,9 @@ object SymDenotations { private var baseDataCache: BaseData = BaseData.None private var memberNamesCache: MemberNames = MemberNames.None - private def memberCache(using Context): LRUCache[Name, PreDenotation] = { + private def memberCache(using Context): HashTable[Name, PreDenotation] = { if (myMemberCachePeriod != ctx.period) { - myMemberCache = new LRUCache + myMemberCache = HashTable() myMemberCachePeriod = ctx.period } myMemberCache @@ -1855,61 +1855,53 @@ object SymDenotations { * The elements of the returned pre-denotation all * have existing symbols. */ - final def membersNamed(name: Name)(using Context): PreDenotation = { - val privates = info.decls.denotsNamed(name, selectPrivate) - privates union nonPrivateMembersNamed(name).filterDisjoint(privates) - } - - /** All non-private members of this class that have the given name. - * The elements of the returned pre-denotation all - * have existing symbols. - * @param inherited The method is called on a parent class from computeNPMembersNamed - */ - final def nonPrivateMembersNamed(name: Name)(using Context): PreDenotation = { - Stats.record("nonPrivateMembersNamed") - if (Config.cacheMembersNamed) { - var denots: PreDenotation = memberCache lookup name - if (denots == null) { - denots = computeNPMembersNamed(name) + final def membersNamed(name: Name)(using Context): PreDenotation = + Stats.record("membersNamed") + if Config.cacheMembersNamed then + var denots: PreDenotation = memberCache.lookup(name) + if denots == null then + denots = computeMembersNamed(name) memberCache.enter(name, denots) - } - else if (Config.checkCacheMembersNamed) { - val denots1 = computeNPMembersNamed(name) + else if Config.checkCacheMembersNamed then + val denots1 = computeMembersNamed(name) assert(denots.exists == denots1.exists, s"cache inconsistency: cached: $denots, computed $denots1, name = $name, owner = $this") - } denots - } - else computeNPMembersNamed(name) - } + else computeMembersNamed(name) + - private[core] def computeNPMembersNamed(name: Name)(using Context): PreDenotation = { - Stats.record("computeNPMembersNamed after fingerprint") - ensureCompleted() - val ownDenots = info.decls.denotsNamed(name, selectNonPrivate) - if (debugTrace) // DEBUG + /** All non-private members of this class that have the given name. + * The elements of the returned pre-denotation all have existing symbols. + */ + final def nonPrivateMembersNamed(name: Name)(using Context): PreDenotation = + val mbr = membersNamed(name) + val nonPrivate = mbr.filterWithFlags(EmptyFlags, Private) + if nonPrivate eq mbr then mbr + else addInherited(name, nonPrivate) + + private[core] def computeMembersNamed(name: Name)(using Context): PreDenotation = + Stats.record("computeMembersNamed") + val ownDenots = info.decls.denotsNamed(name) + if debugTrace then println(s"$this.member($name), ownDenots = $ownDenots") - def collect(denots: PreDenotation, parents: List[Type]): PreDenotation = parents match { + addInherited(name, ownDenots) + + private def addInherited(name: Name, ownDenots: PreDenotation)(using Context): PreDenotation = + def collect(denots: PreDenotation, parents: List[Type]): PreDenotation = parents match case p :: ps => val denots1 = collect(denots, ps) - p.classSymbol.denot match { + p.classSymbol.denot match case parentd: ClassDenotation => - denots1.union( - parentd.nonPrivateMembersNamed(name) - .mapInherited(ownDenots, denots1, thisType)) + val inherited = parentd.nonPrivateMembersNamed(name) + denots1.union(inherited.mapInherited(ownDenots, denots1, thisType)) case _ => denots1 - } - case nil => - denots - } - if (name.isConstructorName) ownDenots + case nil => denots + if name.isConstructorName then ownDenots else collect(ownDenots, classParents) - } - override final def findMember(name: Name, pre: Type, required: FlagSet, excluded: FlagSet)(using Context): Denotation = { - val raw = if (excluded.is(Private)) nonPrivateMembersNamed(name) else membersNamed(name) + override final def findMember(name: Name, pre: Type, required: FlagSet, excluded: FlagSet)(using Context): Denotation = + val raw = if excluded.is(Private) then nonPrivateMembersNamed(name) else membersNamed(name) raw.filterWithFlags(required, excluded).asSeenFrom(pre).toDenot(pre) - } /** Compute tp.baseType(this) */ final def baseTypeOf(tp: Type)(using Context): Type = { @@ -2213,25 +2205,24 @@ object SymDenotations { * object that hides a class or object in the scala package of the same name, because * the behavior would then be unintuitive for such members. */ - override def computeNPMembersNamed(name: Name)(using Context): PreDenotation = { - def recur(pobjs: List[ClassDenotation], acc: PreDenotation): PreDenotation = pobjs match { + override def computeMembersNamed(name: Name)(using Context): PreDenotation = + + def recur(pobjs: List[ClassDenotation], acc: PreDenotation): PreDenotation = pobjs match case pcls :: pobjs1 => if (pcls.isCompleting) recur(pobjs1, acc) - else { - val pmembers = pcls.computeNPMembersNamed(name).filterWithPredicate { d => + else + val pobjMembers = pcls.nonPrivateMembersNamed(name).filterWithPredicate { d => // Drop members of `Any` and `Object` val owner = d.symbol.maybeOwner (owner ne defn.AnyClass) && (owner ne defn.ObjectClass) } - recur(pobjs1, acc.union(pmembers)) - } + recur(pobjs1, acc.union(pobjMembers)) case nil => - val directMembers = super.computeNPMembersNamed(name) + val directMembers = super.computeMembersNamed(name) if !acc.exists then directMembers else acc.union(directMembers.filterWithPredicate(!_.symbol.isAbsent())) match case d: DenotUnion => dropStale(d) case d => d - } def dropStale(multi: DenotUnion): PreDenotation = val compiledNow = multi.filterWithPredicate(d => @@ -2273,13 +2264,12 @@ object SymDenotations { multi.filterWithPredicate(_.symbol.associatedFile == chosen) end dropStale - if (symbol `eq` defn.ScalaPackageClass) { - val denots = super.computeNPMembersNamed(name) - if (denots.exists || name == nme.CONSTRUCTOR) denots + if symbol eq defn.ScalaPackageClass then + val denots = super.computeMembersNamed(name) + if denots.exists || name == nme.CONSTRUCTOR then denots else recur(packageObjs, NoDenotation) - } else recur(packageObjs, NoDenotation) - } + end computeMembersNamed /** The union of the member names of the package and the package object */ override def memberNames(keepOnly: NameFilter)(implicit onBehalf: MemberNames, ctx: Context): Set[Name] = { @@ -2325,6 +2315,13 @@ object SymDenotations { override def owner: Symbol = throw new AssertionError("NoDenotation.owner") override def computeAsSeenFrom(pre: Type)(using Context): SingleDenotation = this override def mapInfo(f: Type => Type)(using Context): SingleDenotation = this + + override def matches(other: SingleDenotation)(using Context): Boolean = false + override def mapInherited(ownDenots: PreDenotation, prevDenots: PreDenotation, pre: Type)(using Context): SingleDenotation = this + override def filterWithPredicate(p: SingleDenotation => Boolean): SingleDenotation = this + override def filterDisjoint(denots: PreDenotation)(using Context): SingleDenotation = this + override def filterWithFlags(required: FlagSet, excluded: FlagSet)(using Context): SingleDenotation = this + NoSymbol.denot = this validFor = Period.allInRun(NoRunId) } @@ -2448,6 +2445,8 @@ object SymDenotations { def apply(module: TermSymbol, modcls: ClassSymbol): LazyType = this private var myDecls: Scope = EmptyScope + private var mySourceModule: Symbol = null + private var myModuleClass: Symbol = null private var mySourceModuleFn: Context ?=> Symbol = LazyType.NoSymbolFn private var myModuleClassFn: Context ?=> Symbol = LazyType.NoSymbolFn @@ -2457,8 +2456,12 @@ object SymDenotations { else sym.info.typeParams def decls: Scope = myDecls - def sourceModule(using Context): Symbol = mySourceModuleFn - def moduleClass(using Context): Symbol = myModuleClassFn + def sourceModule(using Context): Symbol = + if mySourceModule == null then mySourceModule = mySourceModuleFn + mySourceModule + def moduleClass(using Context): Symbol = + if myModuleClass == null then myModuleClass = myModuleClassFn + myModuleClass def withDecls(decls: Scope): this.type = { myDecls = decls; this } def withSourceModule(sourceModuleFn: Context ?=> Symbol): this.type = { mySourceModuleFn = sourceModuleFn; this } diff --git a/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala b/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala index 848c6866b22a..97126df3a6d6 100644 --- a/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala +++ b/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala @@ -31,7 +31,8 @@ class Instrumentation extends MiniPhase { thisPhase => private val namesOfInterest = List( "::", "+=", "toString", "newArray", "box", "toCharArray", "map", "flatMap", "filter", "withFilter", "collect", "foldLeft", "foldRight", "take", - "reverse", "mapConserve", "mapconserve", "filterConserve", "zip") + "reverse", "mapConserve", "mapconserve", "filterConserve", "zip", + "denotsNamed", "lookup", "lookupEntry", "lookupAll", "toList") private var namesToRecord: Set[Name] = _ private var consName: TermName = _ diff --git a/compiler/src/dotty/tools/dotc/util/HashTable.scala b/compiler/src/dotty/tools/dotc/util/HashTable.scala new file mode 100644 index 000000000000..d91d6c5d1a9f --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/HashTable.scala @@ -0,0 +1,142 @@ +package dotty.tools.dotc.util + +object HashTable: + /** The number of elements up to which dense packing is used. + * If the number of elements reaches `DenseLimit` a hash table is used instead + */ + inline val DenseLimit = 8 + +/** A hash table using open hashing with linear scan which is also very space efficient + * at small sizes. + * @param initialCapacity Indicates the initial number of slots in the hash table. + * The actual number of slots is always a power of 2, so the + * initial size of the table will be the smallest power of two + * that is equal or greater than the given `initialCapacity`. + * Minimum value is 4. + * @param capacityMultiple The minimum multiple of capacity relative to used elements. + * The hash table will be re-sized once the number of elements + * multiplied by capacityMultiple exceeds the current size of the hash table. + * However, a table of size up to DenseLimit will be re-sized only + * once the number of elements reaches the table's size. + */ +class HashTable[Key >: Null <: AnyRef, Value >: Null <: AnyRef] + (initialCapacity: Int = 8, capacityMultiple: Int = 3): + import HashTable.DenseLimit + + private var used: Int = _ + private var limit: Int = _ + private var table: Array[AnyRef] = _ + clear() + + private def allocate(capacity: Int) = + table = new Array[AnyRef](capacity * 2) + limit = if capacity <= DenseLimit then capacity - 1 else capacity / capacityMultiple + + private def roundToPower(n: Int) = + if Integer.bitCount(n) == 1 then n + else 1 << (32 - Integer.numberOfLeadingZeros(n)) + + /** Remove all elements from this table and set back to initial configuration */ + def clear(): Unit = + used = 0 + allocate(roundToPower(initialCapacity max 4)) + + /** The number of elements in the set */ + def size: Int = used + + private def isDense = limit < DenseLimit + + /** Hashcode, by default `System.identityHashCode`, but can be overriden */ + protected def hash(x: Key): Int = System.identityHashCode(x) + + /** Equality, by default `eq`, but can be overridden */ + protected def isEqual(x: Key, y: Key): Boolean = x eq y + + /** Turn hashcode `x` into a table index */ + private def index(x: Int): Int = x & (table.length - 2) + + private def firstIndex(key: Key) = if isDense then 0 else index(hash(key)) + private def nextIndex(idx: Int) = index(idx + 2) + + private def keyAt(idx: Int): Key = table(idx).asInstanceOf[Key] + private def valueAt(idx: Int): Value = table(idx + 1).asInstanceOf[Value] + + /** Find entry such that `isEqual(x, entry)`. If it exists, return it. + * If not, enter `x` in set and return `x`. + */ + def lookup(key: Key): Value = + var idx = firstIndex(key) + var k = keyAt(idx) + while k != null do + if isEqual(k, key) then return valueAt(idx) + idx = nextIndex(idx) + k = keyAt(idx) + null + + def enter(key: Key, value: Value): Unit = + var idx = firstIndex(key) + var k = keyAt(idx) + while k != null do + if isEqual(k, key) then + table(idx + 1) = value + return + idx = nextIndex(idx) + k = keyAt(idx) + table(idx) = key + table(idx + 1) = value + used += 1 + if used > limit then growTable() + + def invalidate(key: Key): Unit = + var idx = firstIndex(key) + var k = keyAt(idx) + while k != null do + if isEqual(k, key) then + var hole = idx + if !isDense then + while + idx = nextIndex(idx) + k = keyAt(idx) + k != null && index(hash(k)) != idx + do + table(hole) = k + table(hole + 1) = valueAt(idx) + hole = idx + table(hole) = null + used -= 1 + return + idx = nextIndex(idx) + k = keyAt(idx) + + private def addOld(key: Key, value: AnyRef): Unit = + var idx = firstIndex(key) + var k = keyAt(idx) + while k != null do + idx = nextIndex(idx) + k = keyAt(idx) + table(idx) = key + table(idx + 1) = value + + private def growTable(): Unit = + val oldTable = table + val newLength = + if oldTable.length == DenseLimit then DenseLimit * 2 * roundToPower(capacityMultiple) + else table.length + allocate(newLength) + if isDense then + Array.copy(oldTable, 0, table, 0, oldTable.length) + else + var idx = 0 + while idx < oldTable.length do + val key = oldTable(idx).asInstanceOf[Key] + if key != null then addOld(key, oldTable(idx + 1)) + idx += 2 + + def iterator: Iterator[(Key, Value)] = + for idx <- (0 until table.length by 2).iterator + if keyAt(idx) != null + yield (keyAt(idx), valueAt(idx)) + + override def toString: String = + iterator.map((k, v) => s"$k -> $v").mkString("HashTable(", ", ", ")") +end HashTable diff --git a/tests/neg/i3253.scala b/tests/neg/i3253.scala index 51acaad92706..04a79bb73ea0 100644 --- a/tests/neg/i3253.scala +++ b/tests/neg/i3253.scala @@ -1,5 +1,5 @@ import Test.test -object Test { +class A: def test = " " * 10 // error -} +object Test extends A diff --git a/tests/neg/i9052/A.scala b/tests/pos/i9052/A.scala similarity index 100% rename from tests/neg/i9052/A.scala rename to tests/pos/i9052/A.scala diff --git a/tests/neg/i9052/B.scala b/tests/pos/i9052/B.scala similarity index 100% rename from tests/neg/i9052/B.scala rename to tests/pos/i9052/B.scala