Skip to content

Commit a4a379f

Browse files
committed
Merge branch 'master' into fix/19806-wrong-tasty-of-scala-mod-cls-ref
2 parents 2708c8c + 2c2e8af commit a4a379f

File tree

13 files changed

+265
-27
lines changed

13 files changed

+265
-27
lines changed

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1967,10 +1967,7 @@ object desugar {
19671967
val applyVParams = args.zipWithIndex.map {
19681968
case (p, n) => makeSyntheticParameter(n + 1, p)
19691969
}
1970-
tree match
1971-
case tree: FunctionWithMods =>
1972-
untpd.FunctionWithMods(applyVParams, result, tree.mods, tree.erasedParams)
1973-
case _ => untpd.Function(applyVParams, result)
1970+
cpy.Function(tree)(applyVParams, result).asInstanceOf[untpd.Function]
19741971
}
19751972
}
19761973

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,11 +1255,12 @@ object Trees {
12551255
case _ => finalize(tree, untpd.Ident(name)(sourceFile(tree)))
12561256
}
12571257
def Select(tree: Tree)(qualifier: Tree, name: Name)(using Context): Select = tree match {
1258-
case tree: SelectWithSig =>
1259-
if ((qualifier eq tree.qualifier) && (name == tree.name)) tree
1260-
else finalize(tree, SelectWithSig(qualifier, name, tree.sig)(sourceFile(tree)))
12611258
case tree: Select if (qualifier eq tree.qualifier) && (name == tree.name) => tree
1262-
case _ => finalize(tree, untpd.Select(qualifier, name)(sourceFile(tree)))
1259+
case _ =>
1260+
val tree1 = tree match
1261+
case tree: SelectWithSig => untpd.SelectWithSig(qualifier, name, tree.sig)(using sourceFile(tree))
1262+
case _ => untpd.Select(qualifier, name)(using sourceFile(tree))
1263+
finalize(tree, tree1)
12631264
}
12641265
/** Copy Ident or Select trees */
12651266
def Ref(tree: RefTree)(name: Name)(using Context): RefTree = tree match {

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,11 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
614614
}
615615
def Function(tree: Tree)(args: List[Tree], body: Tree)(using Context): Tree = tree match {
616616
case tree: Function if (args eq tree.args) && (body eq tree.body) => tree
617-
case _ => finalize(tree, untpd.Function(args, body)(tree.source))
617+
case _ =>
618+
val tree1 = tree match
619+
case tree: FunctionWithMods => untpd.FunctionWithMods(args, body, tree.mods, tree.erasedParams)(using tree.source)
620+
case _ => untpd.Function(args, body)(using tree.source)
621+
finalize(tree, tree1)
618622
}
619623
def PolyFunction(tree: Tree)(targs: List[Tree], body: Tree)(using Context): Tree = tree match {
620624
case tree: PolyFunction if (targs eq tree.targs) && (body eq tree.body) => tree

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package transform
33

44
import ast.Trees.*, ast.tpd, core.*
55
import Contexts.*, Types.*, Decorators.*, Symbols.*, DenotTransformers.*
6-
import SymDenotations.*, Scopes.*, StdNames.*, NameOps.*, Names.*
6+
import SymDenotations.*, Scopes.*, StdNames.*, NameOps.*, Names.*, NameKinds.*
77
import MegaPhase.MiniPhase
88

99

@@ -25,7 +25,24 @@ class SpecializeFunctions extends MiniPhase {
2525
/** Create forwarders from the generic applys to the specialized ones.
2626
*/
2727
override def transformDefDef(ddef: DefDef)(using Context) = {
28-
if ddef.name != nme.apply
28+
// Note on special case for inline `apply`s:
29+
// `apply` and `apply$retainedBody` are specialized in this transformation.
30+
// `apply$retainedBody` have the name kind `BodyRetainerName`, these contain
31+
// the runtime implementation of an inline `apply` that implements (or overrides)
32+
// the `FunctionN.apply` method. The inline method is not specialized, it will
33+
// be replaced with the implementation of `apply$retainedBody`. The following code
34+
// inline def apply(x: Int): Double = x.toDouble:Double
35+
// private def apply$retainedBody(x: Int): Double = x.toDouble:Double
36+
// in is transformed into
37+
// inline def apply(x: Int): Double = x.toDouble:Double
38+
// private def apply$retainedBody(x: Int): Double = this.apply$mcDI$sp(x)
39+
// def apply$mcDI$sp(v: Int): Double = x.toDouble:Double
40+
// after erasure it will become
41+
// def apply(v: Int): Double = this.apply$mcDI$sp(v) // from apply$retainedBody
42+
// def apply$mcDI$sp(v: Int): Double = v.toDouble():Double
43+
// def apply(v1: Object): Object = Double.box(this.apply(Int.unbox(v1))) // erasure bridge
44+
45+
if ddef.name.asTermName.exclude(BodyRetainerName) != nme.apply
2946
|| ddef.termParamss.length != 1
3047
|| ddef.termParamss.head.length > 2
3148
|| !ctx.owner.isClass
@@ -44,12 +61,12 @@ class SpecializeFunctions extends MiniPhase {
4461
defn.isSpecializableFunction(cls, paramTypes, retType)
4562
}
4663

47-
if (sym.is(Flags.Deferred) || !isSpecializable) return ddef
64+
if (sym.is(Flags.Deferred) || sym.is(Flags.Inline) || !isSpecializable) return ddef
4865

4966
val specializedApply = newSymbol(
5067
cls,
5168
specName.nn,
52-
sym.flags | Flags.Synthetic,
69+
(sym.flags | Flags.Synthetic) &~ Flags.Private, // Private flag can be set if the name is a BodyRetainerName
5370
sym.info
5471
).entered
5572

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,10 @@ abstract class Lifter {
122122
case TypeApply(fn, targs) =>
123123
cpy.TypeApply(tree)(liftApp(defs, fn), targs)
124124
case Select(pre, name) if isPureRef(tree) =>
125-
cpy.Select(tree)(liftPrefix(defs, pre), name)
125+
val liftedPrefix =
126+
if tree.symbol.is(HasDefaultParams) then liftPrefix(defs, pre)
127+
else liftNonIdempotentPrefix(defs, pre)
128+
cpy.Select(tree)(liftedPrefix, name)
126129
case Block(stats, expr) =>
127130
liftApp(defs ++= stats, expr)
128131
case New(tpt) =>
@@ -138,8 +141,26 @@ abstract class Lifter {
138141
*
139142
* unless `pre` is idempotent.
140143
*/
141-
def liftPrefix(defs: mutable.ListBuffer[Tree], tree: Tree)(using Context): Tree =
144+
def liftNonIdempotentPrefix(defs: mutable.ListBuffer[Tree], tree: Tree)(using Context): Tree =
142145
if (isIdempotentExpr(tree)) tree else lift(defs, tree)
146+
147+
/** Lift prefix `pre` of an application `pre.f(...)` to
148+
*
149+
* val x0 = pre
150+
* x0.f(...)
151+
*
152+
* unless `pre` is idempotent reference, a `this` reference, a literal value, or a or the prefix of an `init` (`New` tree).
153+
*
154+
* Note that default arguments will refer to the prefix, we do not want
155+
* to re-evaluate a complex expression each time we access a getter.
156+
*/
157+
def liftPrefix(defs: mutable.ListBuffer[Tree], tree: Tree)(using Context): Tree =
158+
tree match
159+
case tree: Literal => tree
160+
case tree: This => tree
161+
case tree: New => tree // prefix of <init> call
162+
case tree: RefTree if isIdempotentExpr(tree) => tree
163+
case _ => lift(defs, tree)
143164
}
144165

145166
/** No lifting at all */

compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,6 +1733,58 @@ class DottyBytecodeTests extends DottyBytecodeTest {
17331733
assertSameCode(instructions, expected)
17341734
}
17351735
}
1736+
1737+
@Test def newInPrefixesOfDefaultParam = {
1738+
val source =
1739+
s"""class A:
1740+
| def f(x: Int = 1): Int = x
1741+
|
1742+
|class Test:
1743+
| def meth1() = (new A).f()
1744+
| def meth2() = { val a = new A; a.f() }
1745+
""".stripMargin
1746+
1747+
checkBCode(source) { dir =>
1748+
val clsIn = dir.lookupName("Test.class", directory = false).input
1749+
val clsNode = loadClassNode(clsIn)
1750+
val meth1 = getMethod(clsNode, "meth1")
1751+
val meth2 = getMethod(clsNode, "meth2")
1752+
1753+
val instructions1 = instructionsFromMethod(meth1)
1754+
val instructions2 = instructionsFromMethod(meth2)
1755+
1756+
assert(instructions1 == instructions2,
1757+
"`assert` was not properly inlined in `meth1`\n" +
1758+
diffInstructions(instructions1, instructions2))
1759+
}
1760+
}
1761+
1762+
@Test def newInDependentOfDefaultParam = {
1763+
val source =
1764+
s"""class A:
1765+
| def i: Int = 1
1766+
|
1767+
|class Test:
1768+
| def f(a: A)(x: Int = a.i): Int = x
1769+
| def meth1() = f(new A)()
1770+
| def meth2() = { val a = new A; f(a)() }
1771+
""".stripMargin
1772+
1773+
checkBCode(source) { dir =>
1774+
val clsIn = dir.lookupName("Test.class", directory = false).input
1775+
val clsNode = loadClassNode(clsIn)
1776+
val meth1 = getMethod(clsNode, "meth1")
1777+
val meth2 = getMethod(clsNode, "meth2")
1778+
1779+
val instructions1 = instructionsFromMethod(meth1)
1780+
val instructions2 = instructionsFromMethod(meth2)
1781+
1782+
assert(instructions1 == instructions2,
1783+
"`assert` was not properly inlined in `meth1`\n" +
1784+
diffInstructions(instructions1, instructions2))
1785+
}
1786+
}
1787+
17361788
}
17371789

17381790
object invocationReceiversTestCode {

presentation-compiler/src/main/dotty/tools/pc/AutoImports.scala

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -302,11 +302,24 @@ object AutoImports:
302302
}.headOption
303303
case _ => None
304304

305-
def skipUsingDirectivesOffset =
305+
def firstMemberDefinitionStart(tree: Tree)(using Context): Option[Int] =
306+
tree match
307+
case PackageDef(_, stats) =>
308+
stats.flatMap {
309+
case s: PackageDef => firstMemberDefinitionStart(s)
310+
case stat if stat.span.exists => Some(stat.span.start)
311+
case _ => None
312+
}.headOption
313+
case _ => None
314+
315+
316+
def skipUsingDirectivesOffset(
317+
firstObjectPos: Int = firstMemberDefinitionStart(tree).getOrElse(0)
318+
): Int =
319+
val firstObjectLine = pos.source.offsetToLine(firstObjectPos)
306320
comments
307321
.takeWhile(comment =>
308-
!comment.isDocComment && comment.span.end < firstObjectBody(tree)
309-
.fold(0)(_.span.start)
322+
!comment.isDocComment && pos.source.offsetToLine(comment.span.end) + 1 < firstObjectLine
310323
)
311324
.lastOption
312325
.fold(0)(_.span.end + 1)
@@ -318,7 +331,7 @@ object AutoImports:
318331
val (lineNumber, padTop) = lastImportStatement match
319332
case Some(stm) => (stm.endPos.line + 1, false)
320333
case None if pkg.pid.symbol.isEmptyPackage =>
321-
(pos.source.offsetToLine(skipUsingDirectivesOffset), false)
334+
(pos.source.offsetToLine(skipUsingDirectivesOffset()), false)
322335
case None =>
323336
val pos = pkg.pid.endPos
324337
val line =
@@ -330,7 +343,7 @@ object AutoImports:
330343
new AutoImportPosition(offset, text, padTop)
331344
}
332345

333-
def forScript(isAmmonite: Boolean): Option[AutoImportPosition] =
346+
def forScript(path: String): Option[AutoImportPosition] =
334347
firstObjectBody(tree).map { tmpl =>
335348
val lastImportStatement =
336349
tmpl.body.takeWhile(_.isInstanceOf[Import]).lastOption
@@ -340,10 +353,11 @@ object AutoImports:
340353
offset
341354
case None =>
342355
val scriptOffset =
343-
if isAmmonite then
344-
ScriptFirstImportPosition.ammoniteScStartOffset(text, comments)
345-
else
346-
ScriptFirstImportPosition.scalaCliScStartOffset(text, comments)
356+
if path.isAmmoniteGeneratedFile
357+
then ScriptFirstImportPosition.ammoniteScStartOffset(text, comments)
358+
else if path.isScalaCLIGeneratedFile
359+
then ScriptFirstImportPosition.scalaCliScStartOffset(text, comments)
360+
else Some(skipUsingDirectivesOffset(tmpl.span.start))
347361

348362
scriptOffset.getOrElse {
349363
val tmplPoint = tmpl.self.srcPos.span.point
@@ -359,14 +373,16 @@ object AutoImports:
359373

360374
def fileStart =
361375
AutoImportPosition(
362-
skipUsingDirectivesOffset,
376+
skipUsingDirectivesOffset(),
363377
0,
364378
padTop = false
365379
)
366380

367381
val scriptPos =
368-
if path.isAmmoniteGeneratedFile then forScript(isAmmonite = true)
369-
else if path.isScalaCLIGeneratedFile then forScript(isAmmonite = false)
382+
if path.isAmmoniteGeneratedFile ||
383+
path.isScalaCLIGeneratedFile ||
384+
path.isWorksheet
385+
then forScript(path)
370386
else None
371387

372388
scriptPos

presentation-compiler/test/dotty/tools/pc/base/BaseAutoImportsSuite.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ trait BaseAutoImportsSuite extends BaseCodeActionSuite:
4141
selection
4242
)
4343

44+
def checkWorksheetEdit(
45+
original: String,
46+
expected: String,
47+
selection: Int = 0
48+
): Unit =
49+
checkEditSelection(
50+
"example.worksheet.sc",
51+
original,
52+
expected,
53+
selection
54+
)
55+
4456
def checkEditSelection(
4557
filename: String,
4658
original: String,

presentation-compiler/test/dotty/tools/pc/tests/edit/AutoImportsSuite.scala

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,69 @@ class AutoImportsSuite extends BaseAutoImportsSuite:
342342
)
343343
)
344344

345+
@Test def `worksheet-import` =
346+
checkWorksheetEdit(
347+
worksheetPcWrapper(
348+
"""|//> using scala 3.3.0
349+
|
350+
|// Some comment
351+
|
352+
|// Object comment
353+
|object A {
354+
| val p: <<Path>> = ???
355+
|}
356+
|""".stripMargin
357+
),
358+
worksheetPcWrapper(
359+
"""|//> using scala 3.3.0
360+
|
361+
|// Some comment
362+
|import java.nio.file.Path
363+
|
364+
|// Object comment
365+
|object A {
366+
| val p: Path = ???
367+
|}
368+
|""".stripMargin
369+
)
370+
)
371+
372+
@Test def `object-import` =
373+
checkEdit(
374+
"""|object A {
375+
| //some comment
376+
| val p: <<Path>> = ???
377+
|}
378+
|""".stripMargin,
379+
"""|import java.nio.file.Path
380+
|object A {
381+
| //some comment
382+
| val p: Path = ???
383+
|}
384+
|""".stripMargin,
385+
)
386+
387+
@Test def `toplevels-import` =
388+
checkEdit(
389+
"""|//some comment
390+
|
391+
|val p: <<Path>> = ???
392+
|
393+
|//some other comment
394+
|
395+
|val v = 1
396+
|""".stripMargin,
397+
"""|//some comment
398+
|import java.nio.file.Path
399+
|
400+
|val p: Path = ???
401+
|
402+
|//some other comment
403+
|
404+
|val v = 1
405+
|""".stripMargin,
406+
)
407+
345408
private def ammoniteWrapper(code: String): String =
346409
// Vaguely looks like a scala file that Ammonite generates
347410
// from a sc file.
@@ -359,6 +422,11 @@ class AutoImportsSuite extends BaseAutoImportsSuite:
359422
|}
360423
|""".stripMargin
361424

425+
private def worksheetPcWrapper(code: String): String =
426+
s"""|object worksheet{
427+
|$code
428+
|}""".stripMargin
429+
362430
// https://dotty.epfl.ch/docs/internals/syntax.html#soft-keywords
363431
@Test
364432
def `soft-keyword-check-test` =
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import language.experimental.captureChecking
2+
import annotation.capability
3+
import caps.cap
4+
5+
trait Ptr[A]
6+
@capability trait Scope:
7+
def allocate(size: Int): Ptr[Unit]^{this}
8+
9+
10+
object Scope:
11+
def confined[A](fn: Scope ?->{cap} A): A =
12+
val scope = new Scope:
13+
def allocate(size: Int) = new Ptr[Unit] {}
14+
fn(using scope)
15+
16+
def Test: Unit =
17+
val s = Scope.confined:
18+
val s2 = summon[Scope]
19+
Scope.confined:
20+
s2.allocate(5)
21+
5
22+

0 commit comments

Comments
 (0)