Skip to content

Commit bf3d60a

Browse files
Merge pull request #9163 from dotty-staging/fix-#7294
Fix #7294: Add limit on trees that can be inlined in a compilation unit
2 parents b5b41b4 + 1851a2b commit bf3d60a

File tree

5 files changed

+67
-19
lines changed

5 files changed

+67
-19
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

+18-1
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,12 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
12141214
/** A key to be used in a context property that tracks enclosing inlined calls */
12151215
private val InlinedCalls = Property.Key[List[Tree]]()
12161216

1217+
/** A key to be used in a context property that tracks the number of inlined trees */
1218+
private val InlinedTrees = Property.Key[Counter]()
1219+
final class Counter {
1220+
var count: Int = 0
1221+
}
1222+
12171223
/** Record an enclosing inlined call.
12181224
* EmptyTree calls (for parameters) cancel the next-enclosing call in the list instead of being added to it.
12191225
* We assume parameters are never nested inside parameters.
@@ -1230,14 +1236,25 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
12301236
else
12311237
call :: oldIC
12321238

1233-
ctx.fresh.setProperty(InlinedCalls, newIC)
1239+
val ctx1 = ctx.fresh.setProperty(InlinedCalls, newIC)
1240+
if oldIC.isEmpty then ctx1.setProperty(InlinedTrees, new Counter) else ctx1
12341241
}
12351242

12361243
/** All enclosing calls that are currently inlined, from innermost to outermost.
12371244
*/
12381245
def enclosingInlineds(implicit ctx: Context): List[Tree] =
12391246
ctx.property(InlinedCalls).getOrElse(Nil)
12401247

1248+
/** Record inlined trees */
1249+
def addInlinedTrees(n: Int)(implicit ctx: Context): Unit =
1250+
ctx.property(InlinedTrees).foreach(_.count += n)
1251+
1252+
/** Check if the limit on the number of inlined trees has been reached */
1253+
def reachedInlinedTreesLimit(implicit ctx: Context): Boolean =
1254+
ctx.property(InlinedTrees) match
1255+
case Some(c) => c.count > ctx.settings.XmaxInlinedTrees.value
1256+
case None => false
1257+
12411258
/** The source file where the symbol of the `inline` method referred to by `call`
12421259
* is defined
12431260
*/

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class ScalaSettings extends Settings.SettingGroup {
7272
val Xhelp: Setting[Boolean] = BooleanSetting("-X", "Print a synopsis of advanced options.")
7373
val XnoForwarders: Setting[Boolean] = BooleanSetting("-Xno-forwarders", "Do not generate static forwarders in mirror classes.")
7474
val XmaxInlines: Setting[Int] = IntSetting("-Xmax-inlines", "Maximal number of successive inlines.", 32)
75+
val XmaxInlinedTrees: Setting[Int] = IntSetting("-Xmax-inlined-trees", "Maximal number of inlined trees.", 2_000_000)
7576
val Xmigration: Setting[ScalaVersion] = VersionSetting("-Xmigration", "Warn about constructs whose behavior may have changed since version.")
7677
val Xprint: Setting[List[String]] = PhasesSetting("-Xprint", "Print out program after")
7778
val XprintTypes: Setting[Boolean] = BooleanSetting("-Xprint-types", "Print tree types (debugging option).")

compiler/src/dotty/tools/dotc/typer/Inliner.scala

+30-18
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import Nullables.{given _}
2929
import collection.mutable
3030
import reporting.trace
3131
import util.Spans.Span
32+
import util.NoSourcePosition
3233
import dotty.tools.dotc.transform.{Splicer, TreeMapWithStages}
3334

3435
object Inliner {
@@ -71,14 +72,17 @@ object Inliner {
7172
* and body that replace it.
7273
*/
7374
def inlineCall(tree: Tree)(using Context): Tree = {
75+
val startId = ctx.source.nextId
76+
7477
if tree.symbol.denot != SymDenotations.NoDenotation && tree.symbol.owner.companionModule == defn.CompiletimeTestingPackageObject
7578
if (tree.symbol == defn.CompiletimeTesting_typeChecks) return Intrinsics.typeChecks(tree)
7679
if (tree.symbol == defn.CompiletimeTesting_typeCheckErrors) return Intrinsics.typeCheckErrors(tree)
7780

78-
/** Set the position of all trees logically contained in the expansion of
79-
* inlined call `call` to the position of `call`. This transform is necessary
80-
* when lifting bindings from the expansion to the outside of the call.
81-
*/
81+
82+
/** Set the position of all trees logically contained in the expansion of
83+
* inlined call `call` to the position of `call`. This transform is necessary
84+
* when lifting bindings from the expansion to the outside of the call.
85+
*/
8286
def liftFromInlined(call: Tree) = new TreeMap:
8387
override def transform(t: Tree)(using Context) =
8488
if call.span.exists then
@@ -115,20 +119,28 @@ object Inliner {
115119

116120
// assertAllPositioned(tree) // debug
117121
val tree1 = liftBindings(tree, identity)
118-
if (bindings.nonEmpty)
119-
cpy.Block(tree)(bindings.toList, inlineCall(tree1))
120-
else if (enclosingInlineds.length < ctx.settings.XmaxInlines.value) {
121-
val body = bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors
122-
new Inliner(tree, body).inlined(tree.sourcePos)
123-
}
124-
else
125-
errorTree(
126-
tree,
127-
i"""|Maximal number of successive inlines (${ctx.settings.XmaxInlines.value}) exceeded,
128-
|Maybe this is caused by a recursive inline method?
129-
|You can use -Xmax-inlines to change the limit.""",
130-
(tree :: enclosingInlineds).last.sourcePos
131-
)
122+
val tree2 =
123+
if bindings.nonEmpty then
124+
cpy.Block(tree)(bindings.toList, inlineCall(tree1))
125+
else if enclosingInlineds.length < ctx.settings.XmaxInlines.value && !reachedInlinedTreesLimit then
126+
val body = bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors
127+
new Inliner(tree, body).inlined(tree.sourcePos)
128+
else
129+
val (reason, setting) =
130+
if reachedInlinedTreesLimit then ("inlined trees", ctx.settings.XmaxInlinedTrees)
131+
else ("successive inlines", ctx.settings.XmaxInlines)
132+
errorTree(
133+
tree,
134+
i"""|Maximal number of $reason (${setting.value}) exceeded,
135+
|Maybe this is caused by a recursive inline method?
136+
|You can use ${setting.name} to change the limit.""",
137+
(tree :: enclosingInlineds).last.sourcePos
138+
)
139+
140+
val endId = ctx.source.nextId
141+
addInlinedTrees(endId - startId)
142+
143+
tree2
132144
}
133145

134146
/** Try to inline a pattern with an inline unapply method. Fail with error if the maximal

tests/neg/i7294-a.scala

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package foo
2+
3+
trait Foo { def g(x: Int): Any }
4+
5+
inline given f[T <: Foo] as T = ??? match {
6+
case x: T => x.g(10) // error
7+
}
8+
9+
@main def Test = f // error // error

tests/neg/i7294-b.scala

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package foo
2+
3+
trait Foo { def g(x: Any): Any }
4+
5+
inline given f[T <: Foo] as T = ??? match {
6+
case x: T => x.g(10) // error
7+
}
8+
9+
@main def Test = f // error // error

0 commit comments

Comments
 (0)