From 8e5ea1e93f6cd7a4e94cc387b5a316d3dcc28b6d Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 13 Aug 2019 16:35:16 +0200 Subject: [PATCH 01/16] Add compiler phases to set and unset symbol.defTree --- compiler/src/dotty/tools/dotc/Compiler.scala | 6 ++-- .../tools/dotc/transform/PostTyper.scala | 1 - .../tools/dotc/transform/SetDefTree.scala | 30 +++++++++++++++++ .../tools/dotc/transform/SetDefTreeOff.scala | 32 +++++++++++++++++++ .../dotc/transform/SyntheticMembers.scala | 2 +- 5 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/transform/SetDefTree.scala create mode 100644 compiler/src/dotty/tools/dotc/transform/SetDefTreeOff.scala diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 56e71c29110b..df24cf8a069e 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -57,7 +57,8 @@ class Compiler { new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes new CookComments, // Cook the comments: expand variables, doc, etc. - new CompleteJavaEnums) :: // Fill in constructors for Java enums + new CompleteJavaEnums, // Fill in constructors for Java enums + new SetDefTree) :: // set `symbol.defTree` List(new CheckStatic, // Check restrictions that apply to @static members new ElimRepeated, // Rewrite vararg parameters and arguments new ExpandSAMs, // Expand single abstract method closures to anonymous classes @@ -68,7 +69,8 @@ class Compiler { new ByNameClosures, // Expand arguments to by-name parameters to closures new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope new ClassOf, // Expand `Predef.classOf` calls. - new RefChecks) :: // Various checks mostly related to abstract members and overriding + new RefChecks, // Various checks mostly related to abstract members and overriding + new SetDefTreeOff) :: // unset `symbol.defTree` List(new ElimOpaque, // Turn opaque into normal aliases new TryCatchPatterns, // Compile cases in try/catch new PatternMatcher, // Compile pattern matches diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index a15e192fc6f3..f1df1ff76635 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -113,7 +113,6 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase val sym = tree.symbol Checking.checkValidOperator(sym) sym.transformAnnotations(transformAnnot) - sym.defTree = tree tree } diff --git a/compiler/src/dotty/tools/dotc/transform/SetDefTree.scala b/compiler/src/dotty/tools/dotc/transform/SetDefTree.scala new file mode 100644 index 000000000000..4202b66f5f3b --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SetDefTree.scala @@ -0,0 +1,30 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.transform.MegaPhase._ + +/** Set the `defTree` property of symbols for compile plugins + * that perform "whole-program" analysis. + * + * All plugins that depend on `symbol.defTree` should sit + * between the phase `SetDefTree` and `SetDefTreeOff`. + */ +class SetDefTree extends MiniPhase { + import tpd._ + + override val phaseName: String = SetDefTree.name + override def runsAfter: Set[String] = Set(Pickler.name) + // don't allow plugins to change tasty + // research plugins can still change the phase plan at will + + override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree = tree.setDefTree + + override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = tree.setDefTree + + override def transformTypeDef(tree: TypeDef)(implicit ctx: Context): Tree = tree.setDefTree +} + +object SetDefTree { + val name: String = "SetDefTree" +} diff --git a/compiler/src/dotty/tools/dotc/transform/SetDefTreeOff.scala b/compiler/src/dotty/tools/dotc/transform/SetDefTreeOff.scala new file mode 100644 index 000000000000..a6b22125cee0 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/transform/SetDefTreeOff.scala @@ -0,0 +1,32 @@ +package dotty.tools.dotc.transform + +import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.transform.MegaPhase._ + +/** Unset the `defTree` property of symbols. See the doc for `SetDefTree` */ +class SetDefTreeOff extends MiniPhase { + import tpd._ + + override val phaseName: String = SetDefTreeOff.name + override def runsAfter: Set[String] = Set(SetDefTree.name) + + override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree = { + tree.symbol.defTree = EmptyTree + tree + } + + override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = { + tree.symbol.defTree = EmptyTree + tree + } + + override def transformTypeDef(tree: TypeDef)(implicit ctx: Context): Tree = { + tree.symbol.defTree = EmptyTree + tree + } +} + +object SetDefTreeOff { + val name: String = "SetDefTreeOff" +} diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 4b9f4d891d94..320e7dc11c18 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -222,7 +222,7 @@ class SyntheticMembers(thisPhase: DenotTransformer) { * */ def equalsBody(that: Tree)(implicit ctx: Context): Tree = { - val thatAsClazz = ctx.newSymbol(ctx.owner, nme.x_0, Synthetic, clazzType, coord = ctx.owner.span) // x$0 + val thatAsClazz = ctx.newSymbol(ctx.owner, nme.x_0, Synthetic | Case, clazzType, coord = ctx.owner.span) // x$0 def wildcardAscription(tp: Type) = Typed(Underscore(tp), TypeTree(tp)) val pattern = Bind(thatAsClazz, wildcardAscription(AnnotatedType(clazzType, Annotation(defn.UncheckedAnnot)))) // x$0 @ (_: C @unchecked) // compare primitive fields first, slow equality checks of non-primitive fields can be skipped when primitives differ From c8c598f8efb9a238c8f627ca578bd86e4006dc04 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 13 Aug 2019 11:42:35 +0200 Subject: [PATCH 02/16] test symbol.defTree --- .../src/dotty/tools/dotc/plugins/Plugin.scala | 2 + .../sbt-dotty/analyzer-plugin/app/Hello.scala | 37 +++++++++++ .../sbt-dotty/analyzer-plugin/build.sbt | 27 ++++++++ .../analyzer-plugin/changes/plugin.sbt | 28 +++++++++ .../analyzer-plugin/changes/retain.sbt | 29 +++++++++ .../sbt-dotty/analyzer-plugin/lib/Lib.scala | 11 ++++ .../analyzer-plugin/plugin/Analyzer.scala | 63 +++++++++++++++++++ .../src/main/resources/plugin.properties | 1 + .../analyzer-plugin/project/plugins.sbt | 1 + .../sbt-test/sbt-dotty/analyzer-plugin/test | 17 +++++ 10 files changed, 216 insertions(+) create mode 100644 sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/app/Hello.scala create mode 100644 sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/build.sbt create mode 100644 sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/changes/plugin.sbt create mode 100644 sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/changes/retain.sbt create mode 100644 sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/lib/Lib.scala create mode 100644 sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala create mode 100644 sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/src/main/resources/plugin.properties create mode 100644 sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/project/plugins.sbt create mode 100644 sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/test diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala index b4bfe0a65e85..9ab37cf74e15 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -28,6 +28,8 @@ sealed trait Plugin { * * Research plugin receives a phase plan and return a new phase plan, while * non-research plugin returns a list of phases to be inserted. + * + * @note Research plugins only compile with nightly build. */ def isResearch: Boolean = isInstanceOf[ResearchPlugin] diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/app/Hello.scala b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/app/Hello.scala new file mode 100644 index 000000000000..6ac54e239e7a --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/app/Hello.scala @@ -0,0 +1,37 @@ +package hello + +import lib._ + +class M(val n: Int) { + val a = 30 * n + class B(x: Int) { + val b = x * a + def bar(i: Int) = i * x + } + + def foo(i: Int) = i * n + + def bar = { + class C(val s: String) + val c = new C("hello") + def qux = c.s + qux + } +} + + +object Hello { + def testLib: Unit = { + val a = new A(30) + val b = new a.B(24) + a.foo(3) + b.bar(9) + } + + def testHello: Unit = { + val a = new M(30) + val b = new a.B(24) + a.foo(3) + b.bar(9) + } +} diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/build.sbt b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/build.sbt new file mode 100644 index 000000000000..088b6065932d --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/build.sbt @@ -0,0 +1,27 @@ +lazy val dottyVersion = sys.props("plugin.scalaVersion") + +lazy val plugin = project + .in(file("plugin")) + .settings( + name := "init-checker", + version := "0.0.1", + organization := "ch.epfl.lamp", + scalaVersion := dottyVersion, + + libraryDependencies ++= Seq( + "ch.epfl.lamp" %% "dotty-compiler" % scalaVersion.value % "provided" + ) + ) + +lazy val lib = project + .in(file("lib")) + .settings( + scalaVersion := dottyVersion + ) + +lazy val app = project + .in(file("app")) + .settings( + scalaVersion := dottyVersion + ) + .dependsOn(lib) diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/changes/plugin.sbt b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/changes/plugin.sbt new file mode 100644 index 000000000000..934a20f0f8e8 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/changes/plugin.sbt @@ -0,0 +1,28 @@ +lazy val dottyVersion = sys.props("plugin.scalaVersion") + +lazy val plugin = project + .in(file("plugin")) + .settings( + name := "init-checker", + version := "0.0.1", + organization := "ch.epfl.lamp", + scalaVersion := dottyVersion, + + libraryDependencies ++= Seq( + "ch.epfl.lamp" %% "dotty-compiler" % scalaVersion.value % "provided" + ) + ) + +lazy val lib = project + .in(file("lib")) + .settings( + scalaVersion := dottyVersion + ) + +lazy val app = project + .in(file("app")) + .settings( + scalaVersion := dottyVersion, + addCompilerPlugin("ch.epfl.lamp" %% "init-checker" % "0.0.1") + ) + .dependsOn(lib) diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/changes/retain.sbt b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/changes/retain.sbt new file mode 100644 index 000000000000..b88c32a95a2c --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/changes/retain.sbt @@ -0,0 +1,29 @@ +lazy val dottyVersion = sys.props("plugin.scalaVersion") + +lazy val plugin = project + .in(file("plugin")) + .settings( + name := "init-checker", + version := "0.0.1", + organization := "ch.epfl.lamp", + scalaVersion := dottyVersion, + + libraryDependencies ++= Seq( + "ch.epfl.lamp" %% "dotty-compiler" % scalaVersion.value % "provided" + ) + ) + +lazy val lib = project + .in(file("lib")) + .settings( + scalaVersion := dottyVersion + ) + +lazy val app = project + .in(file("app")) + .settings( + scalaVersion := dottyVersion, + scalacOptions += "-Yretain-trees", + addCompilerPlugin("ch.epfl.lamp" %% "init-checker" % "0.0.1") + ) + .dependsOn(lib) diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/lib/Lib.scala b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/lib/Lib.scala new file mode 100644 index 000000000000..cea405bac1ea --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/lib/Lib.scala @@ -0,0 +1,11 @@ +package lib + +class A(val n: Int) { + val a = 30 * n + class B(x: Int) { + val b = x * a + def bar(i: Int) = i * x + } + + def foo(i: Int) = i * n +} diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala new file mode 100644 index 000000000000..7d7c03980100 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala @@ -0,0 +1,63 @@ +package analyzer + +import scala.language.implicitConversions + +import dotty.tools.dotc._ +import core._ +import Contexts.Context +import plugins._ +import Phases.Phase +import ast.tpd +import transform.MegaPhase.MiniPhase +import Decorators._ +import Symbols.Symbol +import Constants.Constant +import transform.{Pickler, Staging} + +class InitChecker extends PluginPhase with StandardPlugin { + import tpd._ + + val name: String = "initChecker" + override val description: String = "checks that under -Yretain-trees we may get tree for all symbols" + + val phaseName = name + + override val runsAfter = Set(Staging.name) + override val runsBefore = Set(Pickler.name) + + def init(options: List[String]): List[PluginPhase] = this :: Nil + + private def checkDef(tree: Tree)(implicit ctx: Context): Tree = { + val span = tree.symbol.defTree.span + if (!(span.exists && span.end > span.start)) + ctx.error("cannot get tree for " + tree.symbol.show, tree.sourcePos) + tree + } + + private def checkRef(tree: Tree)(implicit ctx: Context): Tree = { + val helloPkgSym = ctx.requiredPackage("hello") + val libPkgSym = ctx.requiredPackage("lib") + val enclosingPkg = tree.symbol.enclosingPackageClass + + if (enclosingPkg == helloPkgSym) { // source code + checkDef(tree) + } + else if (enclosingPkg == libPkgSym) { // tasty from library + checkDef(tree) + // check that all sub-definitions have trees set properly + transformAllDeep(tree.symbol.defTree) + } + + tree + } + + override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree = checkDef(tree) + + override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = checkDef(tree) + + override def transformTypeDef(tree: TypeDef)(implicit ctx: Context): Tree = checkDef(tree) + + override def transformSelect(tree: Select)(implicit ctx: Context): Tree = checkRef(tree) + + override def transformIdent(tree: Ident)(implicit ctx: Context): Tree = checkRef(tree) +} \ No newline at end of file diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/src/main/resources/plugin.properties b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/src/main/resources/plugin.properties new file mode 100644 index 000000000000..6530a0df6c36 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/src/main/resources/plugin.properties @@ -0,0 +1 @@ +pluginClass=analyzer.InitChecker diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/project/plugins.sbt b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/project/plugins.sbt new file mode 100644 index 000000000000..c17caab2d98c --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % sys.props("plugin.version")) diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/test b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/test new file mode 100644 index 000000000000..2d248bdfdddb --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/test @@ -0,0 +1,17 @@ +# Should compile OK without the plugin +> app/compile + +# Publish plugin locally +> plugin/publishLocal + +# Should NOT compile with just the plugin +$ copy-file changes/plugin.sbt build.sbt +> reload +> clean +-> app/compile + +# Should compile with the plugin and -Yretain-trees +$ copy-file changes/retain.sbt build.sbt +> reload +> clean +> app/compile From d93e7c0e2608a157846583866b0a6ef98694a324 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 13 Aug 2019 13:10:21 +0200 Subject: [PATCH 03/16] update Analyzer --- .../analyzer-plugin/plugin/Analyzer.scala | 61 ++++++++++++------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala index 7d7c03980100..e66ebfa7083d 100644 --- a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala @@ -12,7 +12,8 @@ import transform.MegaPhase.MiniPhase import Decorators._ import Symbols.Symbol import Constants.Constant -import transform.{Pickler, Staging} +import Types._ +import transform.{SetDefTree, SetDefTreeOff} class InitChecker extends PluginPhase with StandardPlugin { import tpd._ @@ -22,35 +23,45 @@ class InitChecker extends PluginPhase with StandardPlugin { val phaseName = name - override val runsAfter = Set(Staging.name) - override val runsBefore = Set(Pickler.name) + override val runsAfter = Set(SetDefTree.name) + override val runsBefore = Set(SetDefTreeOff.name) def init(options: List[String]): List[PluginPhase] = this :: Nil private def checkDef(tree: Tree)(implicit ctx: Context): Tree = { - val span = tree.symbol.defTree.span - if (!(span.exists && span.end > span.start)) - ctx.error("cannot get tree for " + tree.symbol.show, tree.sourcePos) + if (tree.symbol.defTree.isEmpty) + ctx.error("cannot get tree for " + tree.show, tree.sourcePos) tree } - private def checkRef(tree: Tree)(implicit ctx: Context): Tree = { - val helloPkgSym = ctx.requiredPackage("hello") - val libPkgSym = ctx.requiredPackage("lib") - val enclosingPkg = tree.symbol.enclosingPackageClass - - if (enclosingPkg == helloPkgSym) { // source code - checkDef(tree) - } - else if (enclosingPkg == libPkgSym) { // tasty from library - checkDef(tree) - // check that all sub-definitions have trees set properly - transformAllDeep(tree.symbol.defTree) + private def checkable(sym: Symbol)(implicit ctx: Context): Boolean = + sym.exists && !sym.isOneOf(Flags.Package) && !sym.isOneOf(Flags.Param) && + (sym.isClass || !sym.isOneOf(Flags.Case, butNot = Flags.Enum)) // pattern-bound symbols + + private def checkRef(tree: Tree)(implicit ctx: Context): Tree = + if (!checkable(tree.symbol)) tree + else { + val helloPkgSym = ctx.requiredPackage("hello").moduleClass + val libPkgSym = ctx.requiredPackage("lib").moduleClass + val enclosingPkg = tree.symbol.enclosingPackageClass + + if (enclosingPkg == helloPkgSym) { // source code + checkDef(tree) + ctx.warning("tree: " + tree.symbol.defTree.show) + } + else if (enclosingPkg == libPkgSym) { // tasty from library + checkDef(tree) + // check that all sub-definitions have trees set properly + // make sure that are no cycles in the code + transformAllDeep(tree.symbol.defTree) + ctx.warning("tree: " + tree.symbol.defTree.show) + } + else { + ctx.warning(tree.symbol + " is neither in lib nor hello, owner = " + enclosingPkg, tree.sourcePos) + } + tree } - tree - } - override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree = checkDef(tree) override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = checkDef(tree) @@ -60,4 +71,12 @@ class InitChecker extends PluginPhase with StandardPlugin { override def transformSelect(tree: Select)(implicit ctx: Context): Tree = checkRef(tree) override def transformIdent(tree: Ident)(implicit ctx: Context): Tree = checkRef(tree) + + override def transformTypeTree(tree: TypeTree)(implicit ctx: Context): Tree = { + tree.tpe.foreachPart { + case tp: NamedType => checkRef(TypeTree(tp)) + case _ => + } + tree + } } \ No newline at end of file From f4f525d6667fd0cea86a9b9557686389eb2f3364 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 13 Aug 2019 16:35:31 +0200 Subject: [PATCH 04/16] update test code --- .../sbt-dotty/analyzer-plugin/app/Hello.scala | 15 ++++++++++----- .../sbt-dotty/analyzer-plugin/lib/Lib.scala | 7 +++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/app/Hello.scala b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/app/Hello.scala index 6ac54e239e7a..9585a8fbe7e7 100644 --- a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/app/Hello.scala +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/app/Hello.scala @@ -2,9 +2,14 @@ package hello import lib._ +case class Student(name: String) + class M(val n: Int) { val a = 30 * n + def this(c: Char) = this(c.toInt) + class B(x: Int) { + def this(c: Char) = this(c.toInt) val b = x * a def bar(i: Int) = i * x } @@ -20,17 +25,17 @@ class M(val n: Int) { } -object Hello { +object Test { def testLib: Unit = { - val a = new A(30) - val b = new a.B(24) + val a: A = new A(30) + val b: a.B = new a.B(24) a.foo(3) b.bar(9) } def testHello: Unit = { - val a = new M(30) - val b = new a.B(24) + val a: M = new M(30) + val b: a.B = new a.B(24) a.foo(3) b.bar(9) } diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/lib/Lib.scala b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/lib/Lib.scala index cea405bac1ea..4b7044d6011f 100644 --- a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/lib/Lib.scala +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/lib/Lib.scala @@ -1,11 +1,18 @@ package lib class A(val n: Int) { + def this(c: Char) = this(c.toInt) + val a = 30 * n + // val p = Product("x", 100) + class B(x: Int) { + def this(c: Char) = this(c.toInt) val b = x * a def bar(i: Int) = i * x } def foo(i: Int) = i * n } + +case class Product(name: String, price: Int) \ No newline at end of file From f3de3b6db7cd2b016582ff4ca12eb2884294dc82 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 13 Aug 2019 17:28:28 +0200 Subject: [PATCH 05/16] disable SetDefTreeOff --- compiler/src/dotty/tools/dotc/Compiler.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index df24cf8a069e..9395d63b215a 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -69,8 +69,8 @@ class Compiler { new ByNameClosures, // Expand arguments to by-name parameters to closures new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope new ClassOf, // Expand `Predef.classOf` calls. - new RefChecks, // Various checks mostly related to abstract members and overriding - new SetDefTreeOff) :: // unset `symbol.defTree` + new RefChecks) :: // Various checks mostly related to abstract members and overriding + // new SetDefTreeOff) :: // unset `symbol.defTree` List(new ElimOpaque, // Turn opaque into normal aliases new TryCatchPatterns, // Compile cases in try/catch new PatternMatcher, // Compile pattern matches From 31d13c4d3f5bb5100c7a981f31c648b3795c6bc0 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 13 Aug 2019 17:29:43 +0200 Subject: [PATCH 06/16] Add plugin test to normal CI The scripted tests only run every night --- .../BootstrappedOnlyCompilationTests.scala | 1 + tests/plugins/pos/analyzer/Analyzer_1.scala | 82 +++++++++++++++++++ tests/plugins/pos/analyzer/Hello_3.scala | 42 ++++++++++ tests/plugins/pos/analyzer/Lib_2.scala | 18 ++++ tests/plugins/pos/analyzer/plugin.properties | 1 + 5 files changed, 144 insertions(+) create mode 100644 tests/plugins/pos/analyzer/Analyzer_1.scala create mode 100644 tests/plugins/pos/analyzer/Hello_3.scala create mode 100644 tests/plugins/pos/analyzer/Lib_2.scala create mode 100644 tests/plugins/pos/analyzer/plugin.properties diff --git a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala index 5feb6482b871..4b1064fc8fb6 100644 --- a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala @@ -183,6 +183,7 @@ class BootstrappedOnlyCompilationTests extends ParallelTesting { } compileFilesInDir("tests/plugins/neg").checkExpectedErrors() + compileFilesInDir("tests/plugins/pos").checkCompile() } } diff --git a/tests/plugins/pos/analyzer/Analyzer_1.scala b/tests/plugins/pos/analyzer/Analyzer_1.scala new file mode 100644 index 000000000000..e66ebfa7083d --- /dev/null +++ b/tests/plugins/pos/analyzer/Analyzer_1.scala @@ -0,0 +1,82 @@ +package analyzer + +import scala.language.implicitConversions + +import dotty.tools.dotc._ +import core._ +import Contexts.Context +import plugins._ +import Phases.Phase +import ast.tpd +import transform.MegaPhase.MiniPhase +import Decorators._ +import Symbols.Symbol +import Constants.Constant +import Types._ +import transform.{SetDefTree, SetDefTreeOff} + +class InitChecker extends PluginPhase with StandardPlugin { + import tpd._ + + val name: String = "initChecker" + override val description: String = "checks that under -Yretain-trees we may get tree for all symbols" + + val phaseName = name + + override val runsAfter = Set(SetDefTree.name) + override val runsBefore = Set(SetDefTreeOff.name) + + def init(options: List[String]): List[PluginPhase] = this :: Nil + + private def checkDef(tree: Tree)(implicit ctx: Context): Tree = { + if (tree.symbol.defTree.isEmpty) + ctx.error("cannot get tree for " + tree.show, tree.sourcePos) + tree + } + + private def checkable(sym: Symbol)(implicit ctx: Context): Boolean = + sym.exists && !sym.isOneOf(Flags.Package) && !sym.isOneOf(Flags.Param) && + (sym.isClass || !sym.isOneOf(Flags.Case, butNot = Flags.Enum)) // pattern-bound symbols + + private def checkRef(tree: Tree)(implicit ctx: Context): Tree = + if (!checkable(tree.symbol)) tree + else { + val helloPkgSym = ctx.requiredPackage("hello").moduleClass + val libPkgSym = ctx.requiredPackage("lib").moduleClass + val enclosingPkg = tree.symbol.enclosingPackageClass + + if (enclosingPkg == helloPkgSym) { // source code + checkDef(tree) + ctx.warning("tree: " + tree.symbol.defTree.show) + } + else if (enclosingPkg == libPkgSym) { // tasty from library + checkDef(tree) + // check that all sub-definitions have trees set properly + // make sure that are no cycles in the code + transformAllDeep(tree.symbol.defTree) + ctx.warning("tree: " + tree.symbol.defTree.show) + } + else { + ctx.warning(tree.symbol + " is neither in lib nor hello, owner = " + enclosingPkg, tree.sourcePos) + } + tree + } + + override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree = checkDef(tree) + + override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = checkDef(tree) + + override def transformTypeDef(tree: TypeDef)(implicit ctx: Context): Tree = checkDef(tree) + + override def transformSelect(tree: Select)(implicit ctx: Context): Tree = checkRef(tree) + + override def transformIdent(tree: Ident)(implicit ctx: Context): Tree = checkRef(tree) + + override def transformTypeTree(tree: TypeTree)(implicit ctx: Context): Tree = { + tree.tpe.foreachPart { + case tp: NamedType => checkRef(TypeTree(tp)) + case _ => + } + tree + } +} \ No newline at end of file diff --git a/tests/plugins/pos/analyzer/Hello_3.scala b/tests/plugins/pos/analyzer/Hello_3.scala new file mode 100644 index 000000000000..9585a8fbe7e7 --- /dev/null +++ b/tests/plugins/pos/analyzer/Hello_3.scala @@ -0,0 +1,42 @@ +package hello + +import lib._ + +case class Student(name: String) + +class M(val n: Int) { + val a = 30 * n + def this(c: Char) = this(c.toInt) + + class B(x: Int) { + def this(c: Char) = this(c.toInt) + val b = x * a + def bar(i: Int) = i * x + } + + def foo(i: Int) = i * n + + def bar = { + class C(val s: String) + val c = new C("hello") + def qux = c.s + qux + } +} + + +object Test { + def testLib: Unit = { + val a: A = new A(30) + val b: a.B = new a.B(24) + a.foo(3) + b.bar(9) + } + + def testHello: Unit = { + val a: M = new M(30) + val b: a.B = new a.B(24) + a.foo(3) + b.bar(9) + } +} diff --git a/tests/plugins/pos/analyzer/Lib_2.scala b/tests/plugins/pos/analyzer/Lib_2.scala new file mode 100644 index 000000000000..4b7044d6011f --- /dev/null +++ b/tests/plugins/pos/analyzer/Lib_2.scala @@ -0,0 +1,18 @@ +package lib + +class A(val n: Int) { + def this(c: Char) = this(c.toInt) + + val a = 30 * n + // val p = Product("x", 100) + + class B(x: Int) { + def this(c: Char) = this(c.toInt) + val b = x * a + def bar(i: Int) = i * x + } + + def foo(i: Int) = i * n +} + +case class Product(name: String, price: Int) \ No newline at end of file diff --git a/tests/plugins/pos/analyzer/plugin.properties b/tests/plugins/pos/analyzer/plugin.properties new file mode 100644 index 000000000000..6530a0df6c36 --- /dev/null +++ b/tests/plugins/pos/analyzer/plugin.properties @@ -0,0 +1 @@ +pluginClass=analyzer.InitChecker From 4e8bb420b482a2d4a9030356933c1b019d5a579a Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 13 Aug 2019 20:03:38 +0200 Subject: [PATCH 07/16] change runsAfter of SetDefTree This needs to be changed because `Pickler` does not exists for running staged code. --- compiler/src/dotty/tools/dotc/transform/SetDefTree.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/SetDefTree.scala b/compiler/src/dotty/tools/dotc/transform/SetDefTree.scala index 4202b66f5f3b..6f6e25a6267f 100644 --- a/compiler/src/dotty/tools/dotc/transform/SetDefTree.scala +++ b/compiler/src/dotty/tools/dotc/transform/SetDefTree.scala @@ -14,7 +14,7 @@ class SetDefTree extends MiniPhase { import tpd._ override val phaseName: String = SetDefTree.name - override def runsAfter: Set[String] = Set(Pickler.name) + override def runsAfter: Set[String] = Set(ReifyQuotes.name) // don't allow plugins to change tasty // research plugins can still change the phase plan at will From b590d9364444bd2775620c63f4fa1aa657fc54db Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 13 Aug 2019 20:40:49 +0200 Subject: [PATCH 08/16] revert changes in PostTyper --- compiler/src/dotty/tools/dotc/transform/PostTyper.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index f1df1ff76635..a15e192fc6f3 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -113,6 +113,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase val sym = tree.symbol Checking.checkValidOperator(sym) sym.transformAnnotations(transformAnnot) + sym.defTree = tree tree } From 28db2aa3c4a31927e9f3b59bf622eebb811b3e6a Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Tue, 13 Aug 2019 21:16:28 +0200 Subject: [PATCH 09/16] Fix CI --- .../dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala | 3 ++- tests/plugins/{pos => custom}/analyzer/Analyzer_1.scala | 0 tests/plugins/{pos => custom}/analyzer/Hello_3.scala | 0 tests/plugins/{pos => custom}/analyzer/Lib_2.scala | 0 tests/plugins/{pos => custom}/analyzer/plugin.properties | 0 5 files changed, 2 insertions(+), 1 deletion(-) rename tests/plugins/{pos => custom}/analyzer/Analyzer_1.scala (100%) rename tests/plugins/{pos => custom}/analyzer/Hello_3.scala (100%) rename tests/plugins/{pos => custom}/analyzer/Lib_2.scala (100%) rename tests/plugins/{pos => custom}/analyzer/plugin.properties (100%) diff --git a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala index 4b1064fc8fb6..b6a65db221d6 100644 --- a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala +++ b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala @@ -163,6 +163,7 @@ class BootstrappedOnlyCompilationTests extends ParallelTesting { } @Test def testPlugins: Unit = { + implicit val testGroup: TestGroup = TestGroup("testPlugins") val pluginFile = "plugin.properties" // 1. hack with absolute path for -Xplugin @@ -183,7 +184,7 @@ class BootstrappedOnlyCompilationTests extends ParallelTesting { } compileFilesInDir("tests/plugins/neg").checkExpectedErrors() - compileFilesInDir("tests/plugins/pos").checkCompile() + compileDir("tests/plugins/custom/analyzer", withCompilerOptions.and("-Yretain-trees")).checkCompile() } } diff --git a/tests/plugins/pos/analyzer/Analyzer_1.scala b/tests/plugins/custom/analyzer/Analyzer_1.scala similarity index 100% rename from tests/plugins/pos/analyzer/Analyzer_1.scala rename to tests/plugins/custom/analyzer/Analyzer_1.scala diff --git a/tests/plugins/pos/analyzer/Hello_3.scala b/tests/plugins/custom/analyzer/Hello_3.scala similarity index 100% rename from tests/plugins/pos/analyzer/Hello_3.scala rename to tests/plugins/custom/analyzer/Hello_3.scala diff --git a/tests/plugins/pos/analyzer/Lib_2.scala b/tests/plugins/custom/analyzer/Lib_2.scala similarity index 100% rename from tests/plugins/pos/analyzer/Lib_2.scala rename to tests/plugins/custom/analyzer/Lib_2.scala diff --git a/tests/plugins/pos/analyzer/plugin.properties b/tests/plugins/custom/analyzer/plugin.properties similarity index 100% rename from tests/plugins/pos/analyzer/plugin.properties rename to tests/plugins/custom/analyzer/plugin.properties From 30c9e3ec3e193de30ab74ff97c373aac03dea2b6 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 14 Aug 2019 15:17:36 +0200 Subject: [PATCH 10/16] Revert changes to compiler As the compiler supports already adding custom phases, there is no need to add the phases to the compiler. --- compiler/src/dotty/tools/dotc/Compiler.scala | 4 +-- .../analyzer-plugin/plugin/Analyzer.scala | 20 ++++++++------ .../analyzer-plugin/plugin}/SetDefTree.scala | 7 +++-- .../plugin}/SetDefTreeOff.scala | 7 +++-- .../src/main/resources/plugin.properties | 2 +- .../plugins/custom/analyzer/Analyzer_1.scala | 26 ++++++++++++++++--- 6 files changed, 47 insertions(+), 19 deletions(-) rename {compiler/src/dotty/tools/dotc/transform => sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin}/SetDefTree.scala (80%) rename {compiler/src/dotty/tools/dotc/transform => sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin}/SetDefTreeOff.scala (77%) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 9395d63b215a..56e71c29110b 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -57,8 +57,7 @@ class Compiler { new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes new CookComments, // Cook the comments: expand variables, doc, etc. - new CompleteJavaEnums, // Fill in constructors for Java enums - new SetDefTree) :: // set `symbol.defTree` + new CompleteJavaEnums) :: // Fill in constructors for Java enums List(new CheckStatic, // Check restrictions that apply to @static members new ElimRepeated, // Rewrite vararg parameters and arguments new ExpandSAMs, // Expand single abstract method closures to anonymous classes @@ -70,7 +69,6 @@ class Compiler { new HoistSuperArgs, // Hoist complex arguments of supercalls to enclosing scope new ClassOf, // Expand `Predef.classOf` calls. new RefChecks) :: // Various checks mostly related to abstract members and overriding - // new SetDefTreeOff) :: // unset `symbol.defTree` List(new ElimOpaque, // Turn opaque into normal aliases new TryCatchPatterns, // Compile cases in try/catch new PatternMatcher, // Compile pattern matches diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala index e66ebfa7083d..9fb8efa557ab 100644 --- a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala @@ -13,20 +13,24 @@ import Decorators._ import Symbols.Symbol import Constants.Constant import Types._ -import transform.{SetDefTree, SetDefTreeOff} +import transform.{CheckStatic} -class InitChecker extends PluginPhase with StandardPlugin { +class InitPlugin extends StandardPlugin { import tpd._ - - val name: String = "initChecker" + val name: String = "initPlugin" override val description: String = "checks that under -Yretain-trees we may get tree for all symbols" - val phaseName = name + def init(options: List[String]): List[PluginPhase] = + (new SetDefTree) :: (new InitChecker) :: Nil +} - override val runsAfter = Set(SetDefTree.name) - override val runsBefore = Set(SetDefTreeOff.name) +class InitChecker extends PluginPhase { + import tpd._ - def init(options: List[String]): List[PluginPhase] = this :: Nil + val phaseName = "symbolTreeChecker" + + override val runsAfter = Set(SetDefTree.name) + override val runsBefore = Set(CheckStatic.name) private def checkDef(tree: Tree)(implicit ctx: Context): Tree = { if (tree.symbol.defTree.isEmpty) diff --git a/compiler/src/dotty/tools/dotc/transform/SetDefTree.scala b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/SetDefTree.scala similarity index 80% rename from compiler/src/dotty/tools/dotc/transform/SetDefTree.scala rename to sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/SetDefTree.scala index 6f6e25a6267f..bc11dcb4666b 100644 --- a/compiler/src/dotty/tools/dotc/transform/SetDefTree.scala +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/SetDefTree.scala @@ -1,8 +1,10 @@ -package dotty.tools.dotc.transform +package analyzer import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.transform.MegaPhase._ +import dotty.tools.dotc.transform.{ReifyQuotes, FirstTransform} +import dotty.tools.dotc.plugins._ /** Set the `defTree` property of symbols for compile plugins * that perform "whole-program" analysis. @@ -10,11 +12,12 @@ import dotty.tools.dotc.transform.MegaPhase._ * All plugins that depend on `symbol.defTree` should sit * between the phase `SetDefTree` and `SetDefTreeOff`. */ -class SetDefTree extends MiniPhase { +class SetDefTree extends PluginPhase { import tpd._ override val phaseName: String = SetDefTree.name override def runsAfter: Set[String] = Set(ReifyQuotes.name) + override def runsBefore: Set[String] = Set(FirstTransform.name) // don't allow plugins to change tasty // research plugins can still change the phase plan at will diff --git a/compiler/src/dotty/tools/dotc/transform/SetDefTreeOff.scala b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/SetDefTreeOff.scala similarity index 77% rename from compiler/src/dotty/tools/dotc/transform/SetDefTreeOff.scala rename to sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/SetDefTreeOff.scala index a6b22125cee0..b1f70f69807b 100644 --- a/compiler/src/dotty/tools/dotc/transform/SetDefTreeOff.scala +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/SetDefTreeOff.scala @@ -1,15 +1,18 @@ -package dotty.tools.dotc.transform +package analyzer import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.transform.MegaPhase._ +import dotty.tools.dotc.transform.{ReifyQuotes, FirstTransform} +import dotty.tools.dotc.plugins._ /** Unset the `defTree` property of symbols. See the doc for `SetDefTree` */ -class SetDefTreeOff extends MiniPhase { +class SetDefTreeOff extends PluginPhase { import tpd._ override val phaseName: String = SetDefTreeOff.name override def runsAfter: Set[String] = Set(SetDefTree.name) + override def runsBefore: Set[String] = Set(FirstTransform.name) override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree = { tree.symbol.defTree = EmptyTree diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/src/main/resources/plugin.properties b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/src/main/resources/plugin.properties index 6530a0df6c36..b456d9292971 100644 --- a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/src/main/resources/plugin.properties +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/src/main/resources/plugin.properties @@ -1 +1 @@ -pluginClass=analyzer.InitChecker +pluginClass=analyzer.InitPlugin diff --git a/tests/plugins/custom/analyzer/Analyzer_1.scala b/tests/plugins/custom/analyzer/Analyzer_1.scala index e66ebfa7083d..3e3409b5e303 100644 --- a/tests/plugins/custom/analyzer/Analyzer_1.scala +++ b/tests/plugins/custom/analyzer/Analyzer_1.scala @@ -13,7 +13,27 @@ import Decorators._ import Symbols.Symbol import Constants.Constant import Types._ -import transform.{SetDefTree, SetDefTreeOff} +import transform.{ReifyQuotes, FirstTransform} + +class SetDefTree extends PluginPhase { + import tpd._ + + override val phaseName: String = SetDefTree.name + override def runsAfter: Set[String] = Set(ReifyQuotes.name) + override def runsBefore: Set[String] = Set(FirstTransform.name) + // don't allow plugins to change tasty + // research plugins can still change the phase plan at will + + override def transformValDef(tree: ValDef)(implicit ctx: Context): Tree = tree.setDefTree + + override def transformDefDef(tree: DefDef)(implicit ctx: Context): Tree = tree.setDefTree + + override def transformTypeDef(tree: TypeDef)(implicit ctx: Context): Tree = tree.setDefTree +} + +object SetDefTree { + val name: String = "SetDefTree" +} class InitChecker extends PluginPhase with StandardPlugin { import tpd._ @@ -24,9 +44,9 @@ class InitChecker extends PluginPhase with StandardPlugin { val phaseName = name override val runsAfter = Set(SetDefTree.name) - override val runsBefore = Set(SetDefTreeOff.name) + override val runsBefore = Set(FirstTransform.name) - def init(options: List[String]): List[PluginPhase] = this :: Nil + def init(options: List[String]): List[PluginPhase] = this :: (new SetDefTree) :: Nil private def checkDef(tree: Tree)(implicit ctx: Context): Tree = { if (tree.symbol.defTree.isEmpty) From 8978c6bb7e46f40e2d80ea708a2b7c01697c3d97 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 23 Aug 2019 15:08:21 +0200 Subject: [PATCH 11/16] Check invariant about the flags of pattern-bound symbols The following invariant should be true for pattern-bound symbols, and *only* for pattern-bound symbols: (sym.isType && !sym.isClass && sym.isOneOf(Case)) || (sym.isTerm && sym.isOneOf(Case, butNot = Enum | Module)) The invariant only holds before patternMatcher. After patternMatcher, there are no more pattern trees, thus the invariant does not hold any more. --- .../src/dotty/tools/dotc/core/Phases.scala | 5 ++- .../tools/dotc/transform/TreeChecker.scala | 35 ++++++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 88cebc7d35cb..a9ebb3e65470 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -354,6 +354,7 @@ object Phases { private[this] var myFlatClasses = false private[this] var myRefChecked = false private[this] var myLambdaLifted = false + private[this] var myPatternTranslated = false private[this] var mySameMembersStartId = NoPhaseId private[this] var mySameParentsStartId = NoPhaseId @@ -372,7 +373,8 @@ object Phases { final def erasedTypes: Boolean = myErasedTypes // Phase is after erasure final def flatClasses: Boolean = myFlatClasses // Phase is after flatten final def refChecked: Boolean = myRefChecked // Phase is after RefChecks - final def lambdaLifted: Boolean = myLambdaLifted // Phase is after LambdaLift + final def lambdaLifted: Boolean = myLambdaLifted // Phase is after LambdaLift + final def patternTranslated: Boolean = myPatternTranslated // Phase is after PatternMatcher final def sameMembersStartId: Int = mySameMembersStartId // id of first phase where all symbols are guaranteed to have the same members as in this phase @@ -391,6 +393,7 @@ object Phases { myFlatClasses = prev.getClass == classOf[Flatten] || prev.flatClasses myRefChecked = prev.getClass == classOf[RefChecks] || prev.refChecked myLambdaLifted = prev.getClass == classOf[LambdaLift] || prev.lambdaLifted + myPatternTranslated = prev.getClass == classOf[PatternMatcher] || prev.patternTranslated mySameMembersStartId = if (changesMembers) id else prev.sameMembersStartId mySameParentsStartId = if (changesParents) id else prev.sameParentsStartId mySameBaseTypesStartId = if (changesBaseTypes) id else prev.sameBaseTypesStartId diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 315880f106a6..43ffb45fd0ef 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -131,6 +131,7 @@ class TreeChecker extends Phase with SymTransformer { class Checker(phasesToCheck: Seq[Phase]) extends ReTyper with Checking { private[this] val nowDefinedSyms = new mutable.HashSet[Symbol] + private[this] val patBoundSyms = new mutable.HashSet[Symbol] private[this] val everDefinedSyms = newMutableSymbolMap[untpd.Tree] // don't check value classes after typer, as the constraint about constructors doesn't hold after transform @@ -171,10 +172,25 @@ class TreeChecker extends Phase with SymTransformer { res } + /** The following invariant holds: + * + * patBoundSyms.contains(sym) <=> patternBound(sym) + */ + def patternBound(sym: Symbol)(implicit ctx: Context): Boolean = + (sym.isType && !sym.isClass && sym.isOneOf(Case)) || + (sym.isTerm && sym.isOneOf(Case, butNot = Enum | Module)) + def withPatSyms[T](syms: List[Symbol])(op: => T)(implicit ctx: Context): T = { - nowDefinedSyms ++= syms + syms.foreach { sym => + assert( + patternBound(sym), + "patBoundSyms.contains(sym) => patternBound(sym) is broken." + + " Pattern bound symbol has incorrect flags: " + sym.flags + ", line " + sym.sourcePos.line + ) + } + patBoundSyms ++= syms val res = op - nowDefinedSyms --= syms + patBoundSyms --= syms res } @@ -189,8 +205,19 @@ class TreeChecker extends Phase with SymTransformer { } def assertDefined(tree: untpd.Tree)(implicit ctx: Context): Unit = - if (tree.symbol.maybeOwner.isTerm) - assert(nowDefinedSyms contains tree.symbol, i"undefined symbol ${tree.symbol} at line " + tree.sourcePos.line) + if (tree.symbol.maybeOwner.isTerm) { + val sym = tree.symbol + assert( + nowDefinedSyms.contains(sym) || patBoundSyms.contains(sym), + i"undefined symbol ${sym} at line " + tree.sourcePos.line + ) + + if (!ctx.phase.patternTranslated) + assert( + !patternBound(sym) || patBoundSyms.contains(sym), + "patternBound(sym) => patBoundSyms.contains(sym) is broken, line " + tree.sourcePos.line + ) + } /** assert Java classes are not used as objects */ def assertIdentNotJavaClass(tree: Tree)(implicit ctx: Context): Unit = tree match { From 2b21ab450180ab8917b58ef49cc4f73866f94582 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 23 Aug 2019 15:21:44 +0200 Subject: [PATCH 12/16] Address review: add a note to code --- tests/plugins/custom/analyzer/Analyzer_1.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/plugins/custom/analyzer/Analyzer_1.scala b/tests/plugins/custom/analyzer/Analyzer_1.scala index 3e3409b5e303..f3b5e2224665 100644 --- a/tests/plugins/custom/analyzer/Analyzer_1.scala +++ b/tests/plugins/custom/analyzer/Analyzer_1.scala @@ -1,3 +1,9 @@ +// Similar code resides in scripted tests, which only runs on nightly: +// +// sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin +// +// You may want to change the code there too + package analyzer import scala.language.implicitConversions From 791c75aa2f7e6d7b158e0755765e102c8450e606 Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Fri, 23 Aug 2019 17:50:20 +0200 Subject: [PATCH 13/16] Reduced inline match symbols should not have Case Those symbols are now normal ValDefs, thus should not have the Case flag. --- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index f85621fe09a8..3cfc7d10f287 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -784,7 +784,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) { * scrutinee as RHS and type that corresponds to RHS. */ def newTermBinding(sym: TermSymbol, rhs: Tree): Unit = { - val copied = sym.copy(info = rhs.tpe.widenTermRefExpr, coord = sym.coord).asTerm + val copied = sym.copy(info = rhs.tpe.widenTermRefExpr, coord = sym.coord, flags = sym.flags &~ Case).asTerm caseBindingMap += ((sym, ValDef(copied, constToLiteral(rhs)).withSpan(sym.span))) } From e5a4b3ee67a6304702f97b9a272cb9a3c487707c Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Sat, 24 Aug 2019 07:38:19 +0200 Subject: [PATCH 14/16] Pattern bound symbols should have Case flag --- compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala | 2 +- compiler/src/dotty/tools/dotc/transform/LazyVals.scala | 2 +- compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala | 2 +- compiler/src/dotty/tools/dotc/transform/TreeChecker.scala | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala index 37dfdca338aa..90f04e016ee9 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandSAMs.scala @@ -124,7 +124,7 @@ class ExpandSAMs extends MiniPhase { def translateMatch(tree: Match, pfParam: Symbol, cases: List[CaseDef], defaultValue: Tree)(implicit ctx: Context) = { val selector = tree.selector val selectorTpe = selector.tpe.widen - val defaultSym = ctx.newSymbol(pfParam.owner, nme.WILDCARD, Synthetic, selectorTpe) + val defaultSym = ctx.newSymbol(pfParam.owner, nme.WILDCARD, Synthetic | Case, selectorTpe) val defaultCase = CaseDef( Bind(defaultSym, Underscore(selectorTpe)), diff --git a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala index 71bf24a133c7..1dc889712ee9 100644 --- a/compiler/src/dotty/tools/dotc/transform/LazyVals.scala +++ b/compiler/src/dotty/tools/dotc/transform/LazyVals.scala @@ -336,7 +336,7 @@ class LazyVals extends MiniPhase with IdentityDenotTransformer { } val retryCase = { - val caseSymbol = ctx.newSymbol(methodSymbol, nme.DEFAULT_EXCEPTION_NAME, Synthetic, defn.ThrowableType) + val caseSymbol = ctx.newSymbol(methodSymbol, nme.DEFAULT_EXCEPTION_NAME, Synthetic | Case, defn.ThrowableType) val triggerRetry = setFlagState.appliedTo(thiz, offset, initState, fieldId) CaseDef( Bind(caseSymbol, ref(caseSymbol)), diff --git a/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala b/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala index b4d56814137b..11a0bfbde1ea 100644 --- a/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala +++ b/compiler/src/dotty/tools/dotc/transform/NonLocalReturns.scala @@ -69,7 +69,7 @@ class NonLocalReturns extends MiniPhase { */ private def nonLocalReturnTry(body: Tree, key: TermSymbol, meth: Symbol)(implicit ctx: Context) = { val keyDef = ValDef(key, New(defn.ObjectType, Nil)) - val ex = ctx.newSymbol(meth, nme.ex, EmptyFlags, nonLocalReturnControl, coord = body.span) + val ex = ctx.newSymbol(meth, nme.ex, Case, nonLocalReturnControl, coord = body.span) val pat = BindTyped(ex, nonLocalReturnControl) val rhs = If( ref(ex).select(nme.key).appliedToNone.select(nme.eq).appliedTo(ref(key)), diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 43ffb45fd0ef..cdea1cb19404 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -185,7 +185,7 @@ class TreeChecker extends Phase with SymTransformer { assert( patternBound(sym), "patBoundSyms.contains(sym) => patternBound(sym) is broken." + - " Pattern bound symbol has incorrect flags: " + sym.flags + ", line " + sym.sourcePos.line + i" Pattern bound symbol $sym has incorrect flags: " + sym.flagsString + ", line " + sym.sourcePos.line ) } patBoundSyms ++= syms @@ -215,7 +215,7 @@ class TreeChecker extends Phase with SymTransformer { if (!ctx.phase.patternTranslated) assert( !patternBound(sym) || patBoundSyms.contains(sym), - "patternBound(sym) => patBoundSyms.contains(sym) is broken, line " + tree.sourcePos.line + i"patternBound(sym) => patBoundSyms.contains(sym) is broken, sym = $sym, line " + tree.sourcePos.line ) } From 9a629c3e1fca51377e9a43633ead385e82fabb1d Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Mon, 26 Aug 2019 20:05:30 +0200 Subject: [PATCH 15/16] Add Symbol.isPatternBound --- compiler/src/dotty/tools/dotc/core/Symbols.scala | 5 +++++ .../dotty/tools/dotc/transform/TreeChecker.scala | 14 +++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 0d7ca769eb23..17c221243a8b 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -540,6 +540,11 @@ object Symbols { d != null && d.flagsUNSAFE.is(Private) } + /** Is the symbol a pattern bound symbol? + */ + final def isPatternBound(implicit ctx: Context): Boolean = + !isClass && this.is(Case, butNot = Enum | Module) + /** The symbol's signature if it is completed or a method, NotAMethod otherwise. */ final def signature(implicit ctx: Context): Signature = if (lastDenot != null && (lastDenot.isCompleted || lastDenot.is(Method))) diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index cdea1cb19404..57b9b1690c69 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -174,17 +174,13 @@ class TreeChecker extends Phase with SymTransformer { /** The following invariant holds: * - * patBoundSyms.contains(sym) <=> patternBound(sym) + * patBoundSyms.contains(sym) <=> sym.isPatternBound */ - def patternBound(sym: Symbol)(implicit ctx: Context): Boolean = - (sym.isType && !sym.isClass && sym.isOneOf(Case)) || - (sym.isTerm && sym.isOneOf(Case, butNot = Enum | Module)) - def withPatSyms[T](syms: List[Symbol])(op: => T)(implicit ctx: Context): T = { syms.foreach { sym => assert( - patternBound(sym), - "patBoundSyms.contains(sym) => patternBound(sym) is broken." + + sym.isPatternBound, + "patBoundSyms.contains(sym) => sym.isPatternBound is broken." + i" Pattern bound symbol $sym has incorrect flags: " + sym.flagsString + ", line " + sym.sourcePos.line ) } @@ -214,8 +210,8 @@ class TreeChecker extends Phase with SymTransformer { if (!ctx.phase.patternTranslated) assert( - !patternBound(sym) || patBoundSyms.contains(sym), - i"patternBound(sym) => patBoundSyms.contains(sym) is broken, sym = $sym, line " + tree.sourcePos.line + !sym.isPatternBound || patBoundSyms.contains(sym), + i"sym.isPatternBound => patBoundSyms.contains(sym) is broken, sym = $sym, line " + tree.sourcePos.line ) } From 1d7e79dd5301105348eee6be3d4299aa13aeaeac Mon Sep 17 00:00:00 2001 From: Liu Fengyun Date: Wed, 28 Aug 2019 14:00:11 +0200 Subject: [PATCH 16/16] address review --- compiler/src/dotty/tools/dotc/plugins/Plugin.scala | 6 +++++- sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/lib/Lib.scala | 1 - tests/plugins/custom/analyzer/Lib_2.scala | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala index 9ab37cf74e15..ad9c110e739d 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -29,7 +29,6 @@ sealed trait Plugin { * Research plugin receives a phase plan and return a new phase plan, while * non-research plugin returns a list of phases to be inserted. * - * @note Research plugins only compile with nightly build. */ def isResearch: Boolean = isInstanceOf[ResearchPlugin] @@ -40,6 +39,7 @@ sealed trait Plugin { val optionsHelp: Option[String] = None } +/** A standard plugin can be inserted into the normal compilation pipeline */ trait StandardPlugin extends Plugin { /** Non-research plugins should override this method to return the phases * @@ -49,6 +49,10 @@ trait StandardPlugin extends Plugin { def init(options: List[String]): List[PluginPhase] } +/** A research plugin may customize the compilation pipeline freely + * + * @note Research plugins are only supported by nightly or snapshot build of the compiler. + */ trait ResearchPlugin extends Plugin { /** Research plugins should override this method to return the new phase plan * diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/lib/Lib.scala b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/lib/Lib.scala index 4b7044d6011f..8f030b5efcbe 100644 --- a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/lib/Lib.scala +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/lib/Lib.scala @@ -4,7 +4,6 @@ class A(val n: Int) { def this(c: Char) = this(c.toInt) val a = 30 * n - // val p = Product("x", 100) class B(x: Int) { def this(c: Char) = this(c.toInt) diff --git a/tests/plugins/custom/analyzer/Lib_2.scala b/tests/plugins/custom/analyzer/Lib_2.scala index 4b7044d6011f..8f030b5efcbe 100644 --- a/tests/plugins/custom/analyzer/Lib_2.scala +++ b/tests/plugins/custom/analyzer/Lib_2.scala @@ -4,7 +4,6 @@ class A(val n: Int) { def this(c: Char) = this(c.toInt) val a = 30 * n - // val p = Product("x", 100) class B(x: Int) { def this(c: Char) = this(c.toInt)