Skip to content

Commit 1f14b35

Browse files
committed
Add warning for anonymous inline classes (#16723)
1 parent e2c456f commit 1f14b35

File tree

17 files changed

+87
-16
lines changed

17 files changed

+87
-16
lines changed

compiler/src/dotty/tools/dotc/printing/Formatting.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ object Formatting {
4242
trait CtxShow:
4343
def run(using Context): Shown
4444

45-
private inline def CtxShow(inline x: Context ?=> Shown) = new CtxShow { def run(using Context) = x(using ctx) }
45+
private inline def CtxShow(inline x: Context ?=> Shown) =
46+
class InlinedCtxShow extends CtxShow { def run(using Context) = x(using ctx) }
47+
new InlinedCtxShow
4648
private def toStr[A: Show](x: A)(using Context): String = Shown.toStr(toShown(x))
4749
private def toShown[A: Show](x: A)(using Context): Shown = Show[A].show(x).runCtxShow
4850

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
208208
case UnstableInlineAccessorID // errorNumber: 192
209209
case VolatileOnValID // errorNumber: 193
210210
case ExtensionNullifiedByMemberID // errorNumber: 194
211+
case InlinedAnonClassWarningID // errorNumber: 195
211212

212213
def errorNumber = ordinal - 1
213214

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3104,6 +3104,15 @@ extends SyntaxMsg(InlineGivenShouldNotBeFunctionID):
31043104
| inline def apply(x: A) = x.toB
31053105
"""
31063106

3107+
class InlinedAnonClassWarning()(using Context)
3108+
extends Message(InlinedAnonClassWarningID):
3109+
def kind = MessageKind.PotentialIssue
3110+
def msg(using Context) = "New anonymous class definition will be duplicated at each inline site"
3111+
def explain(using Context) =
3112+
i"""Anonymous class will be defined at each use site, which may lead to a larger number of classfiles.
3113+
|
3114+
|To inline class definitions, you may provide an explicit class name to avoid this warning."""
3115+
31073116
class ValueDiscarding(tp: Type)(using Context)
31083117
extends Message(ValueDiscardingID):
31093118
def kind = MessageKind.PotentialIssue

compiler/src/dotty/tools/dotc/transform/PruneErasedDefs.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Symbols.*
1010
import typer.RefChecks
1111
import MegaPhase.MiniPhase
1212
import ast.tpd
13+
import reporting.InlinedAnonClassWarning
1314

1415
import config.Feature
1516
import Decorators.*
@@ -51,6 +52,7 @@ class PruneErasedDefs extends MiniPhase with SymTransformer { thisTransform =>
5152
else cpy.ValDef(tree)(rhs = trivialErasedTree(tree.rhs))
5253

5354
override def transformDefDef(tree: DefDef)(using Context): Tree =
55+
RefChecks.checkNoInlineAnnoClasses(tree)
5456
checkErasedInExperimental(tree.symbol)
5557
if !tree.symbol.isEffectivelyErased || tree.rhs.isEmpty then tree
5658
else cpy.DefDef(tree)(rhs = trivialErasedTree(tree.rhs))

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ object RefChecks {
3333
def isStable = true
3434
}
3535

36+
def checkNoInlineAnnoClasses(tree: DefDef)(using Context): Unit =
37+
if tree.symbol.is(Inline) then
38+
new TreeTraverser {
39+
def traverse(tree: Tree)(using Context): Unit =
40+
tree match
41+
case tree: TypeDef if tree.symbol.isAnonymousClass =>
42+
report.warning(new InlinedAnonClassWarning(), tree.symbol.sourcePos)
43+
case _ => traverseChildren(tree)
44+
}.traverse(tree)
45+
3646
/** Only one overloaded alternative is allowed to define default arguments */
3747
private def checkOverloadedRestrictions(clazz: Symbol)(using Context): Unit = {
3848
// Using the default getters (such as methodName$default$1) as a cheap way of

tests/neg/i13044.check

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
-- [E195] Potential Issue Warning: tests/neg/i13044.scala:26:8 ---------------------------------------------------------
2+
26 | new Schema[A] {
3+
| ^
4+
| New anonymous class definition will be duplicated at each inline site
5+
|
6+
| longer explanation available when compiling with `-explain`
7+
-- [E195] Potential Issue Warning: tests/neg/i13044.scala:32:8 ---------------------------------------------------------
8+
32 | new Schema[A] {
9+
| ^
10+
| New anonymous class definition will be duplicated at each inline site
11+
|
12+
| longer explanation available when compiling with `-explain`
113
-- Error: tests/neg/i13044.scala:65:40 ---------------------------------------------------------------------------------
214
65 | implicit def typeSchema: Schema[A] = Schema.gen // error // error
315
| ^^^^^^^^^^

tests/pos/i17314.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ object circelike {
1313
inline final def derived[A](using conf: Configuration)(using
1414
inline mirror: Mirror.Of[A]
1515
): ConfiguredCodec[A] =
16-
new ConfiguredCodec[A]:
16+
class InlinedConfiguredCodec extends ConfiguredCodec[A]:
1717
val codec = summonInline[Codec[URI]] // simplification
18+
new InlinedConfiguredCodec
1819
}
1920

2021
object foo {

tests/pos/not-looping-implicit.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ object Schema {
2424
inline summonInline[Mirror.Of[A]] match {
2525
case m: Mirror.SumOf[A] =>
2626
lazy val members = recurse[m.MirroredElemLabels, m.MirroredElemTypes]()
27-
new Schema[A] {}
27+
???
2828
case m: Mirror.ProductOf[A] =>
2929
lazy val fields = recurse[m.MirroredElemLabels, m.MirroredElemTypes]()
30-
new Schema[A] {}
30+
???
3131
}
3232

3333
inline given gen[A]: Schema[A] = derived[A]

tests/run/i11050.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,14 @@ object Show:
113113

114114
inline def show[T](x: T): String = summonInline[Show[T]].show(x)
115115

116-
transparent inline def derived[T](implicit ev: Mirror.Of[T]): Show[T] = new {
117-
def show(x: T): String = inline ev match {
118-
case m: Mirror.ProductOf[T] => showProduct(x.asInstanceOf[Product], m)
119-
case m: Mirror.SumOf[T] => showCases[m.MirroredElemTypes](0)(x, m.ordinal(x))
116+
transparent inline def derived[T](implicit ev: Mirror.Of[T]): Show[T] =
117+
class InlinedShow extends Show[T] { // provide name to anonymous class
118+
def show(x: T): String = inline ev match {
119+
case m: Mirror.ProductOf[T] => showProduct(x.asInstanceOf[Product], m)
120+
case m: Mirror.SumOf[T] => showCases[m.MirroredElemTypes](0)(x, m.ordinal(x))
121+
}
120122
}
121-
}
123+
new InlinedShow
122124

123125
transparent inline def showProduct[T](x: Product, m: Mirror.ProductOf[T]): String =
124126
constValue[m.MirroredLabel] + showElems[m.MirroredElemTypes, m.MirroredElemLabels](0, Nil)(x)

tests/warn/i15503i.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ package foo.test.i16679a:
247247
import scala.deriving.Mirror
248248
object CaseClassByStringName:
249249
inline final def derived[A](using inline A: Mirror.Of[A]): CaseClassByStringName[A] =
250-
new CaseClassByStringName[A]:
250+
new CaseClassByStringName[A]: // warn
251251
def name: String = A.toString
252252

253253
object secondPackage:
@@ -263,7 +263,7 @@ package foo.test.i16679b:
263263
object CaseClassName:
264264
import scala.deriving.Mirror
265265
inline final def derived[A](using inline A: Mirror.Of[A]): CaseClassName[A] =
266-
new CaseClassName[A]:
266+
new CaseClassName[A]: // warn
267267
def name: String = A.toString
268268

269269
object Foo:
@@ -279,7 +279,7 @@ package foo.test.i17156:
279279
package a:
280280
trait Foo[A]
281281
object Foo:
282-
inline def derived[T]: Foo[T] = new Foo{}
282+
inline def derived[T]: Foo[T] = new Foo{} // warn
283283

284284
package b:
285285
import a.Foo

tests/warn/i15503j.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ package foo.unused.summon.inlines:
4949

5050
transparent inline given conflictInside: C =
5151
summonInline[A]
52-
new {}
52+
???
5353

5454
transparent inline given potentialConflict: C =
5555
summonInline[B]
56-
new {}
56+
???
5757

5858
val b: B = summon[B]
5959
val c: C = summon[C]

tests/warn/i16723.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- [E195] Potential Issue Warning: tests/warn/i16723.scala:3:2 ---------------------------------------------------------
2+
3 | new Object {} // warn
3+
| ^
4+
| New anonymous class definition will be duplicated at each inline site
5+
|
6+
| longer explanation available when compiling with `-explain`

tests/warn/i16723.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
inline def foo =
2+
class NotAnon
3+
new Object {} // warn

tests/warn/i16723a.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- [E195] Potential Issue Warning: tests/warn/i16723a.scala:5:38 -------------------------------------------------------
2+
5 |inline given Converter[Int, String] = new Converter { // warn
3+
| ^
4+
| New anonymous class definition will be duplicated at each inline site
5+
|
6+
| longer explanation available when compiling with `-explain`

tests/warn/i16723a.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
trait Converter[A, B] {
2+
def convert: A => B
3+
}
4+
5+
inline given Converter[Int, String] = new Converter { // warn
6+
def convert = _.toString()
7+
}
8+
9+
def foo(using bar: Converter[Int, String]) =
10+
"foo"
11+
12+
@main
13+
def main =
14+
foo
15+
foo
16+
foo
17+
foo

0 commit comments

Comments
 (0)