@react macro fails when building docs if Props has scaladocs
harpocrates opened this issue · 6 comments
When trying to build docs with sbt doc
, the @react
annotation doesn't work if the Props
defined inside the class has a scaladoc on it. Outside of trying to build docs, everything works fine.
$ sbt doc
Main Scala API documentation to /Users/alec/Scratch/slinky-bug/target/scala-2.13/api...
[error] /Users/alec/Scratch/slinky-bug/src/main/scala/bug/Main.scala:11:2: Components must define a Props type or case class, but none was found.
[error] @react class MyApp extends StatelessComponent {
[error] ^
[error] /Users/alec/Scratch/slinky-bug/src/main/scala/bug/Main.scala:23:21: not found: value MyApp
[error] ReactDOM.render(MyApp("hello world"), container)
[error] ^
[error] two errors found
[error] (Compile / doc) Scaladoc generation failed
[error] Total time: 4 s, completed 6-Jun-2020 6:07:15 PM
Complete example:
package bug
import slinky.core._
import slinky.core.annotations.react
import slinky.web.ReactDOM
import slinky.web.html._
import scala.scalajs.js.annotation.{JSExportTopLevel}
import org.scalajs.dom
@react class MyApp extends StatelessComponent {
/** @param message the message */
case class Props(message: String)
def render = span(props.message)
}
object Exports {
@JSExportTopLevel("mountMyApp")
def mountMyApp(container: dom.raw.Element): Unit = {
ReactDOM.render(MyApp("hello world"), container)
}
}
Version details:
- Scala 2.13.1 or 2.12.10 (both trigger the bug)
- Slinky 0.6.5
- React 16.12.0
Other than that, just wanted to say think you for the amazing project. ❤️
Thanks for the bug report! Probably a bug with how we look for the Props
definition in the macro.
I spent a little time yesterday looking into this. I worry it won't be an easy fix - see the Scala bug where I referenced this issue. The DocDef
issue affects almost all quasiquote pattern matching of ASTs. Some other places this issue crops up:
- the
Props
/State
/Snapshot
members of aclass
annotated with@react
don't work if they have scaladocs (the original bug report) - the
Props
members of anobject
annotated with@react
doesn't work if it has a scaladoc object
andclass
annotated with@react
don't match if they have scaladocs
It is possible work around these one by one, but it gets tedious and error-prone.
Here's a sample diff which solves 1:
diff --git a/core/build.sbt b/core/build.sbt
index 809fd1a..2150c21 100644
--- a/core/build.sbt
+++ b/core/build.sbt
@@ -3,6 +3,7 @@ enablePlugins(ScalaJSPlugin)
name := "slinky-core"
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value
+libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value
scalacOptions ++= {
if (scalaJSVersion.startsWith("0.6.")) Seq("-P:scalajs:sjsDefinedByDefault")
diff --git a/core/src/main/scala/slinky/core/annotations/react.scala b/core/src/main/scala/slinky/core/annotations/react.scala
index b6ce202..d8b034d 100644
--- a/core/src/main/scala/slinky/core/annotations/react.scala
+++ b/core/src/main/scala/slinky/core/annotations/react.scala
@@ -32,7 +32,19 @@ object ReactMacrosImpl {
def createComponentBody(c: whitebox.Context)(cls: c.Tree): (c.Tree, List[c.Tree]) = {
import c.universe._
val q"..$_ class ${className: Name} extends ..$parents { $self => ..$stats}" = cls // scalafix:ok
- val (propsDefinition, applyMethods) = stats.flatMap {
+
+ // Workaround for <https://github.com/scala/bug/issues/7993>
+ val unwrapDocDef = (t: Tree) => {
+ import scala.tools.nsc.ast.Trees
+
+ if (t.isInstanceOf[Trees#DocDef]) {
+ t.asInstanceOf[Trees#DocDef].definition.asInstanceOf[Tree]
+ } else {
+ t
+ }
+ }
+
+ val (propsDefinition, applyMethods) = stats.map(unwrapDocDef).flatMap {
case defn @ q"..$_ type Props = ${_}" =>
Some((defn, Seq()))
@@ -75,7 +87,7 @@ object ReactMacrosImpl {
}.headOption
.getOrElse(c.abort(c.enclosingPosition, "Components must define a Props type or case class, but none was found."))
- val stateDefinition = stats.flatMap {
+ val stateDefinition = stats.map(unwrapDocDef).flatMap {
case defn @ q"..$_ type State = ${_}" =>
Some(defn)
case defn @ q"case class State[..$_](...$_) extends ..$_ { $_ => ..$_ }" =>
@@ -83,7 +95,7 @@ object ReactMacrosImpl {
case _ => None
}.headOption
- val snapshotDefinition = stats.flatMap {
+ val snapshotDefinition = stats.map(unwrapDocDef).flatMap {
case defn @ q"type Snapshot = ${_}" =>
Some(defn)
case defn @ q"case class Snapshot[..$_](...$_) extends ..$_ { $_ => ..$_ }" =>
@@ -105,8 +117,10 @@ object ReactMacrosImpl {
null.asInstanceOf[Snapshot]
()
})
- ..${stats.filterNot(s =>
- s == propsDefinition || s == stateDefinition.orNull || s == snapshotDefinition.orNull
+ ..${stats.filterNot(s => {
+ val s2 = unwrapDocDef(s)
+ s2 == propsDefinition || s2 == stateDefinition.orNull || s2 == snapshotDefinition.orNull
+ }
)}
}"""
Note that this solution just throws away the doc (which is probably not the right thing to do - the doc should just be moved to the right place).
@harpocrates thanks so much for looking into this! Would you be able to make a PR with these changes?
For now, I think it's fine to throw away the docs given that there's no other nice solution for quasiquotes, and we can create a separate issue for passing through the docs. Even though it's not the ideal result, it's probably better than just crashing the compiler.
Are you OK with the part that adds libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value
though? I dislike being tied to concrete compiler types, but agree it would be better than a crash.
@harpocrates apologies for the late response! I think it would be fine to include that dependency, as you said it's better than having compiler crashes.
Hi, i had the same problem. I tried many things, what worked (not sure if only that) was when i improved my scalacOptions.
I left only
"-deprecation", // Emit warning and location for usages of deprecated APIs.
"-encoding",
"utf-8", // Specify character encoding used by source files.
"-explaintypes", // Explain type errors in more detail.
"-feature", // Emit warning and location for usages of features that should be imported explicitly.
"-language:existentials", // Existential types (besides wildcard types) can be written and inferred
"-language:experimental.macros", // Allow macro definition (besides implementation and application)
"-language:higherKinds", // Allow higher-kinded types
"-language:implicitConversions", // Allow definition of implicit functions called views
"-unchecked", // Enable additional warnings where generated code depends on assumptions.
"-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined.
"-Ywarn-numeric-widen",
"-Ymacro-annotations",
i use scala 2.13.3 and slinky 0.6.6
edit: maybe its not the same problem i had only one error:
/Main.scala:51:7: not found: value App
[error] App(()),
[error] ^
[error] one error found