-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
@lihaoyi I have gone with Scala 3.5.0 here since that was the version I have been porting Mill to. I didn't see any testing procedure - so I used the Mill integration tests to validate the override and scaladoc annotation inference is working. (Also I could remove all the override modifiers I had introduced in my Mill PR) Note that now I made `moduledefs` a cross module in order to set the right "species" of scala versions to build plugin against. This is probably solved better with Cross2 but that needs to upgrade mill to 0.11.x which breaks the build in other ways I didn't want to touch ``` ./millw -i resolve 'moduledefs'._ [1/1] resolve moduledefs[2.13.14] moduledefs[3.5.0] ``` ``` ./millw -i resolve 'moduledefs[2.13.14]'.plugin._ [1/1] resolve moduledefs[2.13.14].plugin[2.13.0] moduledefs[2.13.14].plugin[2.13.10] moduledefs[2.13.14].plugin[2.13.11] moduledefs[2.13.14].plugin[2.13.12] moduledefs[2.13.14].plugin[2.13.13] moduledefs[2.13.14].plugin[2.13.14] moduledefs[2.13.14].plugin[2.13.1] moduledefs[2.13.14].plugin[2.13.2] moduledefs[2.13.14].plugin[2.13.3] moduledefs[2.13.14].plugin[2.13.4] moduledefs[2.13.14].plugin[2.13.5] moduledefs[2.13.14].plugin[2.13.6] moduledefs[2.13.14].plugin[2.13.7] moduledefs[2.13.14].plugin[2.13.8] moduledefs[2.13.14].plugin[2.13.9] ``` ``` ./millw -i resolve 'moduledefs[3.5.0]'.plugin._ [1/1] resolve moduledefs[3.5.0].plugin[3.5.0] ```
- Loading branch information
Showing
8 changed files
with
212 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pluginClass=mill.moduledefs.AutoOverridePluginDotty |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package mill.moduledefs | ||
|
||
import scala.collection.mutable.ListBuffer | ||
|
||
import dotty.tools.dotc.plugins.{StandardPlugin, PluginPhase} | ||
import dotty.tools.dotc.core.Contexts.{Context, ctx} | ||
import dotty.tools.dotc.core.Comments.docCtx | ||
import dotty.tools.dotc.core.Symbols.Symbol | ||
import dotty.tools.dotc.core.Symbols.ClassSymbol | ||
import dotty.tools.dotc.core.Symbols.requiredClass | ||
import dotty.tools.dotc.core.Names.termName | ||
import dotty.tools.dotc.core.Flags | ||
import dotty.tools.dotc.report | ||
import dotty.tools.dotc.core.Constants.Constant | ||
import dotty.tools.dotc.core.Annotations.Annotation | ||
import dotty.tools.dotc.ast.tpd.{Template, ValDef, DefDef, Literal, NamedArg} | ||
import dotty.tools.dotc.util.Spans.Span | ||
|
||
class AutoOverridePluginDotty extends StandardPlugin { | ||
|
||
override def initialize(options: List[String])(using Context): List[PluginPhase] = | ||
List(EnableScaladocAnnotation(), AutoOverride()) | ||
|
||
val name = "auto-override-plugin" | ||
val description = "automatically inserts `override` keywords for you" | ||
|
||
/** basically we override each kind of definition to copy over its doc comment to an annotation. */ | ||
private class EnableScaladocAnnotation extends PluginPhase { | ||
|
||
override val phaseName: String = "EmbedScaladocAnnotation" | ||
|
||
override val runsAfter: Set[String] = Set("posttyper") | ||
override val runsBefore: Set[String] = Set("pickler") // TODO: should the annotation be in TASTY? | ||
|
||
private var _ScalaDocAnnot: ClassSymbol | Null = null | ||
private def ScalaDocAnnot(using Context): ClassSymbol = { | ||
val local = _ScalaDocAnnot | ||
if local == null then { | ||
val sym = requiredClass(AutoOverridePluginDotty.scaladocAnnotationClassName) | ||
_ScalaDocAnnot = sym | ||
sym | ||
} else local | ||
} | ||
private lazy val valueName = termName("value") | ||
|
||
private def cookComment(sym: Symbol, span: Span)(using Context): Unit = { | ||
for | ||
docCtx <- ctx.docCtx | ||
comment <- docCtx.docstring(sym) | ||
do { | ||
val text = NamedArg(valueName, Literal(Constant(comment.raw))).withSpan(span) | ||
sym.addAnnotation(Annotation(ScalaDocAnnot, text, span)) | ||
} | ||
} | ||
|
||
override def prepareForTemplate(tree: Template)(using Context): Context = { | ||
cookComment(tree.symbol, tree.span) | ||
ctx | ||
} | ||
|
||
override def prepareForValDef(tree: ValDef)(using Context): Context = { | ||
cookComment(tree.symbol, tree.span) | ||
ctx | ||
} | ||
|
||
override def prepareForDefDef(tree: DefDef)(using Context): Context = { | ||
cookComment(tree.symbol, tree.span) | ||
ctx | ||
} | ||
|
||
} | ||
|
||
/** This phase automatically adds the override annotation to methods that require one. */ | ||
private class AutoOverride extends PluginPhase { | ||
|
||
override val runsAfter = Set("posttyper") | ||
override val runsBefore = Set("crossVersionChecks") // this is where override checking happens | ||
|
||
val phaseName = "auto-override" | ||
|
||
private var _Cacher: ClassSymbol | Null = null | ||
private def Cacher(using Context): ClassSymbol = { | ||
val local = _Cacher | ||
if local == null then { | ||
val sym = requiredClass(AutoOverridePluginDotty.cacherClassName) | ||
_Cacher = sym | ||
sym | ||
} else local | ||
} | ||
|
||
private def isCacher(owner: Symbol)(using Context): Boolean = | ||
if owner.isClass then owner.asClass.baseClasses.exists(_ == Cacher) | ||
else false | ||
|
||
override def prepareForDefDef(d: DefDef)(using Context): Context = { | ||
val sym = d.symbol | ||
if sym.allOverriddenSymbols.count(!_.is(Flags.Abstract)) >= 1 | ||
&& !sym.is(Flags.Override) | ||
&& isCacher(sym.owner) | ||
then | ||
sym.flags = sym.flags | Flags.Override | ||
ctx | ||
} | ||
} | ||
|
||
} | ||
|
||
object AutoOverridePluginDotty { | ||
|
||
private val cacherClassName = "mill.moduledefs.Cacher" | ||
private val scaladocAnnotationClassName = "mill.moduledefs.Scaladoc" | ||
|
||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package mill.moduledefs | ||
|
||
import scala.collection.mutable | ||
import scala.quoted.* | ||
|
||
trait Cacher { | ||
private lazy val cacherLazyMap = mutable.Map.empty[sourcecode.Enclosing, Any] | ||
|
||
protected def cachedTarget[T](t: => T)(implicit c: sourcecode.Enclosing): T = synchronized { | ||
cacherLazyMap.getOrElseUpdate(c, t).asInstanceOf[T] | ||
} | ||
} | ||
|
||
object Cacher { | ||
private def withMacroOwner[T](using Quotes)(op: quotes.reflect.Symbol => T): T = { | ||
import quotes.reflect.* | ||
|
||
// In Scala 3, the top level splice of a macro is owned by a symbol called "macro" with the macro flag set, | ||
// but not the method flag. | ||
def isMacroOwner(sym: Symbol)(using Quotes): Boolean = | ||
sym.name == "macro" && sym.flags.is(Flags.Macro | Flags.Synthetic) && !sym.flags.is( | ||
Flags.Method | ||
) | ||
|
||
def loop(owner: Symbol): T = | ||
if owner.isPackageDef || owner == Symbol.noSymbol then | ||
report.errorAndAbort( | ||
"Cannot find the owner of the macro expansion", | ||
Position.ofMacroExpansion | ||
) | ||
else if isMacroOwner(owner) then op(owner.owner) // Skip the "macro" owner | ||
else loop(owner.owner) | ||
|
||
loop(Symbol.spliceOwner) | ||
} | ||
|
||
def impl0[T: Type](using Quotes)(t: Expr[T]): Expr[T] = withMacroOwner { owner => | ||
import quotes.reflect.* | ||
|
||
val CacherSym = TypeRepr.of[Cacher].typeSymbol | ||
|
||
val ownerIsCacherClass = | ||
owner.owner.isClassDef && | ||
owner.owner.typeRef.baseClasses.contains(CacherSym) | ||
|
||
if (ownerIsCacherClass && owner.flags.is(Flags.Method)) { | ||
val enclosingCtx = Expr.summon[sourcecode.Enclosing].getOrElse( | ||
report.errorAndAbort("Cannot find enclosing context", Position.ofMacroExpansion) | ||
) | ||
|
||
val thisSel = This(owner.owner).asExprOf[Cacher] | ||
'{ $thisSel.cachedTarget[T](${ t })(using $enclosingCtx) } | ||
} else report.errorAndAbort( | ||
"T{} members must be defs defined in a Cacher class/trait/object body", | ||
Position.ofMacroExpansion | ||
) | ||
} | ||
} |