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/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/plugins/Plugin.scala b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala index b4bfe0a65e85..ad9c110e739d 100644 --- a/compiler/src/dotty/tools/dotc/plugins/Plugin.scala +++ b/compiler/src/dotty/tools/dotc/plugins/Plugin.scala @@ -28,6 +28,7 @@ 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. + * */ def isResearch: Boolean = isInstanceOf[ResearchPlugin] @@ -38,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 * @@ -47,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/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/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 diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 315880f106a6..57b9b1690c69 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,21 @@ class TreeChecker extends Phase with SymTransformer { res } + /** The following invariant holds: + * + * patBoundSyms.contains(sym) <=> sym.isPatternBound + */ def withPatSyms[T](syms: List[Symbol])(op: => T)(implicit ctx: Context): T = { - nowDefinedSyms ++= syms + syms.foreach { sym => + assert( + sym.isPatternBound, + "patBoundSyms.contains(sym) => sym.isPatternBound is broken." + + i" Pattern bound symbol $sym has incorrect flags: " + sym.flagsString + ", line " + sym.sourcePos.line + ) + } + patBoundSyms ++= syms val res = op - nowDefinedSyms --= syms + patBoundSyms --= syms res } @@ -189,8 +201,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( + !sym.isPatternBound || patBoundSyms.contains(sym), + i"sym.isPatternBound => patBoundSyms.contains(sym) is broken, sym = $sym, line " + tree.sourcePos.line + ) + } /** assert Java classes are not used as objects */ def assertIdentNotJavaClass(tree: Tree)(implicit ctx: Context): Unit = tree match { 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))) } diff --git a/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala b/compiler/test/dotty/tools/dotc/BootstrappedOnlyCompilationTests.scala index 5feb6482b871..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,6 +184,7 @@ class BootstrappedOnlyCompilationTests extends ParallelTesting { } compileFilesInDir("tests/plugins/neg").checkExpectedErrors() + compileDir("tests/plugins/custom/analyzer", withCompilerOptions.and("-Yretain-trees")).checkCompile() } } 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..9585a8fbe7e7 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/app/Hello.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/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..8f030b5efcbe --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/lib/Lib.scala @@ -0,0 +1,17 @@ +package lib + +class A(val n: Int) { + def this(c: Char) = this(c.toInt) + + val a = 30 * n + + 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/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..9fb8efa557ab --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/Analyzer.scala @@ -0,0 +1,86 @@ +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.{CheckStatic} + +class InitPlugin extends StandardPlugin { + import tpd._ + val name: String = "initPlugin" + override val description: String = "checks that under -Yretain-trees we may get tree for all symbols" + + def init(options: List[String]): List[PluginPhase] = + (new SetDefTree) :: (new InitChecker) :: Nil +} + +class InitChecker extends PluginPhase { + import tpd._ + + 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) + 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/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/SetDefTree.scala b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/SetDefTree.scala new file mode 100644 index 000000000000..bc11dcb4666b --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/SetDefTree.scala @@ -0,0 +1,33 @@ +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. + * + * All plugins that depend on `symbol.defTree` should sit + * between the phase `SetDefTree` and `SetDefTreeOff`. + */ +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" +} diff --git a/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/SetDefTreeOff.scala b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/SetDefTreeOff.scala new file mode 100644 index 000000000000..b1f70f69807b --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/SetDefTreeOff.scala @@ -0,0 +1,35 @@ +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 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 + 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/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..b456d9292971 --- /dev/null +++ b/sbt-dotty/sbt-test/sbt-dotty/analyzer-plugin/plugin/src/main/resources/plugin.properties @@ -0,0 +1 @@ +pluginClass=analyzer.InitPlugin 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 diff --git a/tests/plugins/custom/analyzer/Analyzer_1.scala b/tests/plugins/custom/analyzer/Analyzer_1.scala new file mode 100644 index 000000000000..f3b5e2224665 --- /dev/null +++ b/tests/plugins/custom/analyzer/Analyzer_1.scala @@ -0,0 +1,108 @@ +// 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 + +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.{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._ + + 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(FirstTransform.name) + + 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) + 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/custom/analyzer/Hello_3.scala b/tests/plugins/custom/analyzer/Hello_3.scala new file mode 100644 index 000000000000..9585a8fbe7e7 --- /dev/null +++ b/tests/plugins/custom/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/custom/analyzer/Lib_2.scala b/tests/plugins/custom/analyzer/Lib_2.scala new file mode 100644 index 000000000000..8f030b5efcbe --- /dev/null +++ b/tests/plugins/custom/analyzer/Lib_2.scala @@ -0,0 +1,17 @@ +package lib + +class A(val n: Int) { + def this(c: Char) = this(c.toInt) + + val a = 30 * n + + 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/custom/analyzer/plugin.properties b/tests/plugins/custom/analyzer/plugin.properties new file mode 100644 index 000000000000..6530a0df6c36 --- /dev/null +++ b/tests/plugins/custom/analyzer/plugin.properties @@ -0,0 +1 @@ +pluginClass=analyzer.InitChecker