Skip to content

Commit 3f2925f

Browse files
committed
SIP 61 - Improve checks and type substitution for forwarders
- add documentation page - move before pickling, fix errors due to unpickling select of Invisible definitions and incorrect spans - forwarders now only call the original method. - detect in posttyper which compilation units have unrolled definitions - detect clashes with forwarders and existing definitions - check for illegal uses of @unroll - implementation restriction: no unroll for trait parameters - unlink replaced definitions - check for default parameter - fix invalid pattern in generateFromProduct - sbt-test/scripted: fork when running with unmanaged classpaths - update stdlibExperimentalDefinitions.scala - skip reflection test on scala.js - require final methods, remove special treatment of abstract methods - fix order of printing in test - better error when multiple parameter lists with unroll - Move sbt-scripted tests to vulpix suites
1 parent 233fd90 commit 3f2925f

File tree

177 files changed

+1403
-1121
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

177 files changed

+1403
-1121
lines changed

compiler/src/dotty/tools/dotc/CompilationUnit.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ class CompilationUnit protected (val source: SourceFile, val info: CompilationUn
5959

6060
var hasMacroAnnotations: Boolean = false
6161

62+
def hasUnrollDefs: Boolean = unrolledClasses != null
63+
var unrolledClasses: Set[Symbol] | Null = null
64+
6265
/** Set to `true` if inliner added anonymous mirrors that need to be completed */
6366
var needsMirrorSupport: Boolean = false
6467

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class Compiler {
4040
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
4141
List(new semanticdb.ExtractSemanticDB.ExtractSemanticInfo) :: // Extract info into .semanticdb files
4242
List(new PostTyper) :: // Additional checks and cleanups after type checking
43+
List(new UnrollDefinitions) :: // Unroll annotated methods if detected in PostTyper
4344
List(new sjs.PrepJSInterop) :: // Additional checks and transformations for Scala.js (Scala.js only)
4445
List(new SetRootTree) :: // Set the `rootTreeOrProvider` on class symbols
4546
Nil
@@ -61,7 +62,6 @@ class Compiler {
6162
List(new InstrumentCoverage) :: // Perform instrumentation for code coverage (if -coverage-out is set)
6263
List(new CrossVersionChecks, // Check issues related to deprecated and experimental
6364
new FirstTransform, // Some transformations to put trees into a canonical form
64-
new UnrollDefs, // Unroll annotated methods
6565
new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars
6666
new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes
6767
new CookComments, // Cook the comments: expand variables, doc, etc.

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import collection.mutable
2020
import reporting.{Profile, NoProfile}
2121
import dotty.tools.tasty.TastyFormat.ASTsSection
2222
import quoted.QuotePatterns
23+
import dotty.tools.dotc.config.Feature
2324

2425
object TreePickler:
2526
class StackSizeExceeded(val mdef: tpd.MemberDef) extends Exception
@@ -474,15 +475,16 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) {
474475
case _ =>
475476
if passesConditionForErroringBestEffortCode(tree.hasType) then
476477
// #19951 The signature of a constructor of a Java annotation is irrelevant
478+
val sym = tree.symbol
477479
val sig =
478-
if name == nme.CONSTRUCTOR && tree.symbol.exists && tree.symbol.owner.is(JavaAnnotation) then Signature.NotAMethod
480+
if name == nme.CONSTRUCTOR && sym.exists && sym.owner.is(JavaAnnotation) then Signature.NotAMethod
479481
else tree.tpe.signature
480-
var ename = tree.symbol.targetName
482+
var ename = sym.targetName
481483
val selectFromQualifier =
482484
name.isTypeName
483485
|| qual.isInstanceOf[Hole] // holes have no symbol
484486
|| sig == Signature.NotAMethod // no overload resolution necessary
485-
|| !tree.denot.symbol.exists // polymorphic function type
487+
|| !sym.exists // polymorphic function type
486488
|| tree.denot.asSingleDenotation.isRefinedMethod // refined methods have no defining class symbol
487489
if selectFromQualifier then
488490
writeByte(if name.isTypeName then SELECTtpt else SELECT)
@@ -491,9 +493,9 @@ class TreePickler(pickler: TastyPickler, attributes: Attributes) {
491493
else // select from owner
492494
writeByte(SELECTin)
493495
withLength {
494-
pickleNameAndSig(name, tree.symbol.signature, ename)
496+
pickleNameAndSig(name, sym.signature, ename)
495497
pickleTree(qual)
496-
pickleType(tree.symbol.owner.typeRef)
498+
pickleType(sym.owner.typeRef)
497499
}
498500
else
499501
writeByte(if name.isTypeName then SELECTtpt else SELECT)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ enum ErrorMessageID(val isActive: Boolean = true) extends java.lang.Enum[ErrorMe
220220
case DeprecatedInfixNamedArgumentSyntaxID // errorNumber: 204
221221
case GivenSearchPriorityID // errorNumber: 205
222222
case EnumMayNotBeValueClassesID // errorNumber: 206
223+
case IllegalUnrollPlacementID // errorNumber: 207
223224

224225
def errorNumber = ordinal - 1
225226

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

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3334,14 +3334,14 @@ final class QuotedTypeMissing(tpe: Type)(using Context) extends StagingMessage(Q
33343334

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

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

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

@@ -3408,3 +3408,26 @@ final class EnumMayNotBeValueClasses(sym: Symbol)(using Context) extends SyntaxM
34083408

34093409
def explain(using Context) = ""
34103410
end EnumMayNotBeValueClasses
3411+
3412+
class IllegalUnrollPlacement(origin: Option[Symbol])(using Context)
3413+
extends DeclarationMsg(IllegalUnrollPlacementID):
3414+
def msg(using Context) = origin match
3415+
case None => "@unroll is only allowed on a method parameter"
3416+
case Some(method) =>
3417+
val isCtor = method.isConstructor
3418+
def what = if isCtor then i"a ${if method.owner.is(Trait) then "trait" else "class"} constructor" else i"method ${method.name}"
3419+
val prefix = s"Can not unroll parameters of $what"
3420+
if method.is(Deferred) then
3421+
i"$prefix: it must not be abstract"
3422+
else if isCtor && method.owner.is(Trait) then
3423+
i"implementation restriction: $prefix"
3424+
else if !(isCtor || method.is(Final) || method.owner.is(ModuleClass)) then
3425+
i"$prefix: it is not final"
3426+
else if method.owner.companionClass.is(CaseClass) then
3427+
i"$prefix of a case class companion object: please annotate the class constructor instead"
3428+
else
3429+
assert(method.owner.is(CaseClass))
3430+
i"$prefix of a case class: please annotate the class constructor instead"
3431+
3432+
def explain(using Context) = ""
3433+
end IllegalUnrollPlacement

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import reporting.*
2121
import NameKinds.WildcardParamName
2222
import cc.*
2323
import dotty.tools.dotc.transform.MacroAnnotations.hasMacroAnnotation
24+
import dotty.tools.dotc.core.NameKinds.DefaultGetterName
2425

2526
object PostTyper {
2627
val name: String = "posttyper"
@@ -119,8 +120,39 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
119120

120121
private var inJavaAnnot: Boolean = false
121122

123+
private var seenUnrolledMethods: util.EqHashMap[Symbol, Boolean] | Null = null
124+
122125
private var noCheckNews: Set[New] = Set()
123126

127+
def isValidUnrolledMethod(method: Symbol, origin: SrcPos)(using Context): Boolean =
128+
val seenMethods =
129+
val local = seenUnrolledMethods
130+
if local == null then
131+
val map = new util.EqHashMap[Symbol, Boolean]
132+
seenUnrolledMethods = map
133+
map
134+
else
135+
local
136+
seenMethods.getOrElseUpdate(method, {
137+
val isCtor = method.isConstructor
138+
if
139+
method.name.is(DefaultGetterName)
140+
then
141+
false // not an error, but not an expandable unrolled method
142+
else if
143+
method.is(Deferred)
144+
|| isCtor && method.owner.is(Trait)
145+
|| !(isCtor || method.is(Final) || method.owner.is(ModuleClass))
146+
|| method.owner.companionClass.is(CaseClass)
147+
&& (method.name == nme.apply || method.name == nme.fromProduct)
148+
|| method.owner.is(CaseClass) && method.name == nme.copy
149+
then
150+
report.error(IllegalUnrollPlacement(Some(method)), origin)
151+
false
152+
else
153+
true
154+
})
155+
124156
def withNoCheckNews[T](ts: List[New])(op: => T): T = {
125157
val saved = noCheckNews
126158
noCheckNews ++= ts
@@ -199,6 +231,16 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
199231
tree
200232
}
201233

234+
private def registerIfUnrolledParam(sym: Symbol)(using Context): Unit =
235+
if sym.hasAnnotation(defn.UnrollAnnot) && isValidUnrolledMethod(sym.owner, sym.sourcePos) then
236+
val cls = sym.enclosingClass
237+
val classes = ctx.compilationUnit.unrolledClasses
238+
val additions = Array(cls, cls.linkedClass).filter(_ != NoSymbol)
239+
if classes == null then
240+
ctx.compilationUnit.unrolledClasses = Set.from(additions)
241+
else
242+
ctx.compilationUnit.unrolledClasses = classes ++ additions
243+
202244
private def processValOrDefDef(tree: Tree)(using Context): tree.type =
203245
val sym = tree.symbol
204246
tree match
@@ -215,6 +257,7 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
215257
++ sym.annotations)
216258
else
217259
if sym.is(Param) then
260+
registerIfUnrolledParam(sym)
218261
sym.keepAnnotationsCarrying(thisPhase, Set(defn.ParamMetaAnnot), orNoneOf = defn.NonBeanMetaAnnots)
219262
else if sym.is(ParamAccessor) then
220263
// @publicInBinary is not a meta-annotation and therefore not kept by `keepAnnotationsCarrying`

0 commit comments

Comments
 (0)