-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NestedFuture: new wart to avoid nested futures
Co-authored-by: Iván Molina Rebolledo <[email protected]>
- Loading branch information
1 parent
d7cbeb3
commit b1de43e
Showing
5 changed files
with
110 additions
and
1 deletion.
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
36 changes: 36 additions & 0 deletions
36
core/src/main/scala-2/org/wartremover/contrib/warts/NestedFuture.scala
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,36 @@ | ||
package org.wartremover | ||
package contrib.warts | ||
|
||
object NestedFuture extends WartTraverser { | ||
val message: String = | ||
"""`Future[Future[A]]` will not wait for and discard/cancel the inner future. | ||
|To chain the result of Future to other Future, use flatMap or a for comprehension. | ||
|""".stripMargin | ||
|
||
private val futureSymbols: Set[String] = Set( | ||
"scala.concurrent.Future", | ||
"com.twitter.util.Future" | ||
) | ||
|
||
def apply(u: WartUniverse): u.Traverser = { | ||
import u.universe._ | ||
|
||
new Traverser { | ||
override def traverse(tree: Tree): Unit = { | ||
tree match { | ||
// Ignore trees marked by SuppressWarnings | ||
case t if hasWartAnnotation(u)(t) => | ||
case t: TermTree if futureSymbols.contains(t.tpe.typeSymbol.fullName) => | ||
t.tpe.typeArgs match { | ||
case Seq(singleArg) if singleArg.typeSymbol.fullName == t.tpe.typeSymbol.fullName => | ||
warning(u)(tree.pos, message) | ||
super.traverse(tree) | ||
case _ => super.traverse(tree) | ||
} | ||
case _ => | ||
super.traverse(tree) | ||
} | ||
} | ||
} | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
core/src/main/scala-3/org/wartremover/contrib/warts/NestedFuture.scala
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,33 @@ | ||
package org.wartremover | ||
package contrib.warts | ||
|
||
import scala.concurrent.Future | ||
|
||
object NestedFuture extends WartTraverser { | ||
val message: String = | ||
"""`Future[Future[A]]` will not wait for and discard/cancel the inner future. | ||
|To chain the result of Future to other Future, use flatMap or a for comprehension. | ||
|""".stripMargin | ||
|
||
def apply(u: WartUniverse): u.Traverser = { | ||
new u.Traverser(this) { | ||
import q.reflect.* | ||
|
||
override def traverseTree(tree: Tree)(owner: Symbol): Unit = | ||
tree match { | ||
case _ if tree.isExpr => | ||
tree.asExpr match { | ||
case '{ scala.Predef.??? } => super.traverseTree(tree)(owner) | ||
case '{ | ||
type a | ||
$f: Future[Future[`a`]] | ||
} => | ||
warning(tree.pos, message) | ||
super.traverseTree(tree)(owner) | ||
case _ => super.traverseTree(tree)(owner) | ||
} | ||
case _ => super.traverseTree(tree)(owner) | ||
} | ||
} | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
core/src/test/scala/org/wartremover/contrib/test/NestedFutureTest.scala
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,35 @@ | ||
package org.wartremover.contrib.test | ||
|
||
import org.scalatest.funsuite.AnyFunSuite | ||
import org.wartremover.contrib.warts.NestedFuture | ||
import org.wartremover.test.WartTestTraverser | ||
import scala.concurrent.Future | ||
|
||
class NestedFutureTest extends AnyFunSuite with ResultAssertions { | ||
implicit val ec: scala.concurrent.ExecutionContext = | ||
scala.concurrent.ExecutionContext.global | ||
|
||
test("single future doesn't warn") { | ||
val result = WartTestTraverser(NestedFuture) { | ||
val f: Future[Unit] = Future.successful(()) | ||
} | ||
assertEmpty(result) | ||
} | ||
|
||
test("nested Future[Future[Unit]] warns") { | ||
val result = WartTestTraverser(NestedFuture) { | ||
val f: Future[Future[Unit]] = Future.successful(Future.successful(())) | ||
} | ||
assertWarnings(result)(NestedFuture.message, 1) | ||
} | ||
|
||
test("func causes nested futures") { | ||
val result = WartTestTraverser(NestedFuture) { | ||
val futureFunc: String => Future[String] = arg => Future.successful(arg) | ||
futureFunc("hello world").map(futureFunc) | ||
} | ||
|
||
// NOTE: in scala 2 it emits 2 times, in scala 3 it emits 3 times | ||
assertWarningAnyTimes(result)(NestedFuture.message) | ||
} | ||
} |
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