Skip to content

Instantly share code, notes, and snippets.

@hobwekiva
Created June 23, 2018 09:40
Show Gist options
  • Save hobwekiva/e55719f2bb59d194920399a4696263e9 to your computer and use it in GitHub Desktop.
Save hobwekiva/e55719f2bb59d194920399a4696263e9 to your computer and use it in GitHub Desktop.
package newts.plugin
import scala.collection.mutable
import scala.reflect.internal.util.StatisticsStatics
import scala.tools.nsc.plugins.{Plugin, PluginComponent}
import scala.tools.nsc.transform.{Transform, TypingTransformers}
import scala.tools.nsc.typechecker.Analyzer
import scala.tools.nsc.{Global, Phase, SubComponent}
class NewtsPlugin(override val global: Global) extends Plugin { plugin =>
import global._
override val name: String = "newts"
override lazy val description: String = s"generates newtypes"
override lazy val components: List[PluginComponent] =
List(collectTypeclasses)
val typeclassSymbol = rootMirror.getRequiredClass("newts.Typeclass")
val orphanSymbol = rootMirror.getRequiredClass("newts.orphan")
val newAnalyzer = new NewAnalyzer()
final class NewAnalyzer extends {
val global: plugin.global.type = plugin.global
} with Analyzer {
override def newTyper(context: Context): Typer = new NormalTyper1(context)
override def inferImplicit(tree: global.Tree, pt: global.Type, reportAmbiguous: Boolean, isView: Boolean, context: Context, saveAmbiguousDivergent: Boolean, pos: Position): SearchResult = {
if (pt <:< typeclassSymbol.tpe) {
// Note that the isInvalidConversionTarget seems to make a lot more sense right here, before all the
// work is performed, than at the point where it presently exists.
val shouldPrint = printTypings && !context.undetparams.isEmpty
if (shouldPrint)
typingStack.printTyping(tree, "typing implicit: %s %s".format(tree, context.undetparamsString))
val implicitSearchContext = context.makeImplicit(reportAmbiguous)
val search = new ImplicitSearch(tree, pt, isView, implicitSearchContext, pos)
pluginsNotifyImplicitSearch(search)
val results = search.allImplicits
// println(s"$pt $context")
// println(results)
val result = results.find(_.isSuccess).getOrElse(search.bestImplicit)
pluginsNotifyImplicitSearchResult(result)
if (result.isFailure && saveAmbiguousDivergent && implicitSearchContext.reporter.hasErrors)
implicitSearchContext.reporter.propagateImplicitTypeErrorsTo(context.reporter)
// scala/bug#7944 undetermined type parameters that result from inference within typedImplicit land in
// `implicitSearchContext.undetparams`, *not* in `context.undetparams`
// Here, we copy them up to parent context (analogously to the way the errors are copied above),
// and then filter out any which *were* inferred and are part of the substitutor in the implicit search result.
context.undetparams = ((context.undetparams ++ result.undetparams) filterNot result.subst.from.contains).distinct
result
} else {
val result = super.inferImplicit(tree, pt, reportAmbiguous, isView,context, saveAmbiguousDivergent, pos)
// println(s"$pt $context")
// println(result)
result
}
}
class NormalTyper1(context : Context) extends Typer(context) {
// println(global.phase.name)
override def applyImplicitArgs(fun: global.Tree): global.Tree = {
// println(fun)
super.applyImplicitArgs(fun)
}
}
object typerFactory2 extends {
val global: NewtsPlugin.this.global.type = NewtsPlugin.this.global
} with SubComponent {
import global.statistics
val phaseName = "typer"
val runsAfter = List[String]()
val runsRightAfter = Some("packageobjects")
def newPhase(_prev: Phase): StdPhase = new StdPhase(_prev) {
override def keepsTypeParams = false
resetTyper()
// the log accumulates entries over time, even though it should not (Adriaan, Martin said so).
// Lacking a better fix, we clear it here (before the phase is created, meaning for each
// compiler run). This is good enough for the resident compiler, which was the most affected.
undoLog.clear()
override def run() {
val start = if (StatisticsStatics.areSomeColdStatsEnabled) statistics.startTimer(statistics.typerNanos) else null
global.echoPhaseSummary(this)
for (unit <- currentRun.units) {
applyPhase(unit)
undoLog.clear()
}
// defensive measure in case the bookkeeping in deferred macro expansion is buggy
clearDelayed()
if (StatisticsStatics.areSomeColdStatsEnabled) statistics.stopTimer(statistics.typerNanos, start)
}
def apply(unit: CompilationUnit) {
try {
val typer = newTyper(rootContext(unit))
unit.body = typer.typed(unit.body)
if (global.settings.Yrangepos && !global.reporter.hasErrors) global.validatePositions(unit.body)
for (workItem <- unit.toCheck) workItem()
if (settings.warnUnusedImport)
warnUnusedImports(unit)
if (settings.warnUnused.isSetByUser)
typer checkUnused unit
}
finally {
unit.toCheck.clear()
}
}
}
}
}
// object SampleClassInterceptor {
// import java.util.concurrent.Callable
// import net.bytebuddy.implementation.bind.annotation.{RuntimeType, SuperCall, This}
//
// def intercept(tree: Tree): Tree = {
// println(global.phase.name)
// tree
// }
// }
def getLazy[T](name: String): T = {
val field = classOf[Global].getDeclaredField(name)
field.setAccessible(true)
field.get(global).asInstanceOf[T]
// protected lazy val phasesSet = new mutable.HashSet[SubComponent]
// protected lazy val phasesDescMap = new mutable.HashMap[SubComponent, String] withDefaultValue ""
}
def setup(): Unit = {
// val field = classOf[Global].getDeclaredField("typer$module")
// field.setAccessible(true)
// val cls = field.getType
val phases = getLazy[mutable.HashSet[SubComponent]]("phasesSet")
val phaseDescs = getLazy[mutable.Map[SubComponent, String]]("phasesDescMap")
val oldTyper = phases.find(s => s.phaseName == "typer").get
phases.remove(oldTyper)
val oldDesc = phaseDescs.get(oldTyper).get
phaseDescs.remove(oldTyper)
val field = classOf[Global].getDeclaredField("analyzer")
field.setAccessible(true)
// val newAnalyzer = new NewAnalyzer()
field.set(global, newAnalyzer)
phases.add(newAnalyzer.typerFactory2)
phaseDescs.put(newAnalyzer.typerFactory2, oldDesc)
//
// val proxyConstructor = new ByteBuddy()
// .subclass(cls, ConstructorStrategy.Default.IMITATE_SUPER_CLASS)
// .name("TestOverride")
// .method(ElementMatchers.named("applyImplicitArgs"))
// .intercept(MethodDelegation.to(SampleClassInterceptor))
// .make()
// .load(this.getClass.getClassLoader)
// .getLoaded
// .getDeclaredConstructor(classOf[Global])
//
// field.set(global, proxyConstructor.newInstance(global))
//
// println(global.typer.getClass)
}
setup()
private def collectTypeclasses: PluginComponent = new PluginComponent with Transform with TypingTransformers { component =>
override val phaseName: String = NewtsPlugin.this.name + "-collect"
override val global: NewtsPlugin.this.global.type = NewtsPlugin.this.global
override val runsAfter: List[String] = "typer" :: Nil
def newTransformer(unit: CompilationUnit): Transformer = new MyTransformer(unit)
class MyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {
// The magic happens in `unapply` of objects defined in mixed in traits
override def transform(tree: Tree): Tree = tree match {
case _ =>
tree match {
case tree@ValOrDefDef(mods, name, tpt, rhs) =>
if (mods.isImplicit && (tpt.tpe <:< typeclassSymbol.tpe) && !mods.isSynthetic) {
val tpe = tpt.tpe
val isExplicitOrphan =
if (mods.hasAccessorFlag) {
rhs match {
case field@Select(q, name) =>
field.symbol.hasAnnotation(orphanSymbol)
case _ => false
}
} else mods.hasAnnotationNamed(orphanSymbol.name)
val enclosing: Symbol = tree.symbol.asTerm.owner
if (enclosing.isAbstract) return super.transform(tree)
if (mods.isParameter) return super.transform(tree)
if (!enclosing.isModuleClass && !enclosing.isPackageObjectClass) {
if (!isExplicitOrphan)
error(s"Orphan type instance: ${name.longString}")
}
val enclosingModule = enclosing.companionSymbol
val nonOrphanLocations = tpe.typeConstructor.companion.termSymbol ::
tpe.typeArgs.map(t => t.typeSymbol.companion)
val isOrphan = !nonOrphanLocations.contains(enclosingModule)
if (isOrphan) {
if (!isExplicitOrphan)
error(s"Orphan type instance: ${name.longString}")
}
}
case _ => ()
}
super.transform(tree)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment