-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Make implicit function variables safe as arguments (fixes #10889) #13750
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
Changes from 1 commit
e30dffa
a54bfd4
54f50e5
9133065
e76f078
009f18b
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 |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package dotty.tools | ||
package dotc | ||
package transform | ||
|
||
import MegaPhase._ | ||
import core._ | ||
import Symbols._ | ||
import SymDenotations._ | ||
import Contexts._ | ||
import Types._ | ||
import Flags._ | ||
import Decorators._ | ||
import DenotTransformers._ | ||
import core.StdNames.nme | ||
import core.StdNames | ||
import ast.Trees._ | ||
import reporting.trace | ||
import config.Printers.{ transforms => debug } | ||
|
||
/** Abstract base class of ByNameClosures and ElimByName, factoring out the | ||
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. Copy paste error here. The phase needs an detailed explanation. |
||
* common functionality to transform arguments of by-name parameters. | ||
*/ | ||
class ElimRedundantContextualClosure extends MiniPhase with IdentityDenotTransformer { thisPhase: DenotTransformer => | ||
import ast.tpd._ | ||
import ast.untpd | ||
|
||
override def phaseName:String = ElimRedundantContextualClosure.name | ||
|
||
/** The info of the tree's symbol before it is potentially transformed in this phase */ | ||
private def originalDenotation(tree: Tree)(using Context) = | ||
atPhase(thisPhase)(tree.symbol.denot) | ||
|
||
override def transformApply(tree: Apply)(using Context): Tree = | ||
trace(s"transforming ${tree.show} at phase ${ctx.phase}", show = true) { | ||
|
||
def transformArg(arg: Tree, formal: Type): Tree = { | ||
val veryFormal = formal.widenDealias | ||
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. Why |
||
if(defn.isContextFunctionType(veryFormal) && untpd.isContextualClosure(arg)) { | ||
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. Use Scala 3 conditionals in new code. Never use |
||
val body = unsplice(closureBody(arg)) match { | ||
case Apply(Select(fn, nme.apply), _) => fn | ||
case other => other | ||
} // no-op if not a nested closure of some kind | ||
val underlyingBodyType = body.tpe.widenDealias | ||
val bodyIsContextual = defn.isContextFunctionType(underlyingBodyType) | ||
val bodyTypeMatches = TypeComparer.isSubType(underlyingBodyType, veryFormal) | ||
if(bodyIsContextual && bodyTypeMatches) { | ||
body | ||
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. Drop the redundant braces. We tend to use Scala 3 style throughout in new code. |
||
} else { | ||
arg | ||
} | ||
} else { | ||
arg | ||
} | ||
} | ||
|
||
val mt @ MethodType(_) = tree.fun.tpe.widen | ||
val args1 = tree.args.zipWithConserve(mt.paramInfos)(transformArg) | ||
cpy.Apply(tree)(tree.fun, args1) | ||
} | ||
} | ||
|
||
object ElimRedundantContextualClosure { | ||
val name: String = "elimRedundantContextualClosure" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import scala.annotation.tailrec | ||
|
||
import java.lang.management.ManagementFactory | ||
import java.lang.management.RuntimeMXBean | ||
|
||
import scala.jdk.CollectionConverters._ | ||
|
||
object Test { | ||
trait Txn {} | ||
type AtomicOp[Z] = Txn ?=> Z | ||
type VanillaAtomicOp[Z] = Txn => Z | ||
|
||
object AbortAndRetry extends scala.util.control.ControlThrowable("abort and retry") {} | ||
|
||
def beginTxn:Txn = new Txn {} | ||
|
||
@tailrec def retryN[Z](n:Int)(txn:AtomicOp[Z], i:Int = 0):Z = { | ||
try { | ||
given Txn = beginTxn | ||
val ret:Z = txn | ||
if(i < n) { throw AbortAndRetry } | ||
ret | ||
} catch { | ||
case AbortAndRetry => retryN(n)(txn, i + 1) | ||
} | ||
} | ||
|
||
|
||
@tailrec def safeRetryN[Z](n:Int)(txn:VanillaAtomicOp[Z], i:Int = 0):Z = { | ||
try { | ||
given Txn = beginTxn | ||
val ret:Z = txn.asInstanceOf[AtomicOp[Z]] | ||
if(i < n) { throw AbortAndRetry } | ||
ret | ||
} catch { | ||
case AbortAndRetry => safeRetryN(n)(txn, i + 1) | ||
} | ||
} | ||
|
||
object StackSize { | ||
def unapply(arg:String):Option[Int] = { | ||
Option(arg match { | ||
case s"-Xss$rest" => rest | ||
case s"-XX:ThreadStackSize=$rest" => rest | ||
case _ => null | ||
}).map{ rest => | ||
val shift = (rest.toLowerCase.last match { | ||
case 'k' => 10 | ||
case 'm' => 20 | ||
case 'g' => 30 | ||
case _ => 0 | ||
}) | ||
(if(shift > 0) { | ||
rest.dropRight(1) | ||
} else { | ||
rest | ||
}).toInt << shift | ||
} | ||
} | ||
} | ||
def main(args:Array[String]) = { | ||
val arguments = ManagementFactory.getRuntimeMXBean.getInputArguments | ||
// 64bit VM defaults to 1024K / 1M stack size, 32bit defaults to 320k | ||
// Use 1024 as upper bound. | ||
val maxStackSize:Int = arguments.asScala.reverseIterator.collectFirst{case StackSize(stackSize) => stackSize}.getOrElse(1 << 20) | ||
|
||
val testTxn:VanillaAtomicOp[Boolean] = { | ||
(txn:Txn) => | ||
given Txn = txn | ||
true | ||
} | ||
|
||
Console.println(try { | ||
// maxStackSize is a good upper bound on linear stack growth | ||
// without assuming too much about frame size | ||
// (1 byte / frame is conservative even for a z80) | ||
retryN(maxStackSize)(testTxn.asInstanceOf[AtomicOp[Boolean]]) == safeRetryN(maxStackSize)(testTxn) | ||
} catch { | ||
case e:StackOverflowError => | ||
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. Isn't it easier to just probe stack depth with (Generally wary of tests for SOE and OOM.) 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. I'm not sure I see what you have in mind, can you be a little bit more explicit? 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. apologies in advance if I'm missing the whole point, I'm still reading the issue. I was thinking of https://github.com/scala/scala/blob/2.13.x/test/junit/scala/collection/IteratorTest.scala#L36 although counting frames is subject to optimizations. 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. Ah, sure, we could have the return value from |
||
Console.println(s"Exploded after ${e.getStackTrace.length} frames") | ||
false | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd shorten the name to
ElimContextClosures
. Also, the comment should be active form.