diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index 593645f0113e..15619f2530df 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -39,7 +39,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { import coreBTypes._ import BCodeBodyBuilder._ - private val primitives = new DottyPrimitives(ctx) + protected val primitives: DottyPrimitives /* * Functionality to build the body of ASM MethodNode, except for `synchronized` and `try` expressions. @@ -1021,9 +1021,15 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { } } - def genLoadArguments(args: List[Tree], btpes: List[BType]): Unit = { - (args zip btpes) foreach { case (arg, btpe) => genLoad(arg, btpe) } - } + def genLoadArguments(args: List[Tree], btpes: List[BType]): Unit = + args match + case arg :: args1 => + btpes match + case btpe :: btpes1 => + genLoad(arg, btpe) + genLoadArguments(args1, btpes1) + case _ => + case _ => def genLoadModule(tree: Tree): BType = { val module = ( diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala index 34ec2b2bb747..3c4f77df074f 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala @@ -339,16 +339,15 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * must-single-thread */ def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[Annotation]]): Unit = - val annotationss = pannotss map (_ filter shouldEmitAnnotation) - if (annotationss forall (_.isEmpty)) return - for ((annots, idx) <- annotationss.zipWithIndex; - annot <- annots) { - val typ = annot.tree.tpe - val assocs = assocsFromApply(annot.tree) - val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, typeDescriptor(typ.asInstanceOf[Type]), isRuntimeVisible(annot)) - emitAssocs(pannVisitor, assocs, BCodeHelpers.this)(this) - } - + if pannotss.nestedExists(shouldEmitAnnotation) then + for (annots, idx) <- pannotss.zipWithIndex + annot <- annots + if shouldEmitAnnotation(annot) + do + val typ = annot.tree.tpe + val assocs = assocsFromApply(annot.tree) + val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, typeDescriptor(typ.asInstanceOf[Type]), isRuntimeVisible(annot)) + emitAssocs(pannVisitor, assocs, BCodeHelpers.this)(this) private def shouldEmitAnnotation(annot: Annotation): Boolean = { annot.symbol.is(JavaDefined) && diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala index 708a6fee9fff..4112dcbeda20 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala @@ -35,10 +35,10 @@ trait BCodeIdiomatic { lazy val majorVersion: Int = (classfileVersion & 0xFF) lazy val emitStackMapFrame = (majorVersion >= 50) - val extraProc: Int = GenBCodeOps.mkFlags( - asm.ClassWriter.COMPUTE_MAXS, - if (emitStackMapFrame) asm.ClassWriter.COMPUTE_FRAMES else 0 - ) + val extraProc: Int = + import GenBCodeOps.addFlagIf + asm.ClassWriter.COMPUTE_MAXS + .addFlagIf(emitStackMapFrame, asm.ClassWriter.COMPUTE_FRAMES) lazy val JavaStringBuilderClassName = jlStringBuilderRef.internalName diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 9a1bafa423c7..2c575309d9f1 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -535,7 +535,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { } def lineNumber(tree: Tree): Unit = { if (!emitLines || !tree.span.exists) return; - val nr = ctx.source.atSpan(tree.span).line + 1 + val nr = ctx.source.offsetToLine(tree.span.point) + 1 if (nr != lastEmittedLineNr) { lastEmittedLineNr = nr lastInsn match { @@ -690,12 +690,12 @@ trait BCodeSkelBuilder extends BCodeHelpers { val isNative = methSymbol.hasAnnotation(NativeAttr) val isAbstractMethod = (methSymbol.is(Deferred) || (methSymbol.owner.isInterface && ((methSymbol.is(Deferred)) || methSymbol.isClassConstructor))) - val flags = GenBCodeOps.mkFlags( - javaFlags(methSymbol), - if (isAbstractMethod) asm.Opcodes.ACC_ABSTRACT else 0, - if (false /*methSymbol.isStrictFP*/) asm.Opcodes.ACC_STRICT else 0, - if (isNative) asm.Opcodes.ACC_NATIVE else 0 // native methods of objects are generated in mirror classes - ) + val flags = + import GenBCodeOps.addFlagIf + javaFlags(methSymbol) + .addFlagIf(isAbstractMethod, asm.Opcodes.ACC_ABSTRACT) + .addFlagIf(false /*methSymbol.isStrictFP*/, asm.Opcodes.ACC_STRICT) + .addFlagIf(isNative, asm.Opcodes.ACC_NATIVE) // native methods of objects are generated in mirror classes // TODO needed? for(ann <- m.symbol.annotations) { ann.symbol.initialize } initJMethod(flags, params.map(p => p.symbol.annotations)) diff --git a/compiler/src/dotty/tools/backend/jvm/BTypes.scala b/compiler/src/dotty/tools/backend/jvm/BTypes.scala index b49b97c58ec9..fb399461a03c 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypes.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypes.scala @@ -649,14 +649,13 @@ abstract class BTypes { def innerClassAttributeEntry: Option[InnerClassEntry] = info.nestedInfo map { case NestedInfo(_, outerName, innerName, isStaticNestedClass) => + import GenBCodeOps.addFlagIf InnerClassEntry( internalName, outerName.orNull, innerName.orNull, - GenBCodeOps.mkFlags( - info.flags, - if (isStaticNestedClass) asm.Opcodes.ACC_STATIC else 0 - ) & ClassBType.INNER_CLASSES_FLAGS + info.flags.addFlagIf(isStaticNestedClass, asm.Opcodes.ACC_STATIC) + & ClassBType.INNER_CLASSES_FLAGS ) } diff --git a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala index beea05e0dcc1..7fb716b00782 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala @@ -304,39 +304,36 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes { val finalFlag = sym.is(Final) && !toDenot(sym).isClassConstructor && !sym.is(Mutable) && !sym.enclosingClass.is(Trait) import asm.Opcodes._ - GenBCodeOps.mkFlags( - if (privateFlag) ACC_PRIVATE else ACC_PUBLIC, - if (sym.is(Deferred) || sym.isOneOf(AbstractOrTrait)) ACC_ABSTRACT else 0, - if (sym.isInterface) ACC_INTERFACE else 0, - - if (finalFlag && + import GenBCodeOps.addFlagIf + 0 .addFlagIf(privateFlag, ACC_PRIVATE) + .addFlagIf(!privateFlag, ACC_PUBLIC) + .addFlagIf(sym.is(Deferred) || sym.isOneOf(AbstractOrTrait), ACC_ABSTRACT) + .addFlagIf(sym.isInterface, ACC_INTERFACE) + .addFlagIf(finalFlag // Primitives are "abstract final" to prohibit instantiation // without having to provide any implementations, but that is an // illegal combination of modifiers at the bytecode level so // suppress final if abstract if present. - !sym.isOneOf(AbstractOrTrait) && + && !sym.isOneOf(AbstractOrTrait) // Mixin forwarders are bridges and can be final, but final bridges confuse some frameworks - !sym.is(Bridge)) - ACC_FINAL else 0, - - if (sym.isStaticMember) ACC_STATIC else 0, - if (sym.is(Bridge)) ACC_BRIDGE | ACC_SYNTHETIC else 0, - if (sym.is(Artifact)) ACC_SYNTHETIC else 0, - if (sym.isClass && !sym.isInterface) ACC_SUPER else 0, - if (sym.isAllOf(JavaEnumTrait)) ACC_ENUM else 0, - if (sym.is(JavaVarargs)) ACC_VARARGS else 0, - if (sym.is(Synchronized)) ACC_SYNCHRONIZED else 0, - if (sym.isDeprecated) ACC_DEPRECATED else 0, - if (sym.is(Enum)) ACC_ENUM else 0 - ) + && !sym.is(Bridge), ACC_FINAL) + .addFlagIf(sym.isStaticMember, ACC_STATIC) + .addFlagIf(sym.is(Bridge), ACC_BRIDGE | ACC_SYNTHETIC) + .addFlagIf(sym.is(Artifact), ACC_SYNTHETIC) + .addFlagIf(sym.isClass && !sym.isInterface, ACC_SUPER) + .addFlagIf(sym.isAllOf(JavaEnumTrait), ACC_ENUM) + .addFlagIf(sym.is(JavaVarargs), ACC_VARARGS) + .addFlagIf(sym.is(Synchronized), ACC_SYNCHRONIZED) + .addFlagIf(sym.isDeprecated, ACC_DEPRECATED) + .addFlagIf(sym.is(Enum), ACC_ENUM) } def javaFieldFlags(sym: Symbol) = { import asm.Opcodes._ - javaFlags(sym) | GenBCodeOps.mkFlags( - if (sym.hasAnnotation(TransientAttr)) ACC_TRANSIENT else 0, - if (sym.hasAnnotation(VolatileAttr)) ACC_VOLATILE else 0, - if (sym.is(Mutable)) 0 else ACC_FINAL - ) + import GenBCodeOps.addFlagIf + javaFlags(sym) + .addFlagIf(sym.hasAnnotation(TransientAttr), ACC_TRANSIENT) + .addFlagIf(sym.hasAnnotation(VolatileAttr), ACC_VOLATILE) + .addFlagIf(!sym.is(Mutable), ACC_FINAL) } } diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index 181e4c18f452..efc0256af177 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -50,9 +50,13 @@ class GenBCode extends Phase { myOutput } + private var myPrimitives: DottyPrimitives = null + def run(using Context): Unit = - GenBCodePipeline( - DottyBackendInterface(outputDir, superCallsMap) + if myPrimitives == null then myPrimitives = new DottyPrimitives(ctx) + new GenBCodePipeline( + DottyBackendInterface(outputDir, superCallsMap), + myPrimitives ).run(ctx.compilationUnit.tpdTree) @@ -74,7 +78,7 @@ object GenBCode { val name: String = "genBCode" } -class GenBCodePipeline(val int: DottyBackendInterface)(using Context) extends BCodeSyncAndTry { +class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrimitives)(using Context) extends BCodeSyncAndTry { import DottyBackendInterface.symExtensions private var tree: Tree = _ diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCodeOps.scala b/compiler/src/dotty/tools/backend/jvm/GenBCodeOps.scala index 884b86751b03..210e47566cb9 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCodeOps.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCodeOps.scala @@ -7,7 +7,8 @@ import scala.tools.asm object GenBCodeOps extends GenBCodeOps class GenBCodeOps { - def mkFlags(args: Int*) = args.foldLeft(0)(_ | _) + extension (flags: Int) + def addFlagIf(cond: Boolean, flag: Int): Int = if cond then flags | flag else flags final val PublicStatic = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC final val PublicStaticFinal = asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL diff --git a/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala b/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala index e1b6d8d922f7..72f9675c7b09 100644 --- a/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala +++ b/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala @@ -133,16 +133,15 @@ class DottyPrimitives(ictx: Context) { } def addPrimitives(cls: Symbol, method: TermName, code: Int)(using Context): Unit = { - val alts = cls.info.member(method).alternatives.map(_.symbol) - if (alts.isEmpty) + val alts = cls.info.member(method).alternatives + if alts.isEmpty then report.error(s"Unknown primitive method $cls.$method") - else alts foreach (s => + else for d <- alts do + val s = d.symbol addPrimitive(s, - s.info.paramInfoss match { - case List(tp :: _) if code == ADD && tp =:= ctx.definitions.StringType => CONCAT - case _ => code - } - ) + s.info.paramInfoss match + case (tp :: _) :: Nil if code == ADD && tp =:= ctx.definitions.StringType => CONCAT + case _ => code ) } diff --git a/compiler/src/dotty/tools/dotc/Bench.scala b/compiler/src/dotty/tools/dotc/Bench.scala index 3d264141eb71..e7a61fd558d7 100644 --- a/compiler/src/dotty/tools/dotc/Bench.scala +++ b/compiler/src/dotty/tools/dotc/Bench.scala @@ -22,9 +22,12 @@ object Bench extends Driver: override def doCompile(compiler: Compiler, fileNames: List[String])(using Context): Reporter = times = new Array[Int](numRuns) var reporter: Reporter = emptyReporter + val stats = ctx.settings.YdetailedStats.value for i <- 0 until numRuns do val start = System.nanoTime() - reporter = super.doCompile(compiler, fileNames) + reporter = inContext(ctx.fresh.setSetting(ctx.settings.YdetailedStats, stats && i == numRuns - 1)) { + super.doCompile(compiler, fileNames) + } times(i) = ((System.nanoTime - start) / 1000000).toInt println(s"time elapsed: ${times(i)}ms") if ctx.settings.Xprompt.value then @@ -33,11 +36,10 @@ object Bench extends Driver: println() reporter - def extractNumArg(args: Array[String], name: String, default: Int = 1): (Int, Array[String]) = { - val pos = args indexOf name - if (pos < 0) (default, args) - else (args(pos + 1).toInt, (args take pos) ++ (args drop (pos + 2))) - } + def extractNumArg(args: Array[String], name: String, default: Int = 1): (Int, Array[String]) = + val pos = args.indexOf(name) + if pos < 0 then (default, args) + else (args(pos + 1).toInt, args.take(pos) ++ args.drop(pos + 2)) def reportTimes() = val best = times.sorted diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 19f8cc586279..34237f3d04e2 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -594,9 +594,9 @@ object desugar { (ordinalMethLit(ordinal) :: enumLabelLit(className.toString) :: Nil, scaffolding) else (Nil, Nil) def copyMeths = { - val hasRepeatedParam = constrVparamss.exists(_.exists { + val hasRepeatedParam = constrVparamss.nestedExists { case ValDef(_, tpt, _) => isRepeated(tpt) - }) + } if (mods.is(Abstract) || hasRepeatedParam) Nil // cannot have default arguments for repeated parameters, hence copy method is not issued else { def copyDefault(vparam: ValDef) = diff --git a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala index 4ed857678b26..74a169126eaa 100644 --- a/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala +++ b/compiler/src/dotty/tools/dotc/ast/DesugarEnums.scala @@ -297,7 +297,7 @@ object DesugarEnums { case parent => parent.isType && typeHasRef(parent) } - vparamss.exists(_.exists(valDefHasRef)) || parents.exists(parentHasRef) + vparamss.nestedExists(valDefHasRef) || parents.exists(parentHasRef) } /** A pair consisting of diff --git a/compiler/src/dotty/tools/dotc/ast/Trees.scala b/compiler/src/dotty/tools/dotc/ast/Trees.scala index a293b0e9c1bc..82702f67825d 100644 --- a/compiler/src/dotty/tools/dotc/ast/Trees.scala +++ b/compiler/src/dotty/tools/dotc/ast/Trees.scala @@ -174,7 +174,7 @@ object Trees { def toList: List[Tree[T]] = this :: Nil /** if this tree is the empty tree, the alternative, else this tree */ - def orElse[U >: Untyped <: T](that: => Tree[U]): Tree[U] = + inline def orElse[U >: Untyped <: T](inline that: Tree[U]): Tree[U] = if (this eq genericEmptyTree) that else this /** The number of nodes in this tree */ diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index b2258dee613d..179b40a18bb4 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -674,7 +674,7 @@ object Contexts { final def retractMode(mode: Mode): c.type = c.setMode(c.mode &~ mode) } - private def exploreCtx(using Context): Context = + private def exploreCtx(using Context): FreshContext = util.Stats.record("explore") val base = ctx.base import base._ @@ -701,6 +701,10 @@ object Contexts { val ectx = exploreCtx try op(using ectx) finally wrapUpExplore(ectx) + inline def exploreInFreshCtx[T](inline op: FreshContext ?=> T)(using Context): T = + val ectx = exploreCtx + try op(using ectx) finally wrapUpExplore(ectx) + private def changeOwnerCtx(owner: Symbol)(using Context): Context = val base = ctx.base import base._ diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 8a9ed11fca03..7e6fb0c7f252 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -195,12 +195,16 @@ object Decorators { } extension [T, U](xss: List[List[T]]) - def nestedMap(f: T => U): List[List[U]] = - xss.map(_.map(f)) + def nestedMap(f: T => U): List[List[U]] = xss match + case xs :: xss1 => xs.map(f) :: xss1.nestedMap(f) + case nil => Nil def nestedMapConserve(f: T => U): List[List[U]] = xss.mapconserve(_.mapconserve(f)) def nestedZipWithConserve(yss: List[List[U]])(f: (T, U) => T): List[List[T]] = xss.zipWithConserve(yss)((xs, ys) => xs.zipWithConserve(ys)(f)) + def nestedExists(p: T => Boolean): Boolean = xss match + case xs :: xss1 => xs.exists(p) || xss1.nestedExists(p) + case nil => false end extension extension (text: Text) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 8263bab70aca..234f1b174919 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -844,8 +844,6 @@ class Definitions { @tu lazy val Not_value: Symbol = NotClass.companionModule.requiredMethod(nme.value) @tu lazy val ValueOfClass: ClassSymbol = requiredClass("scala.ValueOf") - @tu lazy val StatsModule: Symbol = requiredModule("dotty.tools.dotc.util.Stats") - @tu lazy val Stats_doRecord: Symbol = StatsModule.requiredMethod("doRecord") @tu lazy val FromDigitsClass: ClassSymbol = requiredClass("scala.util.FromDigits") @tu lazy val FromDigits_WithRadixClass: ClassSymbol = requiredClass("scala.util.FromDigits.WithRadix") diff --git a/compiler/src/dotty/tools/dotc/core/Denotations.scala b/compiler/src/dotty/tools/dotc/core/Denotations.scala index 73e98461046e..752ab3aaa955 100644 --- a/compiler/src/dotty/tools/dotc/core/Denotations.scala +++ b/compiler/src/dotty/tools/dotc/core/Denotations.scala @@ -237,7 +237,7 @@ object Denotations { def mapInfo(f: Type => Type)(using Context): Denotation /** If this denotation does not exist, fallback to alternative */ - final def orElse(that: => Denotation): Denotation = if (this.exists) this else that + inline def orElse(inline that: Denotation): Denotation = if (this.exists) this else that /** The set of alternative single-denotations making up this denotation */ final def alternatives: List[SingleDenotation] = altsWith(alwaysTrue) @@ -596,7 +596,7 @@ object Denotations { def mapInfo(f: Type => Type)(using Context): SingleDenotation = derivedSingleDenotation(symbol, f(info)) - def orElse(that: => SingleDenotation): SingleDenotation = if (this.exists) this else that + inline def orElse(inline that: SingleDenotation): SingleDenotation = if (this.exists) this else that def altsWith(p: Symbol => Boolean): List[SingleDenotation] = if (exists && p(symbol)) this :: Nil else Nil diff --git a/compiler/src/dotty/tools/dotc/core/NameKinds.scala b/compiler/src/dotty/tools/dotc/core/NameKinds.scala index 6d3bd93f4a6d..28791b168aeb 100644 --- a/compiler/src/dotty/tools/dotc/core/NameKinds.scala +++ b/compiler/src/dotty/tools/dotc/core/NameKinds.scala @@ -124,6 +124,7 @@ object NameKinds { case class QualInfo(name: SimpleName) extends Info with QualifiedInfo { override def map(f: SimpleName => SimpleName): NameInfo = new QualInfo(f(name)) override def toString: String = s"$infoString $name" + override def hashCode = scala.runtime.ScalaRunTime._hashCode(this) * 31 + kind.hashCode } def apply(qual: TermName, name: SimpleName): TermName = @@ -173,6 +174,7 @@ object NameKinds { type ThisInfo = NumberedInfo case class NumberedInfo(val num: Int) extends Info with NameKinds.NumberedInfo { override def toString: String = s"$infoString $num" + override def hashCode = scala.runtime.ScalaRunTime._hashCode(this) * 31 + kind.hashCode } def apply(qual: TermName, num: Int): TermName = qual.derived(new NumberedInfo(num)) @@ -371,6 +373,7 @@ object NameKinds { case class SignedInfo(sig: Signature) extends Info { assert(sig ne Signature.NotAMethod) override def toString: String = s"$infoString $sig" + override def hashCode = scala.runtime.ScalaRunTime._hashCode(this) * 31 + kind.hashCode } type ThisInfo = SignedInfo diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index a8c1a1da3078..9f6a9abd2616 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -892,7 +892,7 @@ object SymDenotations { else if is(NoDefaultParams) then false else val result = - rawParamss.exists(_.exists(_.is(HasDefault))) + rawParamss.nestedExists(_.is(HasDefault)) || allOverriddenSymbols.exists(_.hasDefaultParams) setFlag(if result then HasDefaultParams else NoDefaultParams) result @@ -1549,7 +1549,13 @@ object SymDenotations { completeChildrenIn(companionClass) setFlag(ChildrenQueried) - annotations.collect { case Annotation.Child(child) => child }.reverse + /** The children recorded in `annots`, in reverse order */ + def getChildren(annots: List[Annotation], acc: List[Symbol]): List[Symbol] = annots match + case Annotation.Child(child) :: annots1 => getChildren(annots1, child :: acc) + case _ :: annots1 => getChildren(annots1, acc) + case nil => acc + + getChildren(annotations, Nil) end children } diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 9b0b42734381..7ba320159531 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -239,7 +239,7 @@ object Symbols { } /** This symbol, if it exists, otherwise the result of evaluating `that` */ - def orElse(that: => Symbol)(using Context): Symbol = + inline def orElse(inline that: Symbol)(using Context): Symbol = if (this.exists) this else that /** If this symbol satisfies predicate `p` this symbol, otherwise `NoSymbol` */ diff --git a/compiler/src/dotty/tools/dotc/core/TyperState.scala b/compiler/src/dotty/tools/dotc/core/TyperState.scala index 8b4b6a476d1b..48b067c86766 100644 --- a/compiler/src/dotty/tools/dotc/core/TyperState.scala +++ b/compiler/src/dotty/tools/dotc/core/TyperState.scala @@ -113,9 +113,12 @@ class TyperState() { * isApplicableSafe but also for (e.g. erased-lubs.scala) as well as * many parts of dotty itself. */ - def commit()(using Context): Unit = { - Stats.record("typerState.commit") + def commit()(using Context): Unit = assert(isCommittable) + uncheckedCommit() + + def uncheckedCommit()(using Context): Unit = { + Stats.record("typerState.commit") val targetState = ctx.typerState if constraint ne targetState.constraint then Stats.record("typerState.commit.new constraint") diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 8c3883cfd2f6..b630947fca97 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -786,7 +786,8 @@ object Types { */ final def memberNames(keepOnly: NameFilter, pre: Type = this)(using Context): Set[Name] = this match { case tp: ClassInfo => - tp.cls.classDenot.memberNames(keepOnly) filter (keepOnly(pre, _)) + val names = tp.cls.classDenot.memberNames(keepOnly) + if keepOnly.isStable then names else names.filter(keepOnly(pre, _)) case tp: RefinedType => val ns = tp.parent.memberNames(keepOnly, pre) if (keepOnly(pre, tp.refinedName)) ns + tp.refinedName else ns @@ -3216,14 +3217,15 @@ object Types { if ((paramNames eq this.paramNames) && (paramInfos eq this.paramInfos) && (resType eq this.resType)) this else newLikeThis(paramNames, paramInfos, resType) + protected def substParams(pinfos: List[PInfo], from: LambdaType, to: This)(using Context): List[PInfo] = pinfos match + case pinfos @ (pinfo :: rest) => + pinfos.derivedCons(pinfo.subst(from, to).asInstanceOf[PInfo], substParams(rest, from, to)) + case nil => + nil + def newLikeThis(paramNames: List[ThisName], paramInfos: List[PInfo], resType: Type)(using Context): This = - def substParams(pinfos: List[PInfo], to: This): List[PInfo] = pinfos match - case pinfos @ (pinfo :: rest) => - pinfos.derivedCons(pinfo.subst(this, to).asInstanceOf[PInfo], substParams(rest, to)) - case nil => - nil companion(paramNames)( - x => substParams(paramInfos, x), + x => substParams(paramInfos, this, x), x => resType.subst(this, x)) protected def prefixString: String @@ -3370,17 +3372,10 @@ object Types { else resultType } - abstract case class MethodType(paramNames: List[TermName])( - paramInfosExp: MethodType => List[Type], - resultTypeExp: MethodType => Type) - extends MethodOrPoly with TermLambda with NarrowCached { thisMethodType => + abstract case class MethodType(paramNames: List[TermName]) + extends MethodOrPoly, TermLambda, NarrowCached { thisMethodType => type This = MethodType - - val paramInfos: List[Type] = paramInfosExp(this) - val resType: Type = resultTypeExp(this) - assert(resType.exists) - def companion: MethodTypeCompanion final override def isJavaMethod: Boolean = companion eq JavaMethodType @@ -3396,6 +3391,57 @@ object Types { companion.eq(ContextualMethodType) || companion.eq(ErasedContextualMethodType) + /** A type is _clean_ if a substituting this method type for some other method + * type in a binder substitution yields the type itself. This is the case if: + * - the type does not reference this method type + * - the type does not contain parts that are mapped to something else in + * a type map. In particular, it does not contain instantiated type variables, + * skolems, or LazyRefs. + * The purpose for having this method is to avoid binder substitutions + * if the new method type elements are all known to be clean. + * The method is a conservative approximation. It also returns false for + * some less common types that are not worth chasing down in detail. + */ + private def isClean(tp: Type)(using Context): Boolean = tp match + case tp: NamedType => + tp.symbol.is(Package) || (tp.prefix eq NoPrefix) || isClean(tp.prefix) + case tp: AppliedType => + isClean(tp.tycon) && isClean(tp.args) + case tp: BoundType => + tp.binder ne this + case _: ThisType => + true + case tp: TypeVar => + !tp.instanceOpt.exists + case RefinedType(parent, _, refinedInfo) => + isClean(parent) && isClean(refinedInfo) + case TypeAlias(alias) => + isClean(alias) + case TypeBounds(lo, hi) => + isClean(lo) && isClean(hi) + case tp: AnnotatedType => + isClean(tp.underlying) + case tp: AndOrType => + isClean(tp.tp1) && isClean(tp.tp2) + case WildcardType(bounds) => + isClean(bounds) + case mt: MethodType => + isClean(mt.paramInfos) && isClean(mt.resType) + case _: TypeErasure.ErasedValueType | _: ConstantType | NoType => + true + case _ => + false + + private def isClean(tps: List[Type])(using Context): Boolean = tps match + case tp :: tps1 => isClean(tp) && isClean(tps1) + case nil => true + + final override def newLikeThis(paramNames: List[TermName], paramInfos: List[Type], resType: Type)(using Context): This = + if isClean(resType) && isClean(paramInfos) then + companion(paramNames, paramInfos, resType) + else + companion(paramNames, paramInfos, resType, this) + protected[dotc] def computeSignature(using Context): Signature = { val params = if (isErasedMethod) Nil else paramInfos resultSignature.prependTermParams(params, isJavaMethod) @@ -3404,8 +3450,24 @@ object Types { protected def prefixString: String = companion.prefixString } - final class CachedMethodType(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type, val companion: MethodTypeCompanion) - extends MethodType(paramNames)(paramInfosExp, resultTypeExp) + final class CachedMethodType(paramNames: List[TermName]) + (paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type, + val companion: MethodTypeCompanion) + extends MethodType(paramNames): + val paramInfos: List[Type] = paramInfosExp(this) + val resType: Type = resultTypeExp(this) + assert(resType.exists) + + final class CopiedCachedMethodType(paramNames: List[TermName], + @constructorOnly initParamInfos: List[Type], @constructorOnly initResType: Type, + @constructorOnly from: MethodType, val companion: MethodTypeCompanion)(using @constructorOnly ctx: Context) + extends MethodType(paramNames): + val paramInfos: List[Type] = substParams(initParamInfos, from, this) + val resType: Type = initResType.subst(from, this) + + final class SimpleCachedMethodType(paramNames: List[TermName], + val paramInfos: List[Type], val resType: Type, val companion: MethodTypeCompanion) + extends MethodType(paramNames) abstract class LambdaTypeCompanion[N <: Name, PInfo <: Type, LT <: LambdaType] { def syntheticParamName(n: Int): N @@ -3470,7 +3532,14 @@ object Types { } final def apply(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type)(using Context): MethodType = - checkValid(unique(new CachedMethodType(paramNames)(paramInfosExp, resultTypeExp, self))) + checkValid(unique(CachedMethodType(paramNames)(paramInfosExp, resultTypeExp, self))) + + /** Method type with given fields where references to `origin` are substituted with the result */ + final def apply(paramNames: List[TermName], paramInfos: List[Type], resultType: Type, origin: MethodType)(using Context): MethodType = + checkValid(unique(CopiedCachedMethodType(paramNames, paramInfos, resultType, origin, self))) + + override def apply(paramNames: List[TermName], paramInfos: List[Type], resultType: Type)(using Context): MethodType = + checkValid(unique(SimpleCachedMethodType(paramNames, paramInfos, resultType, self))) def checkValid(mt: MethodType)(using Context): mt.type = { if (Config.checkMethodTypes) @@ -3552,7 +3621,7 @@ object Types { * * Variances are stored in the `typeParams` list of the lambda. */ - class HKTypeLambda(val paramNames: List[TypeName], @constructorOnly variances: List[Variance])( + class HKTypeLambda(val paramNames: List[TypeName], /*@constructorOnly*/ variances: List[Variance])( paramInfosExp: HKTypeLambda => List[TypeBounds], resultTypeExp: HKTypeLambda => Type) extends HKLambda with TypeLambda { type This = HKTypeLambda @@ -5642,6 +5711,11 @@ object Types { */ abstract class NameFilter { def apply(pre: Type, name: Name)(using Context): Boolean + + /** Filter does not need to be rechecked with full prefix, if it + * has been already checked for the class denotation of the prefix + */ + def isStable: Boolean } /** A filter for names of abstract types of a given type */ @@ -5651,6 +5725,7 @@ object Types { val mbr = pre.nonPrivateMember(name) mbr.symbol.is(Deferred) && mbr.info.isInstanceOf[RealTypeBounds] } + def isStable = false } /** A filter for names of abstract types of a given type */ @@ -5660,12 +5735,14 @@ object Types { val mbr = pre.member(name) mbr.symbol.isType && !mbr.symbol.isClass } + def isStable = false } /** A filter for names of deferred term definitions of a given type */ object abstractTermNameFilter extends NameFilter { def apply(pre: Type, name: Name)(using Context): Boolean = name.isTermName && pre.nonPrivateMember(name).hasAltWith(_.symbol.is(Deferred)) + def isStable = false } /** A filter for names of type aliases of a given type */ @@ -5675,19 +5752,23 @@ object Types { val mbr = pre.nonPrivateMember(name) mbr.symbol.isAliasType } + def isStable = false } object typeNameFilter extends NameFilter { def apply(pre: Type, name: Name)(using Context): Boolean = name.isTypeName + def isStable = true } object fieldFilter extends NameFilter { def apply(pre: Type, name: Name)(using Context): Boolean = name.isTermName && (pre member name).hasAltWith(!_.symbol.is(Method)) + def isStable = true } object takeAllFilter extends NameFilter { def apply(pre: Type, name: Name)(using Context): Boolean = true + def isStable = true } object implicitFilter extends NameFilter { @@ -5696,6 +5777,7 @@ object Types { * no post-filtering is needed. */ def apply(pre: Type, name: Name)(using Context): Boolean = true + def isStable = true } // ----- Debug --------------------------------------------------------- diff --git a/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala b/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala index 1294b6fe0a72..aa11d31e2d7f 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/NameBuffer.scala @@ -97,14 +97,14 @@ class NameBuffer extends TastyBuffer(10000) { } } - override def assemble(): Unit = { + override def assemble(): Unit = var i = 0 - for ((name, ref) <- nameRefs) { + val nr = nameRefs.iterator + while nr.hasNext do + val (name, ref) = nr.next() assert(ref.index == i) i += 1 pickleNameContents(name) - } - } } object NameBuffer { diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala index fa00db183e0b..636795cb259a 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyUnpickler.scala @@ -31,7 +31,7 @@ class TastyUnpickler(reader: TastyReader) { def this(bytes: Array[Byte]) = this(new TastyReader(bytes)) - private val sectionReader = new mutable.HashMap[String, TastyReader] + private val sectionReader = util.HashMap[String, TastyReader]() val nameAtRef: NameTable = new NameTable private def readName(): TermName = nameAtRef(readNameRef()) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index fe466c5b2836..78af63e4a801 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -59,19 +59,19 @@ class TreeUnpickler(reader: TastyReader, import tpd._ /** A map from addresses of definition entries to the symbols they define */ - private val symAtAddr = new mutable.HashMap[Addr, Symbol] + private val symAtAddr = util.HashMap[Addr, Symbol]() /** A temporary map from addresses of definition entries to the trees they define. * Used to remember trees of symbols that are created by a completion. Emptied * once the tree is inlined into a larger tree. */ - private val treeAtAddr = new mutable.HashMap[Addr, Tree] + private val treeAtAddr = util.HashMap[Addr, Tree]() /** A map from addresses of type entries to the types they define. * Currently only populated for types that might be recursively referenced * from within themselves (i.e. RecTypes, LambdaTypes). */ - private val typeAtAddr = new mutable.HashMap[Addr, Type] + private val typeAtAddr = util.HashMap[Addr, Type]() /** The root symbol denotation which are defined by the Tasty file associated with this * TreeUnpickler. Set by `enterTopLevel`. @@ -751,11 +751,11 @@ class TreeUnpickler(reader: TastyReader, * or else read definition. */ def readIndexedDef()(using Context): Tree = treeAtAddr.remove(currentAddr) match { - case Some(tree) => + case tree: Tree => assert(tree != PoisonTree, s"Cyclic reference while unpickling definition at address ${currentAddr.index} in unit ${ctx.compilationUnit}") skipTree() tree - case none => + case null => val start = currentAddr treeAtAddr(start) = PoisonTree val tree = readNewDef() diff --git a/compiler/src/dotty/tools/dotc/interactive/Completion.scala b/compiler/src/dotty/tools/dotc/interactive/Completion.scala index 79c4f52fb0ba..a4070bca858a 100644 --- a/compiler/src/dotty/tools/dotc/interactive/Completion.scala +++ b/compiler/src/dotty/tools/dotc/interactive/Completion.scala @@ -319,6 +319,7 @@ object Completion { private object completionsFilter extends NameFilter { def apply(pre: Type, name: Name)(using Context): Boolean = !name.isConstructorName && name.toTermName.info.kind == SimpleNameKind + def isStable = true } } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 5aba3e43e368..f31dfe675d88 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -3856,7 +3856,7 @@ object Parsers { val problem = tree match case tree: MemberDef if !(tree.mods.flags & ModifierFlags).isEmpty => i"refinement cannot be ${(tree.mods.flags & ModifierFlags).flagStrings().mkString("`", "`, `", "`")}" - case tree: DefDef if tree.vparamss.exists(_.exists(!_.rhs.isEmpty)) => + case tree: DefDef if tree.vparamss.nestedExists(!_.rhs.isEmpty) => i"refinement cannot have default arguments" case tree: ValOrDefDef => if tree.rhs.isEmpty then "" diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index be25469c549d..1bca5e31c466 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -107,7 +107,7 @@ object Formatting { private type Recorded = Symbol | ParamRef | SkolemType private case class SeenKey(str: String, isType: Boolean) - private class Seen extends mutable.HashMap[SeenKey, List[Recorded]] { + private class Seen extends util.HashMap[SeenKey, List[Recorded]] { override def default(key: SeenKey) = Nil diff --git a/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala b/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala index fb4c92c12f83..a54c0f30f9fa 100644 --- a/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala +++ b/compiler/src/dotty/tools/dotc/reporting/UniqueMessagePositions.scala @@ -2,7 +2,6 @@ package dotty.tools package dotc package reporting -import scala.collection.mutable import util.SourceFile import core.Contexts._ @@ -10,7 +9,7 @@ import core.Contexts._ * are suppressed, unless they are of increasing severity. */ trait UniqueMessagePositions extends Reporter { - private val positions = new mutable.HashMap[(SourceFile, Int), Int] + private val positions = util.HashMap[(SourceFile, Int), Int]() /** Logs a position and returns true if it was already logged. * @note Two positions are considered identical for logging if they have the same point. diff --git a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala index 5eeee5dc1dd3..7af896582e43 100644 --- a/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala +++ b/compiler/src/dotty/tools/dotc/rewrites/Rewrites.scala @@ -10,7 +10,7 @@ import dotty.tools.dotc.reporting.Reporter /** Handles rewriting of Scala2 files to Dotty */ object Rewrites { - private class PatchedFiles extends mutable.HashMap[SourceFile, Patches] + private class PatchedFiles extends util.HashMap[SourceFile, Patches] private case class Patch(span: Span, replacement: String) { def delta = replacement.length - (span.end - span.start) @@ -83,7 +83,7 @@ object Rewrites { /** If -rewrite is set, apply all patches and overwrite patched source files. */ def writeBack()(using Context): Unit = - for (rewrites <- ctx.settings.rewrite.value; source <- rewrites.patched.keys) { + for (rewrites <- ctx.settings.rewrite.value; source <- rewrites.patched.keysIterator) { report.echo(s"[patched file ${source.file.path}]") rewrites.patched(source).writeBack() } diff --git a/compiler/src/dotty/tools/dotc/transform/CountOuterAccesses.scala b/compiler/src/dotty/tools/dotc/transform/CountOuterAccesses.scala index 27a7907b266e..73f2fe42c5a1 100644 --- a/compiler/src/dotty/tools/dotc/transform/CountOuterAccesses.scala +++ b/compiler/src/dotty/tools/dotc/transform/CountOuterAccesses.scala @@ -42,7 +42,7 @@ class CountOuterAccesses extends MiniPhase: // LambdaLift can create outer paths. These need to be known in this phase. /** The number of times an outer accessor that might be dropped is accessed */ - val outerAccessCount = new mutable.HashMap[Symbol, Int] { + val outerAccessCount = new util.HashMap[Symbol, Int] { override def default(s: Symbol): Int = 0 } diff --git a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala index 85e2a62c9172..ecf2995cf30a 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimRepeated.scala @@ -237,7 +237,7 @@ class ElimRepeated extends MiniPhase with InfoTransformer { thisPhase => /** Is there a repeated parameter in some parameter list? */ private def hasRepeatedParams(sym: Symbol)(using Context): Boolean = - sym.info.paramInfoss.flatten.exists(_.isRepeatedParam) + sym.info.paramInfoss.nestedExists(_.isRepeatedParam) /** Is this the type of a method that has a repeated parameter type as * its last parameter in the last parameter list? diff --git a/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala b/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala index 5f8f9c91faf3..5cbf355ad60c 100644 --- a/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala +++ b/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala @@ -1,4 +1,5 @@ -package dotty.tools.dotc +package dotty.tools +package dotc package transform import core._ @@ -27,25 +28,49 @@ class Instrumentation extends MiniPhase { thisPhase => override def isEnabled(using Context) = ctx.settings.Yinstrument.value - private val namesOfInterest = List( - "::", "+=", "toString", "newArray", "box", "toCharArray", - "map", "flatMap", "filter", "withFilter", "collect", "foldLeft", "foldRight", "take", - "reverse", "mapConserve", "mapconserve", "filterConserve", "zip", - "denotsNamed", "lookup", "lookupEntry", "lookupAll", "toList") - private var namesToRecord: Set[Name] = _ + private val collectionNamesOfInterest = List( + "map", "flatMap", "filter", "filterNot", "withFilter", "flatten", "take", + "reverse", "zip", "++", ":::", ":+", "distinct", "dropRight", "takeRight", "groupBy", "groupMap", + "interect", "mkString", "partition", "reverse_:::", + "sortBy", "sortWith", "sorted", "splitAt", "takeWhile", "transpose", "unzip", "unzip3", + "updated", "zipAll", "zipWithIndex", + "mapConserve", "mapconserve", "filterConserve", "zipWithConserve", "mapWithIndexConserve" + ) + + private val namesOfInterest = collectionNamesOfInterest ++ List( + "::", "+=", "toString", "newArray", "box", "toCharArray", "termName", "typeName", + "toList", "fresh") - private var consName: TermName = _ - private var consEqName: TermName = _ + private var namesToRecord: Set[Name] = _ + private var collectionNamesToRecord: Set[Name] = _ + private var Stats_doRecord: Symbol = _ + private var Stats_doRecordSize: Symbol = _ + private var CollectionIterableClass: ClassSymbol = _ override def prepareForUnit(tree: Tree)(using Context): Context = namesToRecord = namesOfInterest.map(_.toTermName).toSet + collectionNamesToRecord = collectionNamesOfInterest.map(_.toTermName).toSet + val StatsModule = requiredModule("dotty.tools.dotc.util.Stats") + Stats_doRecord = StatsModule.requiredMethod("doRecord") + Stats_doRecordSize = StatsModule.requiredMethod("doRecordSize") + CollectionIterableClass = requiredClass("scala.collection.Iterable") ctx private def record(category: String, tree: Tree)(using Context): Tree = { val key = Literal(Constant(s"$category@${tree.sourcePos.show}")) - ref(defn.Stats_doRecord).appliedTo(key, Literal(Constant(1))) + ref(Stats_doRecord).appliedTo(key, Literal(Constant(1))) } + private def recordSize(tree: Apply)(using Context): Tree = tree.fun match + case sel @ Select(qual, name) + if collectionNamesToRecord.contains(name) + && qual.tpe.widen.derivesFrom(CollectionIterableClass) => + val key = Literal(Constant(s"totalSize/${name} in ${qual.tpe.widen.classSymbol.name}@${tree.sourcePos.show}")) + val qual1 = ref(Stats_doRecordSize).appliedTo(key, qual).cast(qual.tpe.widen) + cpy.Apply(tree)(cpy.Select(sel)(qual1, name), tree.args) + case _ => + tree + private def ok(using Context) = !ctx.owner.ownersIterator.exists(_.name.toString.startsWith("Stats")) @@ -53,7 +78,7 @@ class Instrumentation extends MiniPhase { thisPhase => case Select(nu: New, _) => cpy.Block(tree)(record(i"alloc/${nu.tpe}", tree) :: Nil, tree) case ref: RefTree if namesToRecord.contains(ref.name) && ok => - cpy.Block(tree)(record(i"call/${ref.name}", tree) :: Nil, tree) + cpy.Block(tree)(record(i"call/${ref.name}", tree) :: Nil, recordSize(tree)) case _ => tree } diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 0d47b19249e7..5a11e6323dd5 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -22,12 +22,19 @@ object SymUtils { extension (self: Symbol) { - /** All traits implemented by a class or trait except for those inherited through the superclass. */ + /** All traits implemented by a class or trait except for those inherited + * through the superclass. Traits are given in the order they appear in the + * parents clause (which is the reverse of their order in baseClasses) + */ def directlyInheritedTraits(using Context): List[ClassSymbol] = { val superCls = self.asClass.superClass val baseClasses = self.asClass.baseClasses if (baseClasses.isEmpty) Nil - else baseClasses.tail.takeWhile(_ ne superCls).reverse + else + def recur(bcs: List[ClassSymbol], acc: List[ClassSymbol]): List[ClassSymbol] = bcs match + case bc :: bcs1 => if bc eq superCls then acc else recur(bcs1, bc :: acc) + case nil => acc + recur(baseClasses.tail, Nil) } /** All traits implemented by a class, except for those inherited through the superclass. diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 1376199c9162..65da39e326bf 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1647,7 +1647,7 @@ trait Applications extends Compatibility { * Two trials: First, without implicits or SAM conversions enabled. Then, * if the first finds no eligible candidates, with implicits and SAM conversions enabled. */ - def resolveOverloaded(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = + def resolveOverloaded(alts: List[TermRef], pt: Type)(using Context): List[TermRef] = util.Stats.trackTime("resolveOver ms") { record("resolveOverloaded") /** Is `alt` a method or polytype whose result type after the first value parameter @@ -1745,6 +1745,7 @@ trait Applications extends Compatibility { resolve(expanded).map(retract) } else resolve(alts) + } end resolveOverloaded /** This private version of `resolveOverloaded` does the bulk of the work of diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 07791b9d046b..89371bc540a3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -239,10 +239,12 @@ object Implicits: if refs.isEmpty && (!considerExtension || companionRefs.isEmpty) then Nil else - val nestedCtx = ctx.fresh.addMode(Mode.TypevarsMissContext) val candidates = new mutable.ListBuffer[Candidate] def tryCandidate(extensionOnly: Boolean)(ref: ImplicitRef) = - var ckind = explore(candidateKind(ref.underlyingRef))(using nestedCtx) + var ckind = exploreInFreshCtx { (using ctx: FreshContext) => + ctx.setMode(ctx.mode | Mode.TypevarsMissContext) + candidateKind(ref.underlyingRef) + } if extensionOnly then ckind &= Candidate.Extension if ckind != Candidate.None then candidates += Candidate(ref, ckind, level) @@ -974,6 +976,7 @@ trait Implicits: */ def inferImplicit(pt: Type, argument: Tree, span: Span)(using Context): SearchResult = trace(s"search implicit ${pt.show}, arg = ${argument.show}: ${argument.tpe.show}", implicits, show = true) { + Stats.trackTime("inferImplicit ms") { record("inferImplicit") assert(ctx.phase.allowsImplicitSearch, if (argument.isEmpty) i"missing implicit parameter of type $pt after typer" @@ -1013,12 +1016,12 @@ trait Implicits: } // If we are at the outermost implicit search then emit the implicit dictionary, if any. ctx.searchHistory.emitDictionary(span, result) - } + }} /** Try to typecheck an implicit reference */ def typedImplicit(cand: Candidate, pt: Type, argument: Tree, span: Span)(using Context): SearchResult = trace(i"typed implicit ${cand.ref}, pt = $pt, implicitsEnabled == ${ctx.mode is ImplicitsEnabled}", implicits, show = true) { if ctx.run.isCancelled then NoMatchingImplicitsFailure - else + else Stats.trackTime("typed implicit ms") { record("typedImplicit") val ref = cand.ref val generated: Tree = tpd.ref(ref).withSpan(span.startPos) @@ -1081,7 +1084,7 @@ trait Implicits: if (cand.isExtension) Applications.ExtMethodApply(adapted) else adapted SearchSuccess(returned, ref, cand.level)(ctx.typerState, ctx.gadt) - } + }} /** An implicit search; parameters as in `inferImplicit` */ class ImplicitSearch(protected val pt: Type, protected val argument: Tree, span: Span)(using Context): @@ -1297,8 +1300,14 @@ trait Implicits: private def searchImplicit(contextual: Boolean): SearchResult = val eligible = - if contextual then ctx.implicits.eligible(wildProto) - else implicitScope(wildProto).eligible + if contextual then + Stats.trackTime("contextual eligible ms") { + ctx.implicits.eligible(wildProto) + } + else + Stats.trackTime("implicit scope eligible ms") { + implicitScope(wildProto).eligible + } searchImplicit(eligible, contextual) match case result: SearchSuccess => result diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 962c52fed35a..ab82a480562c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -31,12 +31,13 @@ object Inferencing { * but only if the overall result of `isFullyDefined` is `true`. * Variables that are successfully minimized do not count as uninstantiated. */ - def isFullyDefined(tp: Type, force: ForceDegree.Value)(using Context): Boolean = { - val nestedCtx = ctx.fresh.setNewTyperState() - val result = new IsFullyDefinedAccumulator(force)(using nestedCtx).process(tp) - if (result) nestedCtx.typerState.commit() - result - } + def isFullyDefined(tp: Type, force: ForceDegree.Value)(using Context): Boolean = + val current = ctx + explore { + val result = IsFullyDefinedAccumulator(force).process(tp) + if result then ctx.typerState.uncheckedCommit()(using current) + result + } /** The fully defined type, where all type variables are forced. * Throws an error if type contains wildcards. diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index c85aee7aa65b..215b11a84042 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -29,6 +29,7 @@ object RefChecks { private val defaultMethodFilter = new NameFilter { def apply(pre: Type, name: Name)(using Context): Boolean = name.is(DefaultGetterName) + def isStable = true } /** Only one overloaded alternative is allowed to define default arguments */ @@ -490,10 +491,9 @@ object RefChecks { */ def missingTermSymbols: List[Symbol] = val buf = new mutable.ListBuffer[Symbol] - for bc <- clazz.baseClasses - sym <- bc.info.decls.toList - if sym.is(DeferredTerm) && !isImplemented(sym) && !ignoreDeferred(sym) - do buf += sym + for bc <- clazz.baseClasses; sym <- bc.info.decls.toList do + if sym.is(DeferredTerm) && !isImplemented(sym) && !ignoreDeferred(sym) + buf += sym buf.toList // 2. Check that only abstract classes have deferred members diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 5d0b94a45186..88cc4917a880 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -29,13 +29,12 @@ import EtaExpansion.etaExpand import TypeComparer.CompareResult import util.Spans._ import util.common._ -import util.{Property, SimpleIdentityMap, SrcPos} +import util.{Property, SimpleIdentityMap, SrcPos, Stats} import Applications.{ExtMethodApply, IntegratedTypeArgs, productSelectorTypes, wrapDefs} import collection.mutable import annotation.tailrec import Implicits._ -import util.Stats.record import config.Printers.{gadts, typr, debug} import config.Feature._ import config.SourceVersion._ @@ -434,7 +433,7 @@ class Typer extends Namer * (3) Change pattern Idents id (but not wildcards) to id @ _ */ def typedIdent(tree: untpd.Ident, pt: Type)(using Context): Tree = - record("typedIdent") + Stats.record("typedIdent") val name = tree.name def kind = if (name.isTermName) "" else "type " typr.println(s"typed ident $kind$name in ${ctx.owner}") @@ -551,7 +550,7 @@ class Typer extends Namer } def typedSelect(tree: untpd.Select, pt: Type)(using Context): Tree = { - record("typedSelect") + Stats.record("typedSelect") def typeSelectOnTerm(using Context): Tree = typedSelect(tree, pt, typedExpr(tree.qualifier, selectionProto(tree.name, pt, this))) @@ -582,7 +581,7 @@ class Typer extends Namer } def typedThis(tree: untpd.This)(using Context): Tree = { - record("typedThis") + Stats.record("typedThis") assignType(tree) } @@ -602,7 +601,7 @@ class Typer extends Namer def typedNumber(tree: untpd.Number, pt: Type)(using Context): Tree = { import scala.util.FromDigits._ import untpd.NumberKind._ - record("typedNumber") + Stats.record("typedNumber") val digits = tree.digits val target = pt.dealias def lit(value: Any) = Literal(Constant(value)).withSpan(tree.span) @@ -1223,21 +1222,24 @@ class Typer extends Namer } val desugared = - if (protoFormals.length == 1 && params.length != 1 && ptIsCorrectProduct(protoFormals.head)) { + if (protoFormals.length == 1 && params.length != 1 && ptIsCorrectProduct(protoFormals.head)) val isGenericTuple = protoFormals.head.derivesFrom(defn.TupleClass) && !defn.isTupleClass(protoFormals.head.typeSymbol) desugar.makeTupledFunction(params, fnBody, isGenericTuple) - } - else { - val inferredParams: List[untpd.ValDef] = - for ((param, i) <- params.zipWithIndex) yield - if (!param.tpt.isEmpty) param - else cpy.ValDef(param)( - tpt = untpd.TypeTree( - inferredParamType(param, protoFormal(i)).translateFromRepeated(toArray = false))) - desugar.makeClosure(inferredParams, fnBody, resultTpt, isContextual) - } + else + def inferredParams(params: List[untpd.ValDef], idx: Int): List[untpd.ValDef] = params match + case param :: rest => + val param1 = + if !param.tpt.isEmpty then param + else cpy.ValDef(param)( + tpt = untpd.TypeTree( + inferredParamType(param, protoFormal(idx)).translateFromRepeated(toArray = false))) + param1 :: inferredParams(rest, idx + 1) + case nil => + Nil + desugar.makeClosure(inferredParams(params, 0), fnBody, resultTpt, isContextual) + typed(desugared, pt) } @@ -2413,7 +2415,7 @@ class Typer extends Namer * at the present time */ def typedUnadapted(initTree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = { - record("typedUnadapted") + Stats.record("typedUnadapted") val xtree = expanded(initTree) xtree.removeAttachment(TypedAhead) match { case Some(ttree) => ttree @@ -2575,8 +2577,8 @@ class Typer extends Namer /** Typecheck and adapt tree, returning a typed tree. Parameters as for `typedUnadapted` */ def typed(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree = trace(i"typing $tree, pt = $pt", typr, show = true) { - record(s"typed $getClass") - record("typed total") + Stats.record(s"typed $getClass") + Stats.record("typed total") if ctx.phase.isTyper then assertPositioned(tree) if tree.source != ctx.source && tree.source.exists then @@ -2712,11 +2714,11 @@ class Typer extends Namer val nestedCtx = ctx.fresh.setNewTyperState() val result = op(using nestedCtx) if (nestedCtx.reporter.hasErrors && !nestedCtx.reporter.hasStickyErrors) { - record("tryEither.fallBack") + Stats.record("tryEither.fallBack") fallBack(result, nestedCtx.typerState) } else { - record("tryEither.commit") + Stats.record("tryEither.commit") nestedCtx.typerState.commit() result } @@ -2913,7 +2915,7 @@ class Typer extends Namer def adapt(tree: Tree, pt: Type, locked: TypeVars, tryGadtHealing: Boolean = true)(using Context): Tree = try trace(i"adapting $tree to $pt ${if (tryGadtHealing) "" else "(tryGadtHealing=false)" }\n", typr, show = true) { - record("adapt") + Stats.record("adapt") adapt1(tree, pt, locked, tryGadtHealing) } catch case ex: TypeError => errorTree(tree, ex, tree.srcPos.focus) diff --git a/compiler/src/dotty/tools/dotc/util/GenericHashMap.scala b/compiler/src/dotty/tools/dotc/util/GenericHashMap.scala index 134a94e8b888..f2a3f012f76c 100644 --- a/compiler/src/dotty/tools/dotc/util/GenericHashMap.scala +++ b/compiler/src/dotty/tools/dotc/util/GenericHashMap.scala @@ -111,9 +111,11 @@ abstract class GenericHashMap[Key, Value] k = keyAt(idx) k != null do + val eidx = index(hash(k)) if isDense - || index(hole - index(hash(k))) < limit * 2 - // hash(k) is then logically at or before hole; can be moved forward to fill hole + || index(eidx - (hole + 2)) > index(idx - (hole + 2)) + // entry `e` at `idx` can move unless `index(hash(e))` is in + // the (ring-)interval [hole + 2 .. idx] then setKey(hole, k) setValue(hole, valueAt(idx)) @@ -156,7 +158,7 @@ abstract class GenericHashMap[Key, Value] protected def growTable(): Unit = val oldTable = table val newLength = - if oldTable.length == DenseLimit then DenseLimit * 2 * roundToPower(capacityMultiple) + if table.length == DenseLimit * 2 then table.length * roundToPower(capacityMultiple) else table.length allocate(newLength) copyFrom(oldTable) diff --git a/compiler/src/dotty/tools/dotc/util/HashSet.scala b/compiler/src/dotty/tools/dotc/util/HashSet.scala index 8d73eada970f..bd764c523d83 100644 --- a/compiler/src/dotty/tools/dotc/util/HashSet.scala +++ b/compiler/src/dotty/tools/dotc/util/HashSet.scala @@ -109,9 +109,11 @@ class HashSet[T](initialCapacity: Int = 8, capacityMultiple: Int = 2) extends Mu e = entryAt(idx) e != null do + val eidx = index(hash(e)) if isDense - || index(hole - index(hash(e))) < limit - // hash(k) is then logically at or before hole; can be moved forward to fill hole + || index(eidx - (hole + 1)) > index(idx - (hole + 1)) + // entry `e` at `idx` can move unless `index(hash(e))` is in + // the (ring-)interval [hole + 1 .. idx] then setEntry(hole, e) hole = idx diff --git a/compiler/src/dotty/tools/dotc/util/ReadOnlyMap.scala b/compiler/src/dotty/tools/dotc/util/ReadOnlyMap.scala index 020303c18bc2..7eb3d8b4fea3 100644 --- a/compiler/src/dotty/tools/dotc/util/ReadOnlyMap.scala +++ b/compiler/src/dotty/tools/dotc/util/ReadOnlyMap.scala @@ -1,5 +1,6 @@ package dotty.tools package dotc.util +import collection.mutable.ListBuffer /** A class for the reading part of mutable or immutable maps. */ @@ -26,9 +27,12 @@ abstract class ReadOnlyMap[Key, Value]: def contains(key: Key): Boolean = lookup(key) != null def apply(key: Key): Value = lookup(key) match - case null => throw new NoSuchElementException(s"$key") + case null => default(key) case v => v.uncheckedNN + protected def default(key: Key): Value = + throw new NoSuchElementException(s"$key") + def toArray: Array[(Key, Value)] = val result = new Array[(Key, Value)](size) var idx = 0 @@ -37,5 +41,8 @@ abstract class ReadOnlyMap[Key, Value]: idx += 1 result + def toList: List[(Key, Value)] = + (new ListBuffer[(Key, Value)]() ++= iterator).toList + def toSeq: Seq[(Key, Value)] = toArray.toSeq diff --git a/compiler/src/dotty/tools/dotc/util/SourcePosition.scala b/compiler/src/dotty/tools/dotc/util/SourcePosition.scala index 79919442a3b2..35d16cff9f44 100644 --- a/compiler/src/dotty/tools/dotc/util/SourcePosition.scala +++ b/compiler/src/dotty/tools/dotc/util/SourcePosition.scala @@ -25,7 +25,7 @@ extends SrcPos, interfaces.SourcePosition, Showable { def point: Int = span.point - def line: Int = if (source.content().length != 0) source.offsetToLine(point) else -1 + def line: Int = if (source.length != 0) source.offsetToLine(point) else -1 /** Extracts the lines from the underlying source file as `Array[Char]`*/ def linesSlice: Array[Char] = diff --git a/compiler/src/dotty/tools/dotc/util/Stats.scala b/compiler/src/dotty/tools/dotc/util/Stats.scala index ecbf266866eb..a6c258180807 100644 --- a/compiler/src/dotty/tools/dotc/util/Stats.scala +++ b/compiler/src/dotty/tools/dotc/util/Stats.scala @@ -9,7 +9,7 @@ import collection.mutable @sharable object Stats { - final val enabled = false + inline val enabled = false var monitored: Boolean = false @@ -28,6 +28,10 @@ import collection.mutable hits(name) += n } + def doRecordSize(fn: String, coll: scala.collection.Iterable[_]): coll.type = + doRecord(fn, coll.size) + coll + inline def trackTime[T](fn: String)(inline op: T): T = if (enabled) doTrackTime(fn)(op) else op diff --git a/tests/pos-with-compiler/benchSets.scala b/tests/pos-with-compiler/benchSets.scala new file mode 100644 index 000000000000..3e1492e47980 --- /dev/null +++ b/tests/pos-with-compiler/benchSets.scala @@ -0,0 +1,127 @@ +type Elem = Object + +def newElem() = new Object() + +val MissFactor = 2 + +val Runs = 100 // number of runs to warm-up, and then number of runs to test +val ItersPerRun = 1000 +val elems = Array.fill(1024 * MissFactor)(newElem()) + +def testJava = + val set = java.util.HashMap[Elem, Elem]() + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set.put(e, e) + i += 1 + while i > 0 do + i -= 1 + if set.containsKey(elems(i)) then + count += 1 + iter += 1 + count + +def testJavaId = + val set = java.util.IdentityHashMap[Elem, Elem]() + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set.put(e, e) + i += 1 + while i > 0 do + i -= 1 + if set.containsKey(elems(i)) then + count += 1 + iter += 1 + count + +def testScalaMap = + val set = scala.collection.mutable.HashMap[Elem, Elem]() + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set.update(e, e) + i += 1 + while i > 0 do + i -= 1 + if set.contains(elems(i)) then + count += 1 + iter += 1 + count + +def testDottySet = + val set = dotty.tools.dotc.util.HashSet[Elem](64) + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set += e + i += 1 + while i > 0 do + i -= 1 + if set.contains(elems(i)) then + count += 1 + iter += 1 + count + +def testScalaSet = + val set = scala.collection.mutable.HashSet[Elem]() + var count = 0 + var iter = 0 + while iter < ItersPerRun do + var i = 0 + while i < elems.length do + val e = elems(i) + if i % MissFactor == 0 then + set += e + i += 1 + while i > 0 do + i -= 1 + if set.contains(elems(i)) then + count += 1 + iter += 1 + count + + + +val expected = (elems.size / MissFactor) * ItersPerRun + +def profile(name: String, op: => Int) = + for i <- 0 until 100 do assert(op == expected) + //System.gc() + val start = System.nanoTime() + var count = 0 + for i <- 0 until 100 do count += op + //println(count) + assert(count == expected * Runs) + println(f"$name took ${(System.nanoTime().toDouble - start)/1_000_000} ms") + +@main def Test = + profile("java.util.HashMap ", testJava) + profile("java.util.IdentityHashMap ", testJavaId) + profile("scala.collection.HashMap ", testScalaMap) + profile("scala.collection.HashSet ", testScalaSet) + profile("dotty.tools.dotc.HashSet ", testDottySet) + + profile("dotty.tools.dotc.HashSet ", testDottySet) + profile("scala.collection.HashSet ", testScalaSet) + profile("scala.collection.HashMap ", testScalaMap) + profile("java.util.IdentityHashMap ", testJavaId) + profile("java.util.HashMap ", testJava) + diff --git a/tests/run-with-compiler/settest.scala b/tests/run-with-compiler/settest.scala new file mode 100644 index 000000000000..2d930ca3fdf9 --- /dev/null +++ b/tests/run-with-compiler/settest.scala @@ -0,0 +1,71 @@ +trait Generator[+T]: + self => + def generate: T + def map[S](f: T => S) = new Generator[S]: + def generate: S = f(self.generate) + def flatMap[S](f: T => Generator[S]) = new Generator[S]: + def generate: S = f(self.generate).generate + +object Generator: + val NumLimit = 300 + val Iterations = 10000 + + given integers as Generator[Int]: + val rand = new java.util.Random + def generate = rand.nextInt() + + given booleans as Generator[Boolean] = + integers.map(x => x > 0) + + def range(end: Int): Generator[Int] = + integers.map(x => (x % end).abs) + + enum Op: + case Lookup, Update, Remove + export Op._ + + given ops as Generator[Op] = + range(10).map { + case 0 | 1 | 2 | 3 => Lookup + case 4 | 5 | 6 | 7 => Update + case 8 | 9 => Remove + } + + val nums: Generator[Integer] = range(NumLimit).map(Integer(_)) + +@main def Test = + import Generator._ + + val set1 = dotty.tools.dotc.util.HashSet[Int]() + val set2 = scala.collection.mutable.HashSet[Int]() + + def checkSame() = + assert(set1.size == set2.size) + for e <- set1.iterator do + assert(set2.contains(e)) + for e <- set2.iterator do + assert(set1.contains(e)) + + def lookupTest(num: Integer) = + val res1 = set1.contains(num) + val res2 = set2.contains(num) + assert(res1 == res2) + + def updateTest(num: Integer) = + lookupTest(num) + set1 += num + set2 += num + checkSame() + + def removeTest(num: Integer) = + //println(s"test remove $num") + set1 -= num + set2 -= num + checkSame() + + for i <- 0 until Iterations do + val num = nums.generate + Generator.ops.generate match + case Lookup => lookupTest(num) + case Update => updateTest(num) + case Remove => removeTest(num)