-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Support zinc invalidation for type arguments in macro calls #23900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -165,6 +165,26 @@ private class ExtractDependenciesCollector(rec: DependencyRecorder) extends tpd. | |
rec.addClassDependency(parent.tpe.classSymbol, depContext) | ||
} | ||
|
||
// Only reference DependencyByMacroExpansion if it an be found on the classpath, | ||
// as it was added later to the zinc.apiinfo DependencyContext enum | ||
// e.g. pre 1.10.x sbt would throw java.lang.NoSuchFieldError errors here | ||
lazy val allowsDependencyByMacroExpansion = | ||
classOf[DependencyContext].getFields().exists(_.getName() == "DependencyByMacroExpansion") | ||
|
||
private def addMacroDependency(trees: List[Tree])(using Context): Unit = | ||
if (allowsDependencyByMacroExpansion) { | ||
val traverser = new TypeDependencyTraverser { | ||
def addDependency(symbol: Symbol) = | ||
if (!ignoreDependency(symbol)) { | ||
val enclOrModuleClass = if (symbol.is(ModuleVal)) symbol.moduleClass else symbol.enclosingClass | ||
assert(enclOrModuleClass.isClass, s"$enclOrModuleClass, $symbol") | ||
|
||
rec.addClassDependency(enclOrModuleClass, DependencyByMacroExpansion) | ||
} | ||
} | ||
trees.foreach(tree => traverser.traverse(tree.tpe)) | ||
} | ||
|
||
private def depContextOf(cls: Symbol)(using Context): DependencyContext = | ||
if cls.isLocal then LocalDependencyByInheritance | ||
else DependencyByInheritance | ||
|
@@ -226,6 +246,11 @@ private class ExtractDependenciesCollector(rec: DependencyRecorder) extends tpd. | |
case _ => | ||
} | ||
|
||
tree match | ||
case TypeApply(fun, args) if fun.symbol.is(Inline) => | ||
addMacroDependency(args) | ||
Comment on lines
+250
to
+251
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this might fix the specific shape of macro like MacWire that lifts the type symbol into a constructor, but I don't think it addresses the larger issue that we're not traversing the generated code, which I tried to fix in #24171. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Realistically I think we need both - some macros might not directly generate the passed type argument in the resulting tree, but may still use it to decide what that tree should be. I'll review your solution later today and then rebase this PR on top of that |
||
case _ => | ||
|
||
tree match { | ||
case tree: Inlined if !tree.inlinedFromOuterScope => | ||
// The inlined call is normally ignored by TreeTraverser but we need to | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
object Components extends App { | ||
try { | ||
wire[Dep] | ||
} catch { | ||
case e: Throwable => | ||
e.printStackTrace() | ||
sys.exit(-1) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
class Dep() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
class Dep(int: Int) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import scala.quoted.* | ||
|
||
inline def wire[T]: T = ${ wireImpl[T] } | ||
def wireImpl[T: Type](using q: Quotes): Expr[T] = { | ||
import q.reflect.* | ||
|
||
lazy val targetType = TypeRepr.of[T] | ||
val constructorValue = targetType.typeSymbol.primaryConstructor | ||
val constructionMethodTree: Term = { | ||
val ctor = Select(New(TypeIdent(targetType.typeSymbol)), constructorValue) | ||
if (targetType.typeArgs.isEmpty) ctor else ctor.appliedToTypes(targetType.typeArgs) | ||
} | ||
val constructorArgsValue = List(Nil) | ||
val code: Tree = constructorArgsValue.foldLeft(constructionMethodTree)((acc: Term, args: List[Term]) => Apply(acc, args)) | ||
code.asExprOf[T] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import sbt._ | ||
import Keys._ | ||
|
||
object DottyInjectedPlugin extends AutoPlugin { | ||
override def requires = plugins.JvmPlugin | ||
override def trigger = allRequirements | ||
|
||
override val projectSettings = Seq( | ||
scalaVersion := sys.props("plugin.scalaVersion") | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
> run | ||
$ copy-file Dep.scala-added Dep.scala | ||
-> compile |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
package A | ||
class A |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package app | ||
|
||
import Macros.* | ||
import A.A | ||
|
||
object App { | ||
@main def hasFields(expected: Boolean): Unit = { | ||
val actual = Macros.hasAnyField[A] | ||
assert(expected == actual, s"Expected $expected, obtained $actual") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// { | ||
// "projects": [ | ||
// { | ||
// "name": "app", | ||
// "dependsOn": [ | ||
// "macros", | ||
// "A" | ||
// ], | ||
// "scalaVersion": "2.13.12" | ||
// }, | ||
// { | ||
// "name": "macros", | ||
// "scalaVersion": "2.13.12" | ||
// }, | ||
// { | ||
// "name": "A", | ||
// "scalaVersion": "2.13.12" | ||
// } | ||
// ] | ||
// } | ||
|
||
lazy val app = project.in(file("app")) | ||
.dependsOn(macros, A) | ||
|
||
lazy val macros = project.in(file("macros")) | ||
|
||
lazy val A = project.in(file("A")) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package A | ||
class A { | ||
val hello: String = "" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package Macros | ||
|
||
import scala.quoted.* | ||
|
||
object Macros { | ||
inline def hasAnyField[T]: Boolean = ${ hasAnyFieldImpl[T] } | ||
|
||
def hasAnyFieldImpl[T: Type](using Quotes): Expr[Boolean] = { | ||
import quotes.reflect.* | ||
|
||
val hasField = TypeRepr.of[T].typeSymbol.fieldMembers.nonEmpty | ||
|
||
Expr(hasField) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import sbt._ | ||
import Keys._ | ||
|
||
object DottyInjectedPlugin extends AutoPlugin { | ||
override def requires = plugins.JvmPlugin | ||
override def trigger = allRequirements | ||
|
||
override val projectSettings = Seq( | ||
scalaVersion := sys.props("plugin.scalaVersion") | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# adapted from https://github.com/sbt/zinc/blob/1e422e5525c698aa71cc35b30c275c8c1c3135b2/zinc/src/sbt-test/macros/macro-type-change-2/test | ||
> app/run false | ||
$ copy-file changes/A1.scala A/A.scala | ||
> app/run true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
package A | ||
class A |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
package A | ||
class B extends A |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package app | ||
|
||
import Macros.* | ||
import A.B | ||
|
||
object App { | ||
@main def hasFields(expected: Boolean): Unit = { | ||
val actual = Macros.hasAnyField[B] | ||
assert(expected == actual, s"Expected $expected, obtained $actual") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// { | ||
// "projects": [ | ||
// { | ||
// "name": "app", | ||
// "dependsOn": [ | ||
// "macros", | ||
// "A" | ||
// ], | ||
// "scalaVersion": "2.13.12" | ||
// }, | ||
// { | ||
// "name": "macros", | ||
// "scalaVersion": "2.13.12" | ||
// }, | ||
// { | ||
// "name": "A", | ||
// "scalaVersion": "2.13.12" | ||
// } | ||
// ] | ||
// } | ||
|
||
lazy val app = project.in(file("app")) | ||
.dependsOn(macros, A) | ||
|
||
lazy val macros = project.in(file("macros")) | ||
|
||
lazy val A = project.in(file("A")) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package A | ||
class A { | ||
val hello: String = "" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package Macros | ||
|
||
import scala.quoted.* | ||
|
||
object Macros { | ||
inline def hasAnyField[T]: Boolean = ${ hasAnyFieldImpl[T] } | ||
|
||
def hasAnyFieldImpl[T: Type](using Quotes): Expr[Boolean] = { | ||
import quotes.reflect.* | ||
|
||
val hasField = TypeRepr.of[T].typeSymbol.fieldMembers.nonEmpty | ||
|
||
Expr(hasField) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import sbt._ | ||
import Keys._ | ||
|
||
object DottyInjectedPlugin extends AutoPlugin { | ||
override def requires = plugins.JvmPlugin | ||
override def trigger = allRequirements | ||
|
||
override val projectSettings = Seq( | ||
scalaVersion := sys.props("plugin.scalaVersion") | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# adapted from https://github.com/sbt/zinc/blob/1e422e5525c698aa71cc35b30c275c8c1c3135b2/zinc/src/sbt-test/macros/macro-type-change-3/test | ||
> app/run false | ||
$ copy-file changes/A1.scala A/A.scala | ||
> app/run true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
package A | ||
class A |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package app | ||
|
||
import Macros.* | ||
import A.A | ||
|
||
object App { | ||
@main def hasFields(expected: Boolean): Unit = { | ||
val actual = Macros.hasAnyField[A](true) | ||
assert(expected == actual, s"Expected $expected, obtained $actual") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// { | ||
// "projects": [ | ||
// { | ||
// "name": "app", | ||
// "dependsOn": [ | ||
// "macros", | ||
// "A" | ||
// ], | ||
// "scalaVersion": "2.13.12" | ||
// }, | ||
// { | ||
// "name": "macros", | ||
// "scalaVersion": "2.13.12" | ||
// }, | ||
// { | ||
// "name": "A", | ||
// "scalaVersion": "2.13.12" | ||
// } | ||
// ] | ||
// } | ||
|
||
lazy val app = project.in(file("app")) | ||
.dependsOn(macros, A) | ||
|
||
lazy val macros = project.in(file("macros")) | ||
|
||
lazy val A = project.in(file("A")) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package A | ||
class A { | ||
val hello: String = "" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package Macros | ||
|
||
import scala.quoted.* | ||
|
||
object Macros { | ||
inline def hasAnyField[T](placeholder: Boolean): Boolean = ${ hasAnyFieldImpl[T]('placeholder) } | ||
|
||
def hasAnyFieldImpl[T: Type](placeholder: Expr[Boolean])(using Quotes): Expr[Boolean] = { | ||
import quotes.reflect.* | ||
|
||
val hasField = TypeRepr.of[T].typeSymbol.fieldMembers.nonEmpty | ||
|
||
Expr(hasField) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import sbt._ | ||
import Keys._ | ||
|
||
object DottyInjectedPlugin extends AutoPlugin { | ||
override def requires = plugins.JvmPlugin | ||
override def trigger = allRequirements | ||
|
||
override val projectSettings = Seq( | ||
scalaVersion := sys.props("plugin.scalaVersion") | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# adapted from https://github.com/sbt/zinc/blob/1e422e5525c698aa71cc35b30c275c8c1c3135b2/zinc/src/sbt-test/macros/macro-type-change-4/test | ||
> app/run false | ||
$ copy-file changes/A1.scala A/A.scala | ||
> app/run true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
package app | ||
class A |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package app | ||
|
||
import Macros.* | ||
|
||
object App { | ||
@main def hasFields(expected: Boolean): Unit = { | ||
val actual = Macros.hasAnyField[A] | ||
assert(expected == actual, s"Expected $expected, obtained $actual") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// { | ||
// "projects": [ | ||
// { | ||
// "name": "app", | ||
// "dependsOn": [ | ||
// "macros" | ||
// ], | ||
// "scalaVersion": "2.13.12" | ||
// }, | ||
// { | ||
// "name": "macros", | ||
// "scalaVersion": "2.13.12" | ||
// } | ||
// ] | ||
// } | ||
|
||
lazy val app = project.in(file("app")) | ||
.dependsOn(macros) | ||
|
||
lazy val macros = project.in(file("macros")) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
package app | ||
class A { | ||
val hello: String = "" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package Macros | ||
|
||
import scala.quoted.* | ||
|
||
object Macros { | ||
inline def hasAnyField[T]: Boolean = ${ hasAnyFieldImpl[T] } | ||
|
||
def hasAnyFieldImpl[T: Type](using Quotes): Expr[Boolean] = { | ||
import quotes.reflect.* | ||
|
||
val hasField = TypeRepr.of[T].typeSymbol.fieldMembers.nonEmpty | ||
|
||
Expr(hasField) | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.