Skip to content
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

Implement SIP-61 @unroll annotation #21693

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
165c0ab
SIP 61 - copy phase and annotation from com-lihaoyi/unroll
bishabosha Sep 30, 2024
63119f7
SIP 61 - add compilation test, fix missing symbols
bishabosha Sep 30, 2024
9749774
SIP 61 - add sbt scripted test template
bishabosha Sep 30, 2024
75a9275
SIP 61 - substitute types in the forwarder def, enable genericMethod …
bishabosha Oct 1, 2024
a93c3f0
SIP 61 - add abstractMethod tests, unlink replaced definitions
bishabosha Oct 1, 2024
e2c298e
SIP 61 - detect duplicates
bishabosha Oct 1, 2024
1633c9c
SIP 61 - check for default parameter
bishabosha Oct 1, 2024
5f3324b
SIP 61 - fix invalid pattern in generateFromProduct
bishabosha Oct 1, 2024
17586b8
SIP 61 - fork when running sbt-scripted apps with unmanaged classpaths
bishabosha Oct 1, 2024
ed92080
SIP 61 - move before pickling
bishabosha Oct 1, 2024
b8871b7
SIP-61 - fixed unpickling errors - invisible select and incorrect spans
bishabosha Oct 2, 2024
766c4dd
SIP 61 - update stdlibExperimentalDefinitions.scala
bishabosha Oct 2, 2024
a85d2f5
delete debug line
bishabosha Oct 2, 2024
19dfbde
skip reflection test on scala.js
bishabosha Oct 2, 2024
a464a2b
Review: require final, remove special treatment of abstract methods
bishabosha Oct 3, 2024
ea080c5
include constructor in unroll clash, loosen final check for objects
bishabosha Oct 3, 2024
44b1b6f
fix order of printing in test
bishabosha Oct 3, 2024
606dbf2
check for illegal uses of @unroll
bishabosha Oct 3, 2024
dbac4ff
error when unrolling trait constructors
bishabosha Oct 3, 2024
ce4f9b4
better error when multiple parameter lists with unroll
bishabosha Oct 3, 2024
b3cd729
SIP 61 - add documentation, and regression test for local defs
bishabosha Oct 5, 2024
d14448b
SIP 61 - remove tasty hack by not "telescoping" forwarders
bishabosha Oct 5, 2024
5eea799
SIP 61 - add errors when unrolling apply, copy and fromProduct
bishabosha Oct 5, 2024
983a05a
add failing test when inline method calls forwarder
bishabosha Oct 7, 2024
ae83550
Experiment: add new SourceInvisble TASTy flag
bishabosha Oct 7, 2024
d64dc22
fix whitespace
bishabosha Oct 7, 2024
168866d
add failing test: demo transparent inline can not see forwarders at t…
bishabosha Oct 8, 2024
e3a3cb1
change test to establish cyclic case, and solution
bishabosha Oct 8, 2024
35bbd61
detect changed selectIn: error when same file, or suspend otherwise
bishabosha Oct 8, 2024
4ddadad
Merge SourceInvisible and Invisible flags
bishabosha Oct 10, 2024
7b1970c
address review: part 1
bishabosha Nov 13, 2024
c5d05d7
address review: part 2
bishabosha Nov 13, 2024
78fbf04
address review: part 3
bishabosha Nov 14, 2024
f337507
test clause interleaving and value class
bishabosha Nov 15, 2024
ddb9c5a
mechanically copy all sbt-test for unroll
bishabosha Nov 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/CompilationUnit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ class CompilationUnit protected (val source: SourceFile, val info: CompilationUn

var hasMacroAnnotations: Boolean = false

def hasUnrollDefs: Boolean = unrolledClasses.nonEmpty
var unrolledClasses: Set[Symbol] = Set.empty

/** Set to `true` if inliner added anonymous mirrors that need to be completed */
var needsMirrorSupport: Boolean = false

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class Compiler {
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
List(new semanticdb.ExtractSemanticDB.ExtractSemanticInfo) :: // Extract info into .semanticdb files
List(new PostTyper) :: // Additional checks and cleanups after type checking
List(new UnrollDefinitions) :: // Unroll annotated methods if detected in PostTyper
List(new sjs.PrepJSInterop) :: // Additional checks and transformations for Scala.js (Scala.js only)
List(new SetRootTree) :: // Set the `rootTreeOrProvider` on class symbols
Nil
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,7 @@ class Definitions {
@tu lazy val MigrationAnnot: ClassSymbol = requiredClass("scala.annotation.migration")
@tu lazy val NowarnAnnot: ClassSymbol = requiredClass("scala.annotation.nowarn")
@tu lazy val UnusedAnnot: ClassSymbol = requiredClass("scala.annotation.unused")
@tu lazy val UnrollAnnot: ClassSymbol = requiredClass("scala.annotation.unroll")
@tu lazy val TransparentTraitAnnot: ClassSymbol = requiredClass("scala.annotation.transparentTrait")
@tu lazy val NativeAnnot: ClassSymbol = requiredClass("scala.native")
@tu lazy val RepeatedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Repeated")
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Denotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,9 @@ object Denotations {
def filterDisjoint(denots: PreDenotation)(using Context): SingleDenotation =
if (denots.exists && denots.matches(this)) NoDenotation else this
def filterWithFlags(required: FlagSet, excluded: FlagSet)(using Context): SingleDenotation =
val realExcluded = if ctx.isAfterTyper then excluded else excluded | Invisible
val realExcluded =
if ctx.isAfterTyper || ctx.mode.is(Mode.ResolveFromTASTy) then excluded
else excluded | Invisible
def symd: SymDenotation = this match
case symd: SymDenotation => symd
case _ => symbol.denot
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Mode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ object Mode {

/** Use previous Scheme for implicit resolution. Currently significant
* in 3.0-migration where we use Scala-2's scheme instead and in 3.5 and 3.6-migration
* where we use the previous scheme up to 3.4 for comparison with the new scheme.
* where we use the previous scheme up to 3.4 for comparison with the new scheme.
*/
val OldImplicitResolution: Mode = newMode(15, "OldImplicitResolution")

Expand All @@ -125,6 +125,9 @@ object Mode {
/** Read original positions when unpickling from TASTY */
val ReadPositions: Mode = newMode(17, "ReadPositions")

/** We are resolving a SELECT name from TASTy */
val ResolveFromTASTy: Mode = newMode(18, "ResolveFromTASTy")

/** We are elaborating the fully qualified name of a package clause.
* In this case, identifiers should never be imported.
*/
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ object SymDenotations {
case _ =>
// Otherwise, no completion is necessary, see the preconditions of `markAbsent()`.
(myInfo `eq` NoType)
|| is(Invisible) && ctx.isTyper
|| (is(Invisible) && !ctx.mode.is(Mode.ResolveFromTASTy)) && ctx.isTyper
|| is(ModuleVal, butNot = Package) && moduleClass.isAbsent(canForce)
}

Expand Down
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1562,7 +1562,7 @@ class TreeUnpickler(reader: TastyReader,
* - sbt-test/tasty-compat/remove-override
* - sbt-test/tasty-compat/move-method
*/
def lookupInSuper =
def lookupInSuper(using Context) =
val cls = ownerTpe.classSymbol
if cls.exists then
cls.asClass.classDenot
Expand All @@ -1571,14 +1571,18 @@ class TreeUnpickler(reader: TastyReader,
else
NoDenotation

val denot =

def searchDenot(using Context): Denotation =
if owner.is(JavaAnnotation) && name == nme.CONSTRUCTOR then
// #19951 Fix up to read TASTy produced before 3.5.0 -- ignore the signature
ownerTpe.nonPrivateDecl(name).asSeenFrom(prefix)
else
val d = ownerTpe.decl(name).atSignature(sig, target)
(if !d.exists then lookupInSuper else d).asSeenFrom(prefix)

val denot = inContext(ctx.addMode(Mode.ResolveFromTASTy)):
searchDenot // able to resolve Invisible members

makeSelect(qual, name, denot)
case REPEATED =>
val elemtpt = readTpt()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
case DeprecatedInfixNamedArgumentSyntaxID // errorNumber: 204
case GivenSearchPriorityID // errorNumber: 205
case EnumMayNotBeValueClassesID // errorNumber: 206
case IllegalUnrollPlacementID // errorNumber: 207

def errorNumber = ordinal - 1

Expand Down
32 changes: 27 additions & 5 deletions compiler/src/dotty/tools/dotc/reporting/messages.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3331,14 +3331,14 @@ final class QuotedTypeMissing(tpe: Type)(using Context) extends StagingMessage(Q

private def witness = defn.QuotedTypeClass.typeRef.appliedTo(tpe)

override protected def msg(using Context): String =
override protected def msg(using Context): String =
i"Reference to $tpe within quotes requires a given ${witness} in scope"

override protected def explain(using Context): String =
i"""Referencing `$tpe` inside a quoted expression requires a `${witness}` to be in scope.
i"""Referencing `$tpe` inside a quoted expression requires a `${witness}` to be in scope.
|Since Scala is subject to erasure at runtime, the type information will be missing during the execution of the code.
|`${witness}` is therefore needed to carry `$tpe`'s type information into the quoted code.
|Without an implicit `${witness}`, the type `$tpe` cannot be properly referenced within the expression.
|`${witness}` is therefore needed to carry `$tpe`'s type information into the quoted code.
|Without an implicit `${witness}`, the type `$tpe` cannot be properly referenced within the expression.
|To resolve this, ensure that a `${witness}` is available, either through a context-bound or explicitly.
|"""

Expand All @@ -3350,7 +3350,6 @@ final class DeprecatedAssignmentSyntax(key: Name, value: untpd.Tree)(using Conte
|not as an assignment.
|
|To assign a value, use curly braces: `{${key} = ${value}}`."""
+ Message.rewriteNotice("This", version = SourceVersion.`3.6-migration`)

override protected def explain(using Context): String = ""

Expand Down Expand Up @@ -3405,3 +3404,26 @@ final class EnumMayNotBeValueClasses(sym: Symbol)(using Context) extends SyntaxM

def explain(using Context) = ""
end EnumMayNotBeValueClasses

class IllegalUnrollPlacement(origin: Option[Symbol])(using Context)
extends DeclarationMsg(IllegalUnrollPlacementID):
def msg(using Context) = origin match
case None => "@unroll is only allowed on a method parameter"
case Some(method) =>
val isCtor = method.isConstructor
def what = if isCtor then i"a ${if method.owner.is(Trait) then "trait" else "class"} constructor" else i"method ${method.name}"
val prefix = s"Cannot unroll parameters of $what"
if method.is(Deferred) then
i"$prefix: it must not be abstract"
else if isCtor && method.owner.is(Trait) then
i"implementation restriction: $prefix"
else if !(isCtor || method.is(Final) || method.owner.is(ModuleClass)) then
i"$prefix: it is not final"
else if method.owner.companionClass.is(CaseClass) then
i"$prefix of a case class companion object: please annotate the class constructor instead"
else
assert(method.owner.is(CaseClass))
i"$prefix of a case class: please annotate the class constructor instead"

def explain(using Context) = ""
end IllegalUnrollPlacement
31 changes: 31 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import reporting.*
import NameKinds.WildcardParamName
import cc.*
import dotty.tools.dotc.transform.MacroAnnotations.hasMacroAnnotation
import dotty.tools.dotc.core.NameKinds.DefaultGetterName

object PostTyper {
val name: String = "posttyper"
Expand Down Expand Up @@ -119,8 +120,31 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>

private var inJavaAnnot: Boolean = false

private val seenUnrolledMethods: util.EqHashMap[Symbol, Boolean] = new util.EqHashMap[Symbol, Boolean]

private var noCheckNews: Set[New] = Set()

def isValidUnrolledMethod(method: Symbol, origin: SrcPos)(using Context): Boolean =
seenUnrolledMethods.getOrElseUpdate(method, {
val isCtor = method.isConstructor
if
method.name.is(DefaultGetterName)
then
false // not an error, but not an expandable unrolled method
else if
method.is(Deferred)
|| isCtor && method.owner.is(Trait)
|| !(isCtor || method.is(Final) || method.owner.is(ModuleClass))
|| method.owner.companionClass.is(CaseClass)
&& (method.name == nme.apply || method.name == nme.fromProduct)
|| method.owner.is(CaseClass) && method.name == nme.copy
then
report.error(IllegalUnrollPlacement(Some(method)), origin)
false
else
true
})

def withNoCheckNews[T](ts: List[New])(op: => T): T = {
val saved = noCheckNews
noCheckNews ++= ts
Expand Down Expand Up @@ -199,6 +223,12 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
tree
}

private def registerIfUnrolledParam(sym: Symbol)(using Context): Unit =
if sym.hasAnnotation(defn.UnrollAnnot) && isValidUnrolledMethod(sym.owner, sym.sourcePos) then
val cls = sym.enclosingClass
val additions = Array(cls, cls.linkedClass).filter(_ != NoSymbol)
ctx.compilationUnit.unrolledClasses ++= additions

private def processValOrDefDef(tree: Tree)(using Context): tree.type =
val sym = tree.symbol
tree match
Expand All @@ -215,6 +245,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
++ sym.annotations)
else
if sym.is(Param) then
registerIfUnrolledParam(sym)
sym.keepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots)
else if sym.is(ParamAccessor) then
// @publicInBinary is not a meta-annotation and therefore not kept by `keepAnnotationsCarrying`
Expand Down
Loading
Loading