diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 6fe8caa9af2b..4e32634ae274 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -477,7 +477,7 @@ class DottyBackendInterface(outputDirectory: AbstractFile, val superCallsMap: Ma // unrelated change. ctx.base.settings.YnoGenericSig.value || sym.is(Flags.Artifact) - || sym.is(Flags.allOf(Flags.Method, Flags.Lifted)) + || sym.is(Flags.LiftedMethod) || sym.is(Flags.Bridge) ) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 3b31ffe7b363..f35aa98cca9b 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -97,6 +97,7 @@ class Compiler { List(new Constructors, // Collect initialization code in primary constructors // Note: constructors changes decls in transformTemplate, no InfoTransformers should be added after it new FunctionalInterfaces, // Rewrites closures to implement @specialized types of Functions. + new Instrumentation, // Count closure allocations under -Yinstrument-closures new GetClass) :: // Rewrites getClass calls on primitive types. List(new LinkScala2Impls, // Redirect calls to trait methods defined by Scala 2.x, so that they now go to their implementations new LambdaLift, // Lifts out nested functions to class scope, storing free variables in environments diff --git a/compiler/src/dotty/tools/dotc/ast/Positioned.scala b/compiler/src/dotty/tools/dotc/ast/Positioned.scala index f764366302da..7b789c017b73 100644 --- a/compiler/src/dotty/tools/dotc/ast/Positioned.scala +++ b/compiler/src/dotty/tools/dotc/ast/Positioned.scala @@ -51,8 +51,12 @@ abstract class Positioned(implicit @constructorOnly src: SourceFile) extends Pro else { val newpd: this.type = if (mySpan.isSynthetic) { - if (!mySpan.exists && span.exists) + if (!mySpan.exists && span.exists) { envelope(source, span.startPos) // fill in children spans + () // Note: the `()` is there to prevent some inefficient code from being generated. + // Without it we get an allocation of a span here since the result type of the `if` + // is `Any`, the lub of `Span` and `Unit`. + } this } else cloneIn(source) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 4bec4ee635d8..da8ae2f1576b 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -978,7 +978,10 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { } implicit class ListOfTreeDecorator(val xs: List[tpd.Tree]) extends AnyVal { - def tpes: List[Type] = xs map (_.tpe) + def tpes: List[Type] = xs match { + case x :: xs1 => x.tpe :: xs1.tpes + case nil => Nil + } } /** A trait for loaders that compute trees. Currently implemented just by DottyUnpickler. */ diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 2026488631ba..5e24485616bf 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -149,6 +149,9 @@ class ScalaSettings extends Settings.SettingGroup { val YnoDecodeStacktraces: Setting[Boolean] = BooleanSetting("-Yno-decode-stacktraces", "Show raw StackOverflow stacktraces, instead of decoding them into triggering operations.") + val YinstrumentClosures: Setting[Boolean] = BooleanSetting("-Yinstrument-closures", "Add instrumentation code that counts closure creations.") + val YinstrumentAllocations: Setting[Boolean] = BooleanSetting("-Yinstrument-allocations", "Add instrumentation code that counts allocations.") + /** Dottydoc specific settings */ val siteRoot: Setting[String] = StringSetting( "-siteroot", diff --git a/compiler/src/dotty/tools/dotc/core/Decorators.scala b/compiler/src/dotty/tools/dotc/core/Decorators.scala index 8d8bdffa7151..ccf02262a4df 100644 --- a/compiler/src/dotty/tools/dotc/core/Decorators.scala +++ b/compiler/src/dotty/tools/dotc/core/Decorators.scala @@ -151,18 +151,21 @@ object Decorators { * exact meaning of "contains" here. */ implicit class PhaseListDecorator(val names: List[String]) extends AnyVal { - def containsPhase(phase: Phase): Boolean = phase match { - case phase: MegaPhase => phase.miniPhases.exists(containsPhase) - case _ => - names exists { name => - name == "all" || { - val strippedName = name.stripSuffix("+") - val logNextPhase = name != strippedName - phase.phaseName.startsWith(strippedName) || - (logNextPhase && phase.prev.phaseName.startsWith(strippedName)) - } + def containsPhase(phase: Phase): Boolean = + names.nonEmpty && { + phase match { + case phase: MegaPhase => phase.miniPhases.exists(containsPhase) + case _ => + names exists { name => + name == "all" || { + val strippedName = name.stripSuffix("+") + val logNextPhase = name != strippedName + phase.phaseName.startsWith(strippedName) || + (logNextPhase && phase.prev.phaseName.startsWith(strippedName)) + } + } } - } + } } implicit class genericDeco[T](val x: T) extends AnyVal { diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e44b02107266..4230356dc590 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -719,6 +719,9 @@ class Definitions { lazy val ValueOfType: TypeRef = ctx.requiredClassRef("scala.ValueOf") def ValueOfClass(implicit ctx: Context): ClassSymbol = ValueOfType.symbol.asClass + lazy val StatsModule = ctx.requiredModule("dotty.tools.dotc.util.Stats") + def Stats_doRecord(implicit ctx: Context): TermSymbol = StatsModule.requiredMethod("doRecord") + lazy val XMLTopScopeModuleRef: TermRef = ctx.requiredModuleRef("scala.xml.TopScope") lazy val TupleTypeRef: TypeRef = ctx.requiredClassRef("scala.Tuple") diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 26a71b41747b..a442e505b701 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -698,6 +698,8 @@ object Flags { /** Labeled protected[this] */ final val ProtectedLocal: FlagConjunction = allOf(Protected, Local) + final val LiftedMethod: FlagConjunction = allOf(Lifted, Method) + /** Java symbol which is `protected` and `static` */ final val StaticProtected: FlagConjunction = allOf(JavaDefined, Protected, JavaStatic) diff --git a/compiler/src/dotty/tools/dotc/core/Mode.scala b/compiler/src/dotty/tools/dotc/core/Mode.scala index 311428e905b2..bef2a0e50c2b 100644 --- a/compiler/src/dotty/tools/dotc/core/Mode.scala +++ b/compiler/src/dotty/tools/dotc/core/Mode.scala @@ -12,6 +12,9 @@ case class Mode(val bits: Int) extends AnyVal { override def toString: String = (0 until 31).filter(i => (bits & (1 << i)) != 0).map(modeName).mkString("Mode(", ",", ")") + + def ==(that: Mode): Boolean = this.bits == that.bits + def !=(that: Mode): Boolean = this.bits != that.bits } object Mode { diff --git a/compiler/src/dotty/tools/dotc/core/Names.scala b/compiler/src/dotty/tools/dotc/core/Names.scala index aa909c68429e..26c9d9d5e777 100644 --- a/compiler/src/dotty/tools/dotc/core/Names.scala +++ b/compiler/src/dotty/tools/dotc/core/Names.scala @@ -570,7 +570,7 @@ object Names { */ def termName(cs: Array[Char], offset: Int, len: Int): SimpleName = synchronized { util.Stats.record("termName") - val h = hashValue(cs, offset, len) & (table.size - 1) + val h = hashValue(cs, offset, len) & (table.length - 1) /** Make sure the capacity of the character array is at least `n` */ def ensureCapacity(n: Int) = diff --git a/compiler/src/dotty/tools/dotc/core/Periods.scala b/compiler/src/dotty/tools/dotc/core/Periods.scala index be704dfcda8e..f1bfa4d0c7f6 100644 --- a/compiler/src/dotty/tools/dotc/core/Periods.scala +++ b/compiler/src/dotty/tools/dotc/core/Periods.scala @@ -123,6 +123,9 @@ object Periods { this.lastPhaseId max that.lastPhaseId) override def toString: String = s"Period($firstPhaseId..$lastPhaseId, run = $runId)" + + def ==(that: Period): Boolean = this.code == that.code + def !=(that: Period): Boolean = this.code != that.code } object Period { diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 60fcbe2acdb8..8de12f23b8f8 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -79,16 +79,25 @@ object Phases { * whereas a combined TreeTransformer gets period equal to union of periods of it's TreeTransforms */ final def squashPhases(phasess: List[List[Phase]], - phasesToSkip: List[String], stopBeforePhases: List[String], stopAfterPhases: List[String], YCheckAfter: List[String]): List[Phase] = { + phasesToSkip: List[String], + stopBeforePhases: List[String], + stopAfterPhases: List[String], + YCheckAfter: List[String])(implicit ctx: Context): List[Phase] = { val squashedPhases = ListBuffer[Phase]() var prevPhases: Set[String] = Set.empty val YCheckAll = YCheckAfter.contains("all") var stop = false + + def isEnabled(p: Phase): Boolean = + !stop && + !stopBeforePhases.contains(p.phaseName) && + !phasesToSkip.contains(p.phaseName) && + p.isEnabled + val filteredPhases = phasess.map(_.filter { p => - val pstop = stop - stop = stop | stopBeforePhases.contains(p.phaseName) | stopAfterPhases.contains(p.phaseName) - !(pstop || stopBeforePhases.contains(p.phaseName) || phasesToSkip.contains(p.phaseName)) + try isEnabled(p) + finally stop |= stopBeforePhases.contains(p.phaseName) | stopAfterPhases.contains(p.phaseName) }) var i = 0 @@ -323,6 +332,8 @@ object Phases { /** Can this transform change the base types of a type? */ def changesBaseTypes: Boolean = changesParents + def isEnabled(implicit ctx: Context): Boolean = true + def exists: Boolean = true def initContext(ctx: FreshContext): Unit = () diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index b1e237f6d62c..d25f05f234de 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1530,11 +1530,16 @@ object SymDenotations { if (classParents.isEmpty && !emptyParentsExpected) onBehalf.signalProvisional() val builder = new BaseDataBuilder - for (p <- classParents) - p.classSymbol match { - case pcls: ClassSymbol => builder.addAll(pcls.baseClasses) - case _ => assert(isRefinementClass || ctx.mode.is(Mode.Interactive), s"$this has non-class parent: $p") - } + def traverse(parents: List[Type]): Unit = parents match { + case p :: parents1 => + p.classSymbol match { + case pcls: ClassSymbol => builder.addAll(pcls.baseClasses) + case _ => assert(isRefinementClass || ctx.mode.is(Mode.Interactive), s"$this has non-class parent: $p") + } + traverse(parents1) + case nil => + } + traverse(classParents) (classSymbol :: builder.baseClasses, builder.baseClassSet) } diff --git a/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala index 426f0194ccad..f6d7fb097bee 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/CommentPickler.scala @@ -3,11 +3,11 @@ package dotty.tools.dotc.core.tasty import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Comments.{Comment, CommentsContext, ContextDocstrings} import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.dotc.core.tasty.TastyBuffer.Addr +import dotty.tools.dotc.core.tasty.TastyBuffer.{Addr, NoAddr} import java.nio.charset.Charset -class CommentPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr])(implicit ctx: Context) { +class CommentPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Addr)(implicit ctx: Context) { private[this] val buf = new TastyBuffer(5000) pickler.newSection("Comments", buf) @@ -16,8 +16,8 @@ class CommentPickler(pickler: TastyPickler, addrOfTree: tpd.Tree => Option[Addr] new Traverser(ctx.docCtx.get).traverse(root) } - def pickleComment(addrOfTree: Option[Addr], comment: Option[Comment]): Unit = (addrOfTree, comment) match { - case (Some(addr), Some(cmt)) => + def pickleComment(addr: Addr, comment: Option[Comment]): Unit = comment match { + case Some(cmt) if addr != NoAddr => val bytes = cmt.raw.getBytes(Charset.forName("UTF-8")) val length = bytes.length buf.writeAddr(addr) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala index b563f5408ac8..bfaf60ef2ad2 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/PositionPickler.scala @@ -14,7 +14,7 @@ import TastyBuffer._ import util.Spans._ import TastyFormat.SOURCE -class PositionPickler(pickler: TastyPickler, addrOfTree: untpd.Tree => Option[Addr]) { +class PositionPickler(pickler: TastyPickler, addrOfTree: untpd.Tree => Addr) { val buf: TastyBuffer = new TastyBuffer(5000) pickler.newSection("Positions", buf) import ast.tpd._ @@ -72,29 +72,33 @@ class PositionPickler(pickler: TastyPickler, addrOfTree: untpd.Tree => Option[Ad def traverse(x: Any, current: SourceFile): Unit = x match { case x: untpd.Tree => if (x.span.exists) { - val sourceChange = x.source != current - val needsPos = x.span.toSynthetic != x.envelope(x.source) || alwaysNeedsPos(x) - addrOfTree(x) match { - case Some(addr) - if needsPos && !pickledIndices.contains(addr.index) || sourceChange => - // we currently do not share trees when unpickling, so if one path to a tree contains - // a source change while another does not, we have to record the position of the tree twice - // in order not to miss the source change. Test case is t3232a.scala. - //println(i"pickling $x with $span at $addr") + val addr = addrOfTree(x) + if (addr != NoAddr) { + if (x.source != current) { + // we currently do not share trees when unpickling, so if one path to a tree contains + // a source change while another does not, we have to record the position of the tree twice + // in order not to miss the source change. Test case is t3232a.scala. + pickleDeltas(addr.index, x.span) + pickleSource(x.source) + } + else if (!pickledIndices.contains(addr.index) && + (x.span.toSynthetic != x.envelope(x.source) || alwaysNeedsPos(x))) pickleDeltas(addr.index, x.span) - if (sourceChange) pickleSource(x.source) - case _ => - //println(i"no address for $x") } } x match { - case x: untpd.MemberDef @unchecked => - for (ann <- x.symbol.annotations) traverse(ann.tree, x.source) + case x: untpd.MemberDef @unchecked => traverse(x.symbol.annotations, x.source) case _ => } - traverse(x.productIterator, x.source) - case xs: TraversableOnce[_] => - xs.foreach(traverse(_, current)) + val limit = x.productArity + var n = 0 + while (n < limit) { + traverse(x.productElement(n), x.source) + n += 1 + } + case y :: ys => + traverse(y, current) + traverse(ys, current) case x: Annotation => traverse(x.tree, current) case _ => diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyBuffer.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyBuffer.scala index a75d1750dcbe..4e61f615aceb 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyBuffer.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyBuffer.scala @@ -20,6 +20,9 @@ object TastyBuffer { def + (delta: Int): Addr = Addr(this.index + delta) def relativeTo(base: Addr): Addr = this - base.index - AddrWidth + + def ==(that: Addr): Boolean = this.index == that.index + def !=(that: Addr): Boolean = this.index != that.index } val NoAddr: Addr = Addr(-1) diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala index b51af96b9720..97d6d0077bb9 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TastyPickler.scala @@ -67,7 +67,7 @@ class TastyPickler(val rootCls: ClassSymbol) { * Note that trees are looked up by reference equality, * so one can reliably use this function only directly after `pickler`. */ - var addrOfTree: tpd.Tree => Option[Addr] = (_ => None) + var addrOfTree: tpd.Tree => Addr = (_ => NoAddr) /** * Addresses in TASTY file of symbols, stored by pickling. diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeBuffer.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeBuffer.scala index 2de9a776b53b..12ddbe163051 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeBuffer.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeBuffer.scala @@ -4,7 +4,7 @@ package core package tasty import util.Util.{bestFit, dble} -import TastyBuffer.{Addr, AddrWidth} +import TastyBuffer.{Addr, NoAddr, AddrWidth} import config.Printers.pickling import ast.untpd.Tree @@ -25,9 +25,9 @@ class TreeBuffer extends TastyBuffer(50000) { case addr: Addr => addr } - def addrOfTree(tree: Tree): Option[Addr] = treeAddrs.get(tree) match { - case null => None - case addr: Addr => Some(addr) + def addrOfTree(tree: Tree): Addr = treeAddrs.get(tree) match { + case null => NoAddr + case addr: Addr => addr } private def offset(i: Int): Addr = Addr(offsets(i)) diff --git a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala index ad4a073e46fb..7264cd6f70f9 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Reporter.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Reporter.scala @@ -31,8 +31,12 @@ object Reporter { def doReport(m: MessageContainer)(implicit ctx: Context): Unit = () override def report(m: MessageContainer)(implicit ctx: Context): Unit = () } -} + type ErrorHandler = (MessageContainer, Context) => Unit + + private val defaultIncompleteHandler: ErrorHandler = + (mc, ctx) => ctx.reporter.report(mc)(ctx) +} trait Reporting { this: Context => @@ -138,6 +142,7 @@ trait Reporting { this: Context => * error messages. */ abstract class Reporter extends interfaces.ReporterResult { + import Reporter._ /** Report a diagnostic */ def doReport(m: MessageContainer)(implicit ctx: Context): Unit @@ -155,8 +160,8 @@ abstract class Reporter extends interfaces.ReporterResult { finally _truncationOK = saved } - type ErrorHandler = MessageContainer => Context => Unit - private[this] var incompleteHandler: ErrorHandler = d => c => report(d)(c) + private[this] var incompleteHandler: ErrorHandler = defaultIncompleteHandler + def withIncompleteHandler[T](handler: ErrorHandler)(op: => T): T = { val saved = incompleteHandler incompleteHandler = handler @@ -185,15 +190,16 @@ abstract class Reporter extends interfaces.ReporterResult { def reportNewFeatureUseSite(featureTrait: Symbol): Unit = reportedFeaturesUseSites += featureTrait - val unreportedWarnings: mutable.HashMap[String, Int] = new mutable.HashMap[String, Int] { - override def default(key: String) = 0 - } + var unreportedWarnings: Map[String, Int] = Map.empty def report(m: MessageContainer)(implicit ctx: Context): Unit = if (!isHidden(m)) { doReport(m)(ctx.addMode(Mode.Printing)) m match { - case m: ConditionalWarning if !m.enablingOption.value => unreportedWarnings(m.enablingOption.name) += 1 + case m: ConditionalWarning if !m.enablingOption.value => + val key = m.enablingOption.name + unreportedWarnings = + unreportedWarnings.updated(key, unreportedWarnings.getOrElse(key, 0) + 1) case m: Warning => _warningCount += 1 case m: Error => errors = m :: errors @@ -204,7 +210,7 @@ abstract class Reporter extends interfaces.ReporterResult { } def incomplete(m: MessageContainer)(implicit ctx: Context): Unit = - incompleteHandler(m)(ctx) + incompleteHandler(m, ctx) /** Summary of warnings and errors */ def summary: String = { diff --git a/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala b/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala new file mode 100644 index 000000000000..7301101cbd27 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/Instrumentation.scala @@ -0,0 +1,60 @@ +package dotty.tools.dotc +package transform + +import core._ +import Contexts.Context +import Symbols._ +import Flags._ +import SymDenotations._ + +import Decorators._ +import ast.Trees._ +import MegaPhase._ +import StdNames.nme +import Names.TermName +import Constants.Constant + + +/** The phase is enabled if a -Yinstrument-... option is set. + * If enabled, it counts the number of closures or allocations for each source position. + * It does this by generating a call to dotty.tools.dotc.util.Stats.doRecord. + */ +class Instrumentation extends MiniPhase { thisPhase => + import ast.tpd._ + + override def phaseName: String = "instrumentation" + + override def isEnabled(implicit ctx: Context) = + ctx.settings.YinstrumentClosures.value || + ctx.settings.YinstrumentAllocations.value + + private var consName: TermName = _ + private var consEqName: TermName = _ + + override def prepareForUnit(tree: Tree)(implicit ctx: Context): Context = { + consName = "::".toTermName + consEqName = "+=".toTermName + ctx + } + + private def record(category: String, tree: Tree)(implicit ctx: Context): Tree = { + val key = Literal(Constant(s"$category${tree.sourcePos.show}")) + ref(defn.Stats_doRecord).appliedTo(key, Literal(Constant(1))) + } + + override def transformApply(tree: Apply)(implicit ctx: Context): Tree = tree.fun match { + case Select(nu: New, _) => + cpy.Block(tree)(record(i"alloc/${nu.tpe}@", tree) :: Nil, tree) + case Select(_, name) if name == consName || name == consEqName => + cpy.Block(tree)(record("alloc/::", tree) :: Nil, tree) + case _ => + tree + } + + override def transformBlock(tree: Block)(implicit ctx: Context): Block = tree.expr match { + case _: Closure => + cpy.Block(tree)(record("closure/", tree) :: tree.stats, tree.expr) + case _ => + tree + } +} diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 8969a0088739..ec6ae4ff8d50 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -1159,7 +1159,7 @@ trait Applications extends Compatibility { self: Typer with Dynamic => * result matching `resultType`? */ def hasExtensionMethod(tp: Type, name: TermName, argType: Type, resultType: Type)(implicit ctx: Context) = { - val mbr = tp.memberBasedOnFlags(name, required = allOf(ExtensionMethod)) + val mbr = tp.memberBasedOnFlags(name, required = ExtensionMethod) mbr.exists && isApplicable(tp.select(name, mbr), argType :: Nil, resultType) } diff --git a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala index 0fabadf4bb3e..43567923b1c2 100644 --- a/compiler/src/dotty/tools/dotc/typer/ConstFold.scala +++ b/compiler/src/dotty/tools/dotc/typer/ConstFold.scala @@ -48,7 +48,7 @@ object ConstFold { } } - private def finish[T <: Tree](tree: T)(compX: => Constant)(implicit ctx: Context): T = + @forceInline private def finish[T <: Tree](tree: T)(compX: => Constant)(implicit ctx: Context): T = try { val x = compX if (x ne null) tree.withType(ConstantType(x)).asInstanceOf[T] diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 44dcfdd15f3e..5ad489f41340 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -92,23 +92,26 @@ object Implicits { def viewCandidateKind(tpw: Type, argType: Type, resType: Type): Candidate.Kind = { - def methodCandidateKind(mt: MethodType, formal: => Type) = + def methodCandidateKind(mt: MethodType, approx: Boolean) = if (!mt.isImplicitMethod && - mt.paramInfos.lengthCompare(1) == 0 && - ctx.test(implicit ctx => argType relaxed_<:< formal)) + mt.paramInfos.lengthCompare(1) == 0 && { + var formal = widenSingleton(mt.paramInfos.head) + if (approx) formal = wildApprox(formal) + ctx.test(implicit ctx => argType relaxed_<:< formal) + }) Candidate.Conversion else Candidate.None tpw match { case mt: MethodType => - methodCandidateKind(mt, widenSingleton(mt.paramInfos.head)) + methodCandidateKind(mt, approx = false) case poly: PolyType => // We do not need to call ProtoTypes#constrained on `poly` because // `candidateKind` is always called with mode TypevarsMissContext enabled. poly.resultType match { case mt: MethodType => - methodCandidateKind(mt, wildApprox(widenSingleton(mt.paramInfos.head))) + methodCandidateKind(mt, approx = true) case rtp => viewCandidateKind(wildApprox(rtp), argType, resType) } @@ -140,7 +143,7 @@ object Implicits { tpw.derivesFrom(defn.Predef_Conforms) && ref.symbol != defn.Predef_conforms val hasExtensions = resType match { case SelectionProto(name, _, _, _) => - tpw.memberBasedOnFlags(name, required = allOf(ExtensionMethod)).exists + tpw.memberBasedOnFlags(name, required = ExtensionMethod).exists case _ => false } val conversionKind = diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 0b726256c0b3..85cc5ccad59c 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -2567,18 +2567,20 @@ class Typer extends Namer def adaptNoArgs(wtp: Type): Tree = { val ptNorm = underlyingApplied(pt) - lazy val functionExpected = defn.isFunctionType(ptNorm) - lazy val resultMatch = constrainResult(tree.symbol, wtp, followAlias(pt)) + def functionExpected = defn.isFunctionType(ptNorm) + def resultMatch = constrainResult(tree.symbol, wtp, followAlias(pt)) def needsEta = pt match { case _: SingletonType => false case IgnoredProto(_: FunOrPolyProto) => false case _ => true } + var resMatch: Boolean = false wtp match { case wtp: ExprType => readaptSimplified(tree.withType(wtp.resultType)) - case wtp: MethodType if wtp.isImplicitMethod && (resultMatch || !functionExpected) => - if (resultMatch || ctx.mode.is(Mode.ImplicitsEnabled)) adaptNoArgsImplicitMethod(wtp) + case wtp: MethodType if wtp.isImplicitMethod && + ({ resMatch = resultMatch; resMatch } || !functionExpected) => + if (resMatch || ctx.mode.is(Mode.ImplicitsEnabled)) adaptNoArgsImplicitMethod(wtp) else { // Don't proceed with implicit search if result type cannot match - the search // will likely be under-constrained, which means that an unbounded number of alternatives @@ -2586,8 +2588,9 @@ class Typer extends Namer err.typeMismatch(tree, pt) } case wtp: MethodType if needsEta => + val funExpected = functionExpected val arity = - if (functionExpected) + if (funExpected) if (!isFullyDefined(pt, ForceDegree.none) && isFullyDefined(wtp, ForceDegree.none)) // if method type is fully defined, but expected type is not, // prioritize method parameter types as parameter types of the eta-expanded closure @@ -2598,7 +2601,7 @@ class Typer extends Namer if (nparams > 0 || pt.eq(AnyFunctionProto)) nparams else -1 // no eta expansion in this case } - adaptNoArgsUnappliedMethod(wtp, functionExpected, arity) + adaptNoArgsUnappliedMethod(wtp, funExpected, arity) case _ => adaptNoArgsOther(wtp) } diff --git a/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala b/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala index ad0ec87067c5..ce91764fb4ca 100644 --- a/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala +++ b/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala @@ -139,6 +139,7 @@ object SimpleIdentityMap { private def value(i: Int): V = bindings(i + 1).asInstanceOf[V] def size: Int = bindings.length / 2 + Stats.record(s"SimpleIdentityMap/$size") def apply(k: K): V = { var i = 0 diff --git a/compiler/src/dotty/tools/dotc/util/Spans.scala b/compiler/src/dotty/tools/dotc/util/Spans.scala index 1672d3468fa1..c3b1e371e7a2 100644 --- a/compiler/src/dotty/tools/dotc/util/Spans.scala +++ b/compiler/src/dotty/tools/dotc/util/Spans.scala @@ -127,6 +127,9 @@ object Spans { else s"${left}no position${right}" } + + def ==(that: Span): Boolean = this.coords == that.coords + def !=(that: Span): Boolean = this.coords != that.coords } private def fromOffsets(start: Int, end: Int, pointDelta: Int) = { diff --git a/compiler/src/dotty/tools/dotc/util/Stats.scala b/compiler/src/dotty/tools/dotc/util/Stats.scala index 7fec4c75bbcc..746b5e554e8d 100644 --- a/compiler/src/dotty/tools/dotc/util/Stats.scala +++ b/compiler/src/dotty/tools/dotc/util/Stats.scala @@ -26,7 +26,7 @@ import collection.mutable def record(fn: => String, n: => Int = 1): Unit = if (enabled) doRecord(fn, n) - private def doRecord(fn: String, n: Int) = + def doRecord(fn: String, n: Int) = if (monitored) { val name = if (fn.startsWith("member-")) "member" else fn hits(name) += n @@ -75,6 +75,17 @@ import collection.mutable } } + final val GroupChar = '/' + + /** Aggregate all counts of all keys with a common prefix, followed by `:` */ + private def aggregate(): Unit = { + val groups = hits.keys + .filter(_.contains(GroupChar)) + .groupBy(_.takeWhile(_ != GroupChar)) + for ((prefix, names) <- groups; name <- names) + hits(s"Total $prefix") += hits(name) + } + def maybeMonitored[T](op: => T)(implicit ctx: Context): T = { if (ctx.settings.YdetailedStats.value) { val hb = new HeartBeat() @@ -83,6 +94,7 @@ import collection.mutable try op finally { hb.continue = false + aggregate() println() println(hits.toList.sortBy(_._2).map{ case (x, y) => s"$x -> $y" } mkString "\n") println(s"uniqueInfo (size, accesses, collisions): ${ctx.base.uniquesSizes}") diff --git a/compiler/src/dotty/tools/repl/ParseResult.scala b/compiler/src/dotty/tools/repl/ParseResult.scala index 8bfb3864dff7..d949f1f997d2 100644 --- a/compiler/src/dotty/tools/repl/ParseResult.scala +++ b/compiler/src/dotty/tools/repl/ParseResult.scala @@ -157,7 +157,7 @@ object ParseResult { case _ => { val reporter = newStoreReporter var needsMore = false - reporter.withIncompleteHandler(_ => _ => needsMore = true) { + reporter.withIncompleteHandler((_, _) => needsMore = true) { parseStats(sourceCode)(ctx.fresh.setReporter(reporter)) !reporter.hasErrors && needsMore }