Skip to content

Commit 3f1e20a

Browse files
Merge pull request #9984 from dotty-staging/move-blackbox-macro-exapnsion-after-typer
Expand non-transparent macros after Typer
2 parents 69d363b + bdf2482 commit 3f1e20a

File tree

99 files changed

+700
-177
lines changed

Some content is hidden

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

99 files changed

+700
-177
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,5 +89,9 @@ community-build/sbt-scalajs-sbt
8989
*.check.out
9090
!/dist/bin/
9191

92+
# semanticdb test output files
93+
*.expect.scala.out
94+
*.expect.out
95+
9296
# Bloop
9397
.bsp

bench-run/src/main/scala/dotty/tools/benchmarks/tuples/TupleOps.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class TupleOps {
5050
def tupleMergeSort(tuple: Tuple): Tuple =
5151
if (tuple.size <= 1) tuple
5252
else {
53-
val (tuple1, tuple2) = tuple.splitAt(tuple.size / 2)
53+
val (tuple1, tuple2) = tuple.splitAt(tuple.size / 2): (Tuple, Tuple)// TODO remove ascriptions (issue with type variable constraints)
5454
val (sorted1, sorted2) = (tupleMergeSort(tuple1), tupleMergeSort(tuple2))
5555
tupleMerge(sorted1, sorted2)
5656
}

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

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,26 @@ class CompilationUnit protected (val source: SourceFile) {
3838
*/
3939
val freshNames: FreshNameCreator = new FreshNameCreator.Default
4040

41+
/** Will be set to `true` if there are inline call that must be inlined after typer.
42+
* The information is used in phase `Inlining` in order to avoid traversing trees that need no transformations.
43+
*/
44+
var needsInlining: Boolean = false
45+
4146
/** Will be set to `true` if contains `Quote`.
4247
* The information is used in phase `Staging` in order to avoid traversing trees that need no transformations.
4348
*/
4449
var needsStaging: Boolean = false
4550

51+
/** Will be set to `true` if contains `Quote` that needs to be pickled
52+
* The information is used in phase `PickleQuotes` in order to avoid traversing trees that need no transformations.
53+
*/
54+
var needsQuotePickling: Boolean = false
55+
4656
/** A structure containing a temporary map for generating inline accessors */
4757
val inlineAccessors: InlineAccessors = new InlineAccessors
4858

4959
var suspended: Boolean = false
60+
var suspendedAtInliningPhase: Boolean = false
5061

5162
/** Can this compilation unit be suspended */
5263
def isSuspendable: Boolean = true
@@ -61,6 +72,8 @@ class CompilationUnit protected (val source: SourceFile) {
6172
report.echo(i"suspended: $this")
6273
suspended = true
6374
ctx.run.suspendedUnits += this
75+
if ctx.phase == Phases.inliningPhase then
76+
suspendedAtInliningPhase = true
6477
throw CompilationUnit.SuspendException()
6578

6679
private var myAssignmentSpans: Map[Int, List[Span]] = null
@@ -90,7 +103,9 @@ object CompilationUnit {
90103
if (forceTrees) {
91104
val force = new Force
92105
force.traverse(unit1.tpdTree)
93-
unit1.needsStaging = force.needsStaging
106+
unit1.needsStaging = force.containsQuote
107+
unit1.needsQuotePickling = force.containsQuote
108+
unit1.needsInlining = force.containsInline
94109
}
95110
unit1
96111
}
@@ -116,10 +131,13 @@ object CompilationUnit {
116131

117132
/** Force the tree to be loaded */
118133
private class Force extends TreeTraverser {
119-
var needsStaging = false
134+
var containsQuote = false
135+
var containsInline = false
120136
def traverse(tree: Tree)(using Context): Unit = {
121137
if (tree.symbol.isQuote)
122-
needsStaging = true
138+
containsQuote = true
139+
if tree.symbol.is(Flags.Inline) then
140+
containsInline = true
123141
traverseChildren(tree)
124142
}
125143
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class Compiler {
5050
/** Phases dealing with TASTY tree pickling and unpickling */
5151
protected def picklerPhases: List[List[Phase]] =
5252
List(new Pickler) :: // Generate TASTY info
53+
List(new Inlining) :: // Inline and execute macros
5354
List(new PickleQuotes) :: // Turn quoted trees into explicit run-time data structures
5455
Nil
5556

@@ -61,6 +62,7 @@ class Compiler {
6162
new CookComments, // Cook the comments: expand variables, doc, etc.
6263
new CheckStatic, // Check restrictions that apply to @static members
6364
new BetaReduce, // Reduce closure applications
65+
new InlineVals, // Check right hand-sides of an `inline val`s
6466
new ExpandSAMs, // Expand single abstract method closures to anonymous classes
6567
new init.Checker) :: // Check initialization of objects
6668
List(new ElimRepeated, // Rewrite vararg parameters and arguments

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,27 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
101101
*/
102102
def units: List[CompilationUnit] = myUnits
103103

104-
var suspendedUnits: mutable.ListBuffer[CompilationUnit] = mutable.ListBuffer()
105-
106104
private def units_=(us: List[CompilationUnit]): Unit =
107105
myUnits = us
108106

107+
var suspendedUnits: mutable.ListBuffer[CompilationUnit] = mutable.ListBuffer()
108+
109+
def checkSuspendedUnits(newUnits: List[CompilationUnit])(using Context): Unit =
110+
if newUnits.isEmpty && suspendedUnits.nonEmpty && !ctx.reporter.errorsReported then
111+
val where =
112+
if suspendedUnits.size == 1 then i"in ${suspendedUnits.head}."
113+
else i"""among
114+
|
115+
| ${suspendedUnits.toList}%, %
116+
|"""
117+
val enableXprintSuspensionHint =
118+
if ctx.settings.XprintSuspension.value then ""
119+
else "\n\nCompiling with -Xprint-suspension gives more information."
120+
report.error(em"""Cyclic macro dependencies $where
121+
|Compilation stopped since no further progress can be made.
122+
|
123+
|To fix this, place macros in one set of files and their callers in another.$enableXprintSuspensionHint""")
124+
109125
/** The files currently being compiled (active or suspended).
110126
* This may return different results over time.
111127
* These files do not have to be source files since it's possible to compile

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ class ScalaSettings extends Settings.SettingGroup with CommonScalaSettings {
227227
val Yinstrument: Setting[Boolean] = BooleanSetting("-Yinstrument", "Add instrumentation code that counts allocations and closure creations.")
228228
val YinstrumentDefs: Setting[Boolean] = BooleanSetting("-Yinstrument-defs", "Add instrumentation code that counts method calls; needs -Yinstrument to be set, too.")
229229

230+
val YforceInlineWhileTyping: Setting[Boolean] = BooleanSetting("-Yforce-inline-while-typing", "Make non-transparent inline methods inline when typing. Emulates the old inlining behavior of 3.0.0-M3.")
231+
230232
/** Dottydoc specific settings that are not used in scaladoc */
231233
val docSnapshot: Setting[Boolean] = BooleanSetting("-doc-snapshot", "Generate a documentation snapshot for the current Dotty version")
232234

compiler/src/dotty/tools/dotc/core/Mode.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,9 @@ object Mode {
122122

123123
/** Are we enforcing null safety */
124124
val SafeNulls = newMode(28, "SafeNulls")
125+
126+
/** We are typing the body of the condition of an `inline if` or the scrutinee of an `inline match`
127+
* This mode forces expansion of inline calls in those positions even during typing.
128+
*/
129+
val ForceInline: Mode = newMode(29, "ForceInline")
125130
}

compiler/src/dotty/tools/dotc/core/Phases.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ object Phases {
198198
private var myPostTyperPhase: Phase = _
199199
private var mySbtExtractDependenciesPhase: Phase = _
200200
private var myPicklerPhase: Phase = _
201+
private var myInliningPhase: Phase = _
201202
private var myPickleQuotesPhase: Phase = _
202203
private var myFirstTransformPhase: Phase = _
203204
private var myCollectNullableFieldsPhase: Phase = _
@@ -218,6 +219,7 @@ object Phases {
218219
final def postTyperPhase: Phase = myPostTyperPhase
219220
final def sbtExtractDependenciesPhase: Phase = mySbtExtractDependenciesPhase
220221
final def picklerPhase: Phase = myPicklerPhase
222+
final def inliningPhase: Phase = myInliningPhase
221223
final def pickleQuotesPhase: Phase = myPickleQuotesPhase
222224
final def firstTransformPhase: Phase = myFirstTransformPhase
223225
final def collectNullableFieldsPhase: Phase = myCollectNullableFieldsPhase
@@ -241,6 +243,7 @@ object Phases {
241243
myPostTyperPhase = phaseOfClass(classOf[PostTyper])
242244
mySbtExtractDependenciesPhase = phaseOfClass(classOf[sbt.ExtractDependencies])
243245
myPicklerPhase = phaseOfClass(classOf[Pickler])
246+
myInliningPhase = phaseOfClass(classOf[Inlining])
244247
myPickleQuotesPhase = phaseOfClass(classOf[PickleQuotes])
245248
myFirstTransformPhase = phaseOfClass(classOf[FirstTransform])
246249
myCollectNullableFieldsPhase = phaseOfClass(classOf[CollectNullableFields])
@@ -406,6 +409,7 @@ object Phases {
406409
def postTyperPhase(using Context): Phase = ctx.base.postTyperPhase
407410
def sbtExtractDependenciesPhase(using Context): Phase = ctx.base.sbtExtractDependenciesPhase
408411
def picklerPhase(using Context): Phase = ctx.base.picklerPhase
412+
def inliningPhase(using Context): Phase = ctx.base.inliningPhase
409413
def pickleQuotesPhase(using Context): Phase = ctx.base.pickleQuotesPhase
410414
def firstTransformPhase(using Context): Phase = ctx.base.firstTransformPhase
411415
def refchecksPhase(using Context): Phase = ctx.base.refchecksPhase

compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,11 @@ class ExtractAPI extends Phase {
7777
} finally pw.close()
7878
}
7979

80-
if (ctx.sbtCallback != null) {
80+
if ctx.sbtCallback != null &&
81+
!ctx.compilationUnit.suspendedAtInliningPhase // already registered before this unit was suspended
82+
then
8183
classes.foreach(ctx.sbtCallback.api(sourceFile.file, _))
8284
mainClasses.foreach(ctx.sbtCallback.mainClass(sourceFile.file, _))
83-
}
8485
}
8586
}
8687

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package dotty.tools
2+
package dotc
3+
package transform
4+
5+
import dotty.tools.dotc.core.Contexts._
6+
import dotty.tools.dotc.core.Decorators._
7+
import dotty.tools.dotc.core.Flags._
8+
import dotty.tools.dotc.core.Types._
9+
import dotty.tools.dotc.transform.MegaPhase.MiniPhase
10+
import dotty.tools.dotc.typer.Inliner
11+
12+
/** Check that `tree.rhs` can be right hand-side of an `inline` value definition. */
13+
class InlineVals extends MiniPhase:
14+
import ast.tpd._
15+
16+
def phaseName: String = "inlineVals"
17+
18+
override def checkPostCondition(tree: Tree)(using Context): Unit =
19+
if !ctx.erasedTypes then
20+
tree match
21+
case tree: ValDef => checkInlineConformant(tree)
22+
case _ =>
23+
24+
override def transformValDef(tree: ValDef)(using Context): Tree =
25+
checkInlineConformant(tree)
26+
tree
27+
28+
/** Check that `tree.rhs` can be right hand-side of an `inline` value definition. */
29+
private def checkInlineConformant(tree: ValDef)(using Context): Unit = {
30+
if tree.symbol.is(Inline, butNot = DeferredOrTermParamOrAccessor)
31+
&& !Inliner.inInlineMethod
32+
then
33+
val rhs = tree.rhs
34+
val tpt = tree.tpt
35+
tpt.tpe.widenTermRefExpr.dealias.normalized match
36+
case tp: ConstantType =>
37+
if !isPureExpr(rhs) then
38+
val details = if enclosingInlineds.isEmpty then "" else em"but was: $rhs"
39+
report.error(s"inline value must be pure$details", rhs.srcPos)
40+
case _ =>
41+
val pos = if tpt.span.isZeroExtent then rhs.srcPos else tpt.srcPos
42+
report.error(em"inline value must have a literal constant type", pos)
43+
}

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,22 @@ import scala.annotation.constructorOnly
3333
/** Inlines all calls to inline methods that are not in an inline method or a quote */
3434
class Inlining extends MacroTransform {
3535
import tpd._
36+
import Inlining._
3637

3738
override def phaseName: String = Inlining.name
3839

3940
override def allowsImplicitSearch: Boolean = true
4041

42+
override def run(using Context): Unit =
43+
if ctx.compilationUnit.needsInlining then
44+
try super.run
45+
catch case _: CompilationUnit.SuspendException => ()
46+
47+
override def runOn(units: List[CompilationUnit])(using Context): List[CompilationUnit] =
48+
val newUnits = super.runOn(units).filterNot(_.suspended)
49+
ctx.run.checkSuspendedUnits(newUnits)
50+
newUnits
51+
4152
override def checkPostCondition(tree: Tree)(using Context): Unit =
4253
tree match {
4354
case PackageDef(pid, _) if tree.symbol.owner == defn.RootClass =>
@@ -69,13 +80,13 @@ class Inlining extends MacroTransform {
6980
else super.transform(tree)
7081
case _: Typed | _: Block =>
7182
super.transform(tree)
72-
case _ if Inliner.isInlineable(tree) && !tree.tpe.widen.isInstanceOf[MethodOrPoly] && StagingContext.level == 0 =>
83+
case _ if Inliner.needsInlining(tree) =>
7384
val tree1 = super.transform(tree)
74-
val inlined = Inliner.inlineCall(tree1)
75-
if tree1 eq inlined then inlined
76-
else transform(inlined)
85+
if tree1.tpe.isError then tree1
86+
else Inliner.inlineCall(tree1)
7787
case _: GenericApply if tree.symbol.isQuote =>
78-
ctx.compilationUnit.needsStaging = true
88+
if level == 0 then
89+
ctx.compilationUnit.needsQuotePickling = true
7990
super.transform(tree)(using StagingContext.quoteContext)
8091
case _: GenericApply if tree.symbol.isExprSplice =>
8192
super.transform(tree)(using StagingContext.spliceContext)
@@ -85,6 +96,5 @@ class Inlining extends MacroTransform {
8596
}
8697
}
8798

88-
object Inlining {
99+
object Inlining:
89100
val name: String = "inlining"
90-
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class PickleQuotes extends MacroTransform {
8888
}
8989

9090
override def run(using Context): Unit =
91-
if (ctx.compilationUnit.needsStaging) super.run(using freshStagingContext)
91+
if (ctx.compilationUnit.needsQuotePickling) super.run(using freshStagingContext)
9292

9393
protected def newTransformer(using Context): Transformer = new Transformer {
9494
override def transform(tree: tpd.Tree)(using Context): tpd.Tree =

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,12 +258,16 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
258258
override def transform(tree: Tree)(using Context): Tree =
259259
try tree match {
260260
case tree: Ident if !tree.isType =>
261+
if tree.symbol.is(Inline) && !Inliner.inInlineMethod then
262+
ctx.compilationUnit.needsInlining = true
261263
checkNoConstructorProxy(tree)
262264
tree.tpe match {
263265
case tpe: ThisType => This(tpe.cls).withSpan(tree.span)
264266
case _ => tree
265267
}
266268
case tree @ Select(qual, name) =>
269+
if tree.symbol.is(Inline) then
270+
ctx.compilationUnit.needsInlining = true
267271
if (name.isTypeName) {
268272
Checking.checkRealizable(qual.tpe, qual.srcPos)
269273
withMode(Mode.Type)(super.transform(tree))
@@ -302,6 +306,9 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
302306
case tree: TypeApply =>
303307
if tree.symbol.isQuote then
304308
ctx.compilationUnit.needsStaging = true
309+
ctx.compilationUnit.needsQuotePickling = true
310+
if tree.symbol.is(Inline) then
311+
ctx.compilationUnit.needsInlining = true
305312
val tree1 @ TypeApply(fn, args) = normalizeTypeArgs(tree)
306313
args.foreach(checkInferredWellFormed)
307314
if (fn.symbol != defn.ChildAnnot.primaryConstructor)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ class TupleOptimizations extends MiniPhase with IdentityDenotTransformer {
173173
val size = tpes.size
174174
if (size == 0)
175175
// Array.emptyObjectArray
176-
ref(defn.ArrayModule).select("emptyObjectArray".toTermName).ensureApplied
176+
ref(defn.ArrayModule).select("emptyObjectArray".toTermName).ensureApplied.withSpan(tree.span)
177177
else if (size <= MaxTupleArity)
178178
// scala.runtime.Tuples.productToArray(tup.asInstanceOf[Product])
179179
ref(defn.RuntimeTuples_productToArray).appliedTo(tup.asInstance(defn.ProductClass.typeRef))

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ class YCheckPositions extends Phase {
2727

2828
// Check current context is correct
2929
assert(ctx.source == sources.head)
30-
if (!tree.isEmpty && !tree.isInstanceOf[untpd.TypedSplice] && ctx.typerState.isGlobalCommittable)
31-
if (!tree.isType) { // TODO also check types, currently we do not add Inlined(EmptyTree, _, _) for types. We should.
30+
if (!tree.isEmpty && !tree.isInstanceOf[untpd.TypedSplice] && !tree.isInstanceOf[Inlined] && ctx.typerState.isGlobalCommittable)
31+
if !tree.isType // TODO also check types, currently we do not add Inlined(EmptyTree, _, _) for types. We should.
32+
&& !tree.symbol.is(InlineProxy) // TODO check inline proxies (see tests/tun/lst)
33+
then
3234
val currentSource = sources.head
33-
assert(tree.source == currentSource, i"wrong source set for $tree # ${tree.uniqueId} of ${tree.getClass}, set to ${tree.source} but context had $currentSource")
34-
}
35+
assert(tree.source == currentSource, i"wrong source set for $tree # ${tree.uniqueId} of ${tree.getClass}, set to ${tree.source} but context had $currentSource\n ${tree.symbol.flagsString}")
3536

3637
// Recursivlely check children while keeping track of current source
3738
tree match {
@@ -42,7 +43,7 @@ class YCheckPositions extends Phase {
4243
traverse(expansion)(using inlineContext(EmptyTree).withSource(sources.head))
4344
sources = old
4445
case Inlined(call, bindings, expansion) =>
45-
bindings.foreach(traverse(_))
46+
// bindings.foreach(traverse(_)) // TODO check inline proxies (see tests/tun/lst)
4647
sources = call.symbol.topLevelClass.source :: sources
4748
if (!isMacro(call)) // FIXME macro implementations can drop Inlined nodes. We should reinsert them after macro expansion based on the positions of the trees
4849
traverse(expansion)(using inlineContext(call).withSource(sources.head))
@@ -55,8 +56,10 @@ class YCheckPositions extends Phase {
5556
}
5657

5758
private def isMacro(call: Tree)(using Context) =
58-
if (ctx.phase <= postTyperPhase) call.symbol.is(Macro)
59-
else call.isInstanceOf[Select] // The call of a macro after typer is encoded as a Select while other inlines are Ident
60-
// TODO remove this distinction once Inline nodes of expanded macros can be trusted (also in Inliner.inlineCallTrace)
59+
call.symbol.is(Macro) ||
60+
// The call of a macro after typer is encoded as a Select while other inlines are Ident
61+
// TODO remove this distinction once Inline nodes of expanded macros can be trusted (also in Inliner.inlineCallTrace)
62+
(!(ctx.phase <= postTyperPhase) && call.isInstanceOf[Select])
63+
6164
}
6265

0 commit comments

Comments
 (0)