Skip to content

Commit

Permalink
Merge branch 'master' into fix/19806-wrong-tasty-of-scala-mod-cls-ref
Browse files Browse the repository at this point in the history
  • Loading branch information
i10416 committed Mar 1, 2024
2 parents 2708c8c + 2c2e8af commit a4a379f
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 27 deletions.
5 changes: 1 addition & 4 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1967,10 +1967,7 @@ object desugar {
val applyVParams = args.zipWithIndex.map {
case (p, n) => makeSyntheticParameter(n + 1, p)
}
tree match
case tree: FunctionWithMods =>
untpd.FunctionWithMods(applyVParams, result, tree.mods, tree.erasedParams)
case _ => untpd.Function(applyVParams, result)
cpy.Function(tree)(applyVParams, result).asInstanceOf[untpd.Function]
}
}

Expand Down
9 changes: 5 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1255,11 +1255,12 @@ object Trees {
case _ => finalize(tree, untpd.Ident(name)(sourceFile(tree)))
}
def Select(tree: Tree)(qualifier: Tree, name: Name)(using Context): Select = tree match {
case tree: SelectWithSig =>
if ((qualifier eq tree.qualifier) && (name == tree.name)) tree
else finalize(tree, SelectWithSig(qualifier, name, tree.sig)(sourceFile(tree)))
case tree: Select if (qualifier eq tree.qualifier) && (name == tree.name) => tree
case _ => finalize(tree, untpd.Select(qualifier, name)(sourceFile(tree)))
case _ =>
val tree1 = tree match
case tree: SelectWithSig => untpd.SelectWithSig(qualifier, name, tree.sig)(using sourceFile(tree))
case _ => untpd.Select(qualifier, name)(using sourceFile(tree))
finalize(tree, tree1)
}
/** Copy Ident or Select trees */
def Ref(tree: RefTree)(name: Name)(using Context): RefTree = tree match {
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,11 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
}
def Function(tree: Tree)(args: List[Tree], body: Tree)(using Context): Tree = tree match {
case tree: Function if (args eq tree.args) && (body eq tree.body) => tree
case _ => finalize(tree, untpd.Function(args, body)(tree.source))
case _ =>
val tree1 = tree match
case tree: FunctionWithMods => untpd.FunctionWithMods(args, body, tree.mods, tree.erasedParams)(using tree.source)
case _ => untpd.Function(args, body)(using tree.source)
finalize(tree, tree1)
}
def PolyFunction(tree: Tree)(targs: List[Tree], body: Tree)(using Context): Tree = tree match {
case tree: PolyFunction if (targs eq tree.targs) && (body eq tree.body) => tree
Expand Down
25 changes: 21 additions & 4 deletions compiler/src/dotty/tools/dotc/transform/SpecializeFunctions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package transform

import ast.Trees.*, ast.tpd, core.*
import Contexts.*, Types.*, Decorators.*, Symbols.*, DenotTransformers.*
import SymDenotations.*, Scopes.*, StdNames.*, NameOps.*, Names.*
import SymDenotations.*, Scopes.*, StdNames.*, NameOps.*, Names.*, NameKinds.*
import MegaPhase.MiniPhase


Expand All @@ -25,7 +25,24 @@ class SpecializeFunctions extends MiniPhase {
/** Create forwarders from the generic applys to the specialized ones.
*/
override def transformDefDef(ddef: DefDef)(using Context) = {
if ddef.name != nme.apply
// Note on special case for inline `apply`s:
// `apply` and `apply$retainedBody` are specialized in this transformation.
// `apply$retainedBody` have the name kind `BodyRetainerName`, these contain
// the runtime implementation of an inline `apply` that implements (or overrides)
// the `FunctionN.apply` method. The inline method is not specialized, it will
// be replaced with the implementation of `apply$retainedBody`. The following code
// inline def apply(x: Int): Double = x.toDouble:Double
// private def apply$retainedBody(x: Int): Double = x.toDouble:Double
// in is transformed into
// inline def apply(x: Int): Double = x.toDouble:Double
// private def apply$retainedBody(x: Int): Double = this.apply$mcDI$sp(x)
// def apply$mcDI$sp(v: Int): Double = x.toDouble:Double
// after erasure it will become
// def apply(v: Int): Double = this.apply$mcDI$sp(v) // from apply$retainedBody
// def apply$mcDI$sp(v: Int): Double = v.toDouble():Double
// def apply(v1: Object): Object = Double.box(this.apply(Int.unbox(v1))) // erasure bridge

if ddef.name.asTermName.exclude(BodyRetainerName) != nme.apply
|| ddef.termParamss.length != 1
|| ddef.termParamss.head.length > 2
|| !ctx.owner.isClass
Expand All @@ -44,12 +61,12 @@ class SpecializeFunctions extends MiniPhase {
defn.isSpecializableFunction(cls, paramTypes, retType)
}

if (sym.is(Flags.Deferred) || !isSpecializable) return ddef
if (sym.is(Flags.Deferred) || sym.is(Flags.Inline) || !isSpecializable) return ddef

val specializedApply = newSymbol(
cls,
specName.nn,
sym.flags | Flags.Synthetic,
(sym.flags | Flags.Synthetic) &~ Flags.Private, // Private flag can be set if the name is a BodyRetainerName
sym.info
).entered

Expand Down
25 changes: 23 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ abstract class Lifter {
case TypeApply(fn, targs) =>
cpy.TypeApply(tree)(liftApp(defs, fn), targs)
case Select(pre, name) if isPureRef(tree) =>
cpy.Select(tree)(liftPrefix(defs, pre), name)
val liftedPrefix =
if tree.symbol.is(HasDefaultParams) then liftPrefix(defs, pre)
else liftNonIdempotentPrefix(defs, pre)
cpy.Select(tree)(liftedPrefix, name)
case Block(stats, expr) =>
liftApp(defs ++= stats, expr)
case New(tpt) =>
Expand All @@ -138,8 +141,26 @@ abstract class Lifter {
*
* unless `pre` is idempotent.
*/
def liftPrefix(defs: mutable.ListBuffer[Tree], tree: Tree)(using Context): Tree =
def liftNonIdempotentPrefix(defs: mutable.ListBuffer[Tree], tree: Tree)(using Context): Tree =
if (isIdempotentExpr(tree)) tree else lift(defs, tree)

/** Lift prefix `pre` of an application `pre.f(...)` to
*
* val x0 = pre
* x0.f(...)
*
* unless `pre` is idempotent reference, a `this` reference, a literal value, or a or the prefix of an `init` (`New` tree).
*
* Note that default arguments will refer to the prefix, we do not want
* to re-evaluate a complex expression each time we access a getter.
*/
def liftPrefix(defs: mutable.ListBuffer[Tree], tree: Tree)(using Context): Tree =
tree match
case tree: Literal => tree
case tree: This => tree
case tree: New => tree // prefix of <init> call
case tree: RefTree if isIdempotentExpr(tree) => tree
case _ => lift(defs, tree)
}

/** No lifting at all */
Expand Down
52 changes: 52 additions & 0 deletions compiler/test/dotty/tools/backend/jvm/DottyBytecodeTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1733,6 +1733,58 @@ class DottyBytecodeTests extends DottyBytecodeTest {
assertSameCode(instructions, expected)
}
}

@Test def newInPrefixesOfDefaultParam = {
val source =
s"""class A:
| def f(x: Int = 1): Int = x
|
|class Test:
| def meth1() = (new A).f()
| def meth2() = { val a = new A; a.f() }
""".stripMargin

checkBCode(source) { dir =>
val clsIn = dir.lookupName("Test.class", directory = false).input
val clsNode = loadClassNode(clsIn)
val meth1 = getMethod(clsNode, "meth1")
val meth2 = getMethod(clsNode, "meth2")

val instructions1 = instructionsFromMethod(meth1)
val instructions2 = instructionsFromMethod(meth2)

assert(instructions1 == instructions2,
"`assert` was not properly inlined in `meth1`\n" +
diffInstructions(instructions1, instructions2))
}
}

@Test def newInDependentOfDefaultParam = {
val source =
s"""class A:
| def i: Int = 1
|
|class Test:
| def f(a: A)(x: Int = a.i): Int = x
| def meth1() = f(new A)()
| def meth2() = { val a = new A; f(a)() }
""".stripMargin

checkBCode(source) { dir =>
val clsIn = dir.lookupName("Test.class", directory = false).input
val clsNode = loadClassNode(clsIn)
val meth1 = getMethod(clsNode, "meth1")
val meth2 = getMethod(clsNode, "meth2")

val instructions1 = instructionsFromMethod(meth1)
val instructions2 = instructionsFromMethod(meth2)

assert(instructions1 == instructions2,
"`assert` was not properly inlined in `meth1`\n" +
diffInstructions(instructions1, instructions2))
}
}

}

object invocationReceiversTestCode {
Expand Down
40 changes: 28 additions & 12 deletions presentation-compiler/src/main/dotty/tools/pc/AutoImports.scala
Original file line number Diff line number Diff line change
Expand Up @@ -302,11 +302,24 @@ object AutoImports:
}.headOption
case _ => None

def skipUsingDirectivesOffset =
def firstMemberDefinitionStart(tree: Tree)(using Context): Option[Int] =
tree match
case PackageDef(_, stats) =>
stats.flatMap {
case s: PackageDef => firstMemberDefinitionStart(s)
case stat if stat.span.exists => Some(stat.span.start)
case _ => None
}.headOption
case _ => None


def skipUsingDirectivesOffset(
firstObjectPos: Int = firstMemberDefinitionStart(tree).getOrElse(0)
): Int =
val firstObjectLine = pos.source.offsetToLine(firstObjectPos)
comments
.takeWhile(comment =>
!comment.isDocComment && comment.span.end < firstObjectBody(tree)
.fold(0)(_.span.start)
!comment.isDocComment && pos.source.offsetToLine(comment.span.end) + 1 < firstObjectLine
)
.lastOption
.fold(0)(_.span.end + 1)
Expand All @@ -318,7 +331,7 @@ object AutoImports:
val (lineNumber, padTop) = lastImportStatement match
case Some(stm) => (stm.endPos.line + 1, false)
case None if pkg.pid.symbol.isEmptyPackage =>
(pos.source.offsetToLine(skipUsingDirectivesOffset), false)
(pos.source.offsetToLine(skipUsingDirectivesOffset()), false)
case None =>
val pos = pkg.pid.endPos
val line =
Expand All @@ -330,7 +343,7 @@ object AutoImports:
new AutoImportPosition(offset, text, padTop)
}

def forScript(isAmmonite: Boolean): Option[AutoImportPosition] =
def forScript(path: String): Option[AutoImportPosition] =
firstObjectBody(tree).map { tmpl =>
val lastImportStatement =
tmpl.body.takeWhile(_.isInstanceOf[Import]).lastOption
Expand All @@ -340,10 +353,11 @@ object AutoImports:
offset
case None =>
val scriptOffset =
if isAmmonite then
ScriptFirstImportPosition.ammoniteScStartOffset(text, comments)
else
ScriptFirstImportPosition.scalaCliScStartOffset(text, comments)
if path.isAmmoniteGeneratedFile
then ScriptFirstImportPosition.ammoniteScStartOffset(text, comments)
else if path.isScalaCLIGeneratedFile
then ScriptFirstImportPosition.scalaCliScStartOffset(text, comments)
else Some(skipUsingDirectivesOffset(tmpl.span.start))

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

def fileStart =
AutoImportPosition(
skipUsingDirectivesOffset,
skipUsingDirectivesOffset(),
0,
padTop = false
)

val scriptPos =
if path.isAmmoniteGeneratedFile then forScript(isAmmonite = true)
else if path.isScalaCLIGeneratedFile then forScript(isAmmonite = false)
if path.isAmmoniteGeneratedFile ||
path.isScalaCLIGeneratedFile ||
path.isWorksheet
then forScript(path)
else None

scriptPos
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,18 @@ trait BaseAutoImportsSuite extends BaseCodeActionSuite:
selection
)

def checkWorksheetEdit(
original: String,
expected: String,
selection: Int = 0
): Unit =
checkEditSelection(
"example.worksheet.sc",
original,
expected,
selection
)

def checkEditSelection(
filename: String,
original: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,69 @@ class AutoImportsSuite extends BaseAutoImportsSuite:
)
)

@Test def `worksheet-import` =
checkWorksheetEdit(
worksheetPcWrapper(
"""|//> using scala 3.3.0
|
|// Some comment
|
|// Object comment
|object A {
| val p: <<Path>> = ???
|}
|""".stripMargin
),
worksheetPcWrapper(
"""|//> using scala 3.3.0
|
|// Some comment
|import java.nio.file.Path
|
|// Object comment
|object A {
| val p: Path = ???
|}
|""".stripMargin
)
)

@Test def `object-import` =
checkEdit(
"""|object A {
| //some comment
| val p: <<Path>> = ???
|}
|""".stripMargin,
"""|import java.nio.file.Path
|object A {
| //some comment
| val p: Path = ???
|}
|""".stripMargin,
)

@Test def `toplevels-import` =
checkEdit(
"""|//some comment
|
|val p: <<Path>> = ???
|
|//some other comment
|
|val v = 1
|""".stripMargin,
"""|//some comment
|import java.nio.file.Path
|
|val p: Path = ???
|
|//some other comment
|
|val v = 1
|""".stripMargin,
)

private def ammoniteWrapper(code: String): String =
// Vaguely looks like a scala file that Ammonite generates
// from a sc file.
Expand All @@ -359,6 +422,11 @@ class AutoImportsSuite extends BaseAutoImportsSuite:
|}
|""".stripMargin

private def worksheetPcWrapper(code: String): String =
s"""|object worksheet{
|$code
|}""".stripMargin

// https://dotty.epfl.ch/docs/internals/syntax.html#soft-keywords
@Test
def `soft-keyword-check-test` =
Expand Down
22 changes: 22 additions & 0 deletions tests/pos-custom-args/captures/i19751.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import language.experimental.captureChecking
import annotation.capability
import caps.cap

trait Ptr[A]
@capability trait Scope:
def allocate(size: Int): Ptr[Unit]^{this}


object Scope:
def confined[A](fn: Scope ?->{cap} A): A =
val scope = new Scope:
def allocate(size: Int) = new Ptr[Unit] {}
fn(using scope)

def Test: Unit =
val s = Scope.confined:
val s2 = summon[Scope]
Scope.confined:
s2.allocate(5)
5

Loading

0 comments on commit a4a379f

Please sign in to comment.