Created
June 23, 2018 09:40
-
-
Save hobwekiva/e55719f2bb59d194920399a4696263e9 to your computer and use it in GitHub Desktop.
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
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