-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Inline SAM conversions can generate lots of code #16497
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
Comments
```scala inline given a: Conversion[String, Item] = Item(_) // error ``` will now produce this error: ``` 5 | inline given a: Conversion[String, Item] = Item(_) // error | ^^^^^^^ |inline given alias cannot have a function value as right-hand side. |Either drop `inline` or rewrite the given with an explicit `apply` method. |----------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | A function value on the right-hand side of an inline given alias would expand to | an anonymous class. Each application of the inline given would then create a | fresh copy of that class, which can increase code size in surprising ways. | For that reason, functions are disallowed as right hand sides of inline given aliases. | You should either drop `inline` or rewrite to an explicit `apply` method. E.g. | | inline given Conversion[A, B] = x => x.toB // error | | should be re-formulated as | | inline given Conversion[A, B] with | def apply(x: A) = x.toB | ``` Fixes scala#16497
Silent rewriting is problematic since the version with explicit apply creates a user-visible class name. So that leaves warning or error. I tend towards error, since it is clearer and the problem is easily avoided by a rewrite. EDIT: It seems there are some weird interactions with @compiletimeOnly. The error behavior of #16498 is different for test neg-macros/i11483. So maybe it's easier to just do a warning. The error behavior is implemented in #16498 and the warning behavior is implemented in #16499. |
```scala inline given a: Conversion[String, Item] = Item(_) ``` will now produce this warning: ``` 5 | inline given a: Conversion[String, Item] = Item(_) | ^^^^^^^ |An inline given alias with a function value as right-hand side can significantly increase |generated code size. You should either drop the `inline` or rewrite the given with an |explicit `apply` method. |---------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | A function value on the right-hand side of an inline given alias expands to | an anonymous class. Each application of the inline given will then create a | fresh copy of that class, which can increase code size in surprising ways. | For that reason, functions are discouraged as right hand sides of inline given aliases. | You should either drop `inline` or rewrite to an explicit `apply` method. E.g. | | inline given Conversion[A, B] = x => x.toB | | should be re-formulated as | | inline given Conversion[A, B] with | def apply(x: A) = x.toB | ``` Fixes scala#16497
To inline the conversion and the least amount of code we should use given Conversion[String, Item] with
inline def apply(x: String) = Item(x) |
There are four combinations class Item1(x: String)
inline given Conversion[String, Item1] = Item1(_)
def test1 = summon[Conversion[String, Item1]].apply("abc")
class Item2(x: String)
inline given Conversion[String, Item2] with
def apply(x: String) = Item2(x)
class Item3(x: String)
given Conversion[String, Item3] with
inline def apply(x: String) = Item3(x)
class Item4(x: String)
inline given Conversion[String, Item4] with
inline def apply(x: String) = Item4(x)
def test1 = summon[Conversion[String, Item1]].apply("abc")
def test2 = summon[Conversion[String, Item2]].apply("abc")
def test3 = summon[Conversion[String, Item3]].apply("abc")
def test4 = summon[Conversion[String, Item4]].apply("abc") which produce the following def test1: Item1 = ((x: String) => (new Item1(x)): Conversion[String, Item1]).apply("abc")
def test2: Item2 = (new given_Conversion_String_Item2(): given_Conversion_String_Item2).apply("abc")
def test3: Item3 = new Item3("abc"): Item3 // BEST
def test4: Item4 =
val given_Conversion_String_Item4_this: given_Conversion_String_Item4 = new given_Conversion_String_Item4():given_Conversion_String_Item4
new Item4("abc"):Item4 |
Is there any situation right now where |
It just inlines the constructor call. Could be useful when passed to an inline using parameter that wants to inspect what was passed. |
If the given has parameters we get class Item1(x: String)(using DummyImplicit)
inline given (using d: DummyImplicit): Conversion[String, Item1] = Item1(_)
class Item2(x: String)(using DummyImplicit)
inline given (using d: DummyImplicit): Conversion[String, Item2] with
def apply(x: String) = Item2(x)
class Item3(x: String)(using DummyImplicit)
given (using d: DummyImplicit): Conversion[String, Item3] with
inline def apply(x: String) = Item3(x)
class Item4(x: String)(using DummyImplicit)
inline given (using d: DummyImplicit): Conversion[String, Item4] with
inline def apply(x: String) = Item4(x)
def test1 = summon[Conversion[String, Item1]].apply("abc")
def test2 = summon[Conversion[String, Item2]].apply("abc")
def test3 = summon[Conversion[String, Item3]].apply("abc")
def test4 = summon[Conversion[String, Item4]].apply("abc") we get def test1: Item1 =
(((x: String) => new Item1(x)(DummyImplicit.dummyImplicit)): Conversion[String, Item1]).apply("abc")
def test2: Item2 =
(new given_Conversion_String_Item2(using DummyImplicit.dummyImplicit)(): given_Conversion_String_Item2).apply("abc")
def test3: Item3 =
val given_Conversion_String_Item3_this: given_Conversion_String_Item3 = given_Conversion_String_Item3(DummyImplicit.dummyImplicit)
new Item3("abc")(given_Conversion_String_Item3_this.Foo$package$given_Conversion_String_Item3$$inline$d):Item3
def test4: Item4 =
val given_Conversion_String_Item4_this: given_Conversion_String_Item4 = new given_Conversion_String_Item4(using DummyImplicit.dummyImplicit)() :given_Conversion_String_Item4
new Item4("abc")(given_Conversion_String_Item4_this.Foo$package$given_Conversion_String_Item4$$inline$d):Item4 in this case the |
```scala inline given a: Conversion[String, Item] = Item(_) ``` will now produce this warning: ``` 5 | inline given a: Conversion[String, Item] = Item(_) | ^^^^^^^ |An inline given alias with a function value as right-hand side can significantly increase |generated code size. You should either drop the `inline` or rewrite the given with an |explicit `apply` method. |---------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | A function value on the right-hand side of an inline given alias expands to | an anonymous class. Each application of the inline given will then create a | fresh copy of that class, which can increase code size in surprising ways. | For that reason, functions are discouraged as right hand sides of inline given aliases. | You should either drop `inline` or rewrite to an explicit `apply` method. E.g. | | inline given Conversion[A, B] = x => x.toB | | should be re-formulated as | | inline given Conversion[A, B] with | def apply(x: A) = x.toB | ``` Fixes #16497 Alternative to #16498
```scala inline given a: Conversion[String, Item] = Item(_) ``` will now produce this warning: ``` 5 | inline given a: Conversion[String, Item] = Item(_) | ^^^^^^^ |An inline given alias with a function value as right-hand side can significantly increase |generated code size. You should either drop the `inline` or rewrite the given with an |explicit `apply` method. |---------------------------------------------------------------------------- | Explanation (enabled by `-explain`) |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - | A function value on the right-hand side of an inline given alias expands to | an anonymous class. Each application of the inline given will then create a | fresh copy of that class, which can increase code size in surprising ways. | For that reason, functions are discouraged as right hand sides of inline given aliases. | You should either drop `inline` or rewrite to an explicit `apply` method. E.g. | | inline given Conversion[A, B] = x => x.toB | | should be re-formulated as | | inline given Conversion[A, B] with | def apply(x: A) = x.toB | ``` Fixes scala#16497
Uh oh!
There was an error while loading. Please reload this page.
Compiler version
All 3.x versions
Minimized example
Here's some code that illustrates a problem found in Lichess:
Output
Here's the code generated for the first version:
And here's the code generated for the 2nd version:
The first version generates an anonymous function for the SAM-type Conversion, which will be expanded to an anonymous class later. Since the conversion is
inline
, this means that every one of its invocations will generate an anonymous class. This can lead to increased code pressure without the user noticing the problem, since all invocations are implicit. It's probably this what caused the increased code size in the Lichess port to Scala 3.The second version does not have this problem. There's a single class generated and every inline conversion just creates a new object instance.
Question
Should we prevent the first version or at least warn about it? I see the following possibilities:
apply
.What do people think? Which of these should be preferred?
The text was updated successfully, but these errors were encountered: