Skip to content

Commit ff328ce

Browse files
committed
Fix #8619: Demand transparent for whitebox inlines
Previously, whitebox inlines could be declared in three different ways 1. writing `inline def f <: T = ...` for inline methods 2. writing `inline given f: _ <: T` = ...` for inline givens 3. leaving out the result type for inline methods The last was an oversight, not backed by the spec. It turned out that it was not that easy to implement (3), so it was not done, and afterwards code exploited the loophole. In the new scheme, `whitebox` inlines demand `transparent`. The result type can be given or left out, the effect is the same. The old `<:` result type syntax will be phased out in a subseqent PR once the new syntax is in a release. `transparent` is a soft modifier. It is valid only together with `inline`. Why not allow `transparent` on its own and let it subsume `inline`. The point is that we should steer users firmly towards blackbox macros, since they are much easier to deal with for tooling. This means we want to make whitebox macros strictly more verbose than blackbox macros. Otherwise someone might find `transparent` "nicer" than `inline` and simply use it on these grounds without realizing (or caring about) the consequences. For the same reason I did not follow the (otherwise tempting) idea to simply re-use `opaque` instead of `transparent`. An opaque inline kleeps its type on expansion whereas a transparent one gets the type of its expansion. But that would have nudged to user to prefer `inline` over `opaque inline`, so would again have gjven the wrong incentive. On the other hand, `transparent` as a dual of `opaque` is nice. It fits into the same terminology. It's simply that type aliases are transparent by default and have to be made opaque with a modifier, whereas inline methods are opaque by default, and have to be made transparent by a modifier.
1 parent ad7da2f commit ff328ce

File tree

16 files changed

+72
-66
lines changed

16 files changed

+72
-66
lines changed

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -247,18 +247,9 @@ object desugar {
247247
cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs))
248248
}
249249

250-
var meth1 = addEvidenceParams(
250+
val meth1 = addEvidenceParams(
251251
cpy.DefDef(meth)(name = methName, tparams = tparams1), epbuf.toList)
252252

253-
if (meth1.mods.is(Inline))
254-
meth1.tpt match {
255-
case TypeBoundsTree(_, tpt1, _) =>
256-
meth1 = cpy.DefDef(meth1)(tpt = tpt1)
257-
case tpt if !tpt.isEmpty && !meth1.rhs.isEmpty =>
258-
meth1 = cpy.DefDef(meth1)(rhs = Typed(meth1.rhs, tpt))
259-
case _ =>
260-
}
261-
262253
/** The longest prefix of parameter lists in vparamss whose total length does not exceed `n` */
263254
def takeUpTo(vparamss: List[List[ValDef]], n: Int): List[List[ValDef]] = vparamss match {
264255
case vparams :: vparamss1 =>

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
195195
case class Lazy()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Lazy)
196196

197197
case class Inline()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Inline)
198+
199+
case class Transparent()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.EmptyFlags)
198200
}
199201

200202
/** Modifiers and annotations for definitions
@@ -326,7 +328,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
326328
def derivedTree(originalSym: Symbol)(implicit ctx: Context): tpd.Tree
327329
}
328330

329-
/** Property key containing TypeTrees whose type is computed
331+
/** Property key containing TypeTrees whose type is computed
330332
* from the symbol in this type. These type trees have marker trees
331333
* TypeRefOfSym or InfoOfSym as their originals.
332334
*/

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@ object StdNames {
593593
val toString_ : N = "toString"
594594
val toTypeConstructor: N = "toTypeConstructor"
595595
val tpe : N = "tpe"
596+
val transparent : N = "transparent"
596597
val tree : N = "tree"
597598
val true_ : N = "true"
598599
val typedProductIterator: N = "typedProductIterator"

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2692,6 +2692,7 @@ object Parsers {
26922692
case nme.inline => Mod.Inline()
26932693
case nme.opaque => Mod.Opaque()
26942694
case nme.open => Mod.Open()
2695+
case nme.transparent => Mod.Transparent()
26952696
}
26962697
}
26972698

@@ -2748,7 +2749,7 @@ object Parsers {
27482749
* | AccessModifier
27492750
* | override
27502751
* | opaque
2751-
* LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased | inline
2752+
* LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased | inline | transparent
27522753
*/
27532754
def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = {
27542755
@tailrec
@@ -2766,7 +2767,11 @@ object Parsers {
27662767
}
27672768
else
27682769
mods
2769-
normalize(loop(start))
2770+
val result = normalize(loop(start))
2771+
for case mod @ Mod.Transparent() <- result.mods do
2772+
if !result.is(Inline) then
2773+
syntaxError(em"`transparent` can only be used in conjunction with `inline`", mod.span)
2774+
result
27702775
}
27712776

27722777
val funTypeArgMods: BitSet = BitSet(ERASED)
@@ -3250,7 +3255,8 @@ object Parsers {
32503255
var tpt = fromWithinReturnType {
32513256
if in.token == SUBTYPE && mods.is(Inline) then
32523257
in.nextToken()
3253-
TypeBoundsTree(EmptyTree, toplevelTyp())
3258+
mods1 = addMod(mods1, Mod.Transparent())
3259+
toplevelTyp()
32543260
else typedOpt()
32553261
}
32563262
if (in.isScala2CompatMode) newLineOptWhenFollowedBy(LBRACE)
@@ -3521,7 +3527,8 @@ object Parsers {
35213527
syntaxError("`_ <:` is only allowed for given with `inline` modifier")
35223528
in.nextToken()
35233529
accept(SUBTYPE)
3524-
givenAlias(TypeBoundsTree(EmptyTree, toplevelTyp()))
3530+
mods1 = addMod(mods1, Mod.Transparent())
3531+
givenAlias(toplevelTyp())
35253532
else
35263533
val parents = constrApps(commaOK = true, templateCanFollow = true)
35273534
if in.token == EQUALS && parents.length == 1 && parents.head.isType then

compiler/src/dotty/tools/dotc/parsing/Tokens.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,5 +282,5 @@ object Tokens extends TokensCommon {
282282

283283
final val scala3keywords = BitSet(ENUM, ERASED, GIVEN)
284284

285-
final val softModifierNames = Set(nme.inline, nme.opaque, nme.open)
285+
final val softModifierNames = Set(nme.inline, nme.opaque, nme.open, nme.transparent)
286286
}

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -864,7 +864,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
864864
val rawFlags = if (sym.exists) sym.flags else mods.flags
865865
if (rawFlags.is(Param)) flagMask = flagMask &~ Given
866866
val flags = rawFlags & flagMask
867-
val flagsText = toTextFlags(sym, flags)
867+
var flagsText = toTextFlags(sym, flags)
868+
if mods.hasMod(classOf[untpd.Mod.Transparent]) then
869+
flagsText = "transparent " ~ flagsText
868870
val annotations =
869871
if (sym.exists) sym.annotations.filterNot(ann => dropAnnotForModText(ann.symbol)).map(_.tree)
870872
else mods.annotations.filterNot(tree => dropAnnotForModText(tree.symbol))

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -863,10 +863,11 @@ class Namer { typer: Typer =>
863863

864864
private def addInlineInfo(sym: Symbol) = original match {
865865
case original: untpd.DefDef if sym.isInlineMethod =>
866-
PrepareInlineable.registerInlineInfo(
867-
sym,
868-
implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs
869-
)(localContext(sym))
866+
def rhsToInline(using Context): tpd.Tree =
867+
val mdef = typedAheadExpr(original).asInstanceOf[tpd.DefDef]
868+
if original.mods.hasMod(classOf[untpd.Mod.Transparent]) then mdef.rhs
869+
else tpd.Typed(mdef.rhs, mdef.tpt)
870+
PrepareInlineable.registerInlineInfo(sym, rhsToInline)(localContext(sym))
870871
case _ =>
871872
}
872873

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ object PrepareInlineable {
204204
def isLocal(sym: Symbol, inlineMethod: Symbol)(implicit ctx: Context): Boolean =
205205
isLocalOrParam(sym, inlineMethod) && !(sym.is(Param) && sym.owner == inlineMethod)
206206

207+
/** The type ascription `rhs: tpt`, unless `original` is `transparent`. */
208+
def wrapRHS(original: untpd.DefDef, tpt: Tree, rhs: Tree)(using Context): Tree =
209+
if original.mods.mods.exists(_.isInstanceOf[untpd.Mod.Transparent]) then rhs
210+
else Typed(rhs, tpt)
211+
207212
/** Register inline info for given inlineable method `sym`.
208213
*
209214
* @param sym The symbol denotation of the inlineable method for which info is registered
@@ -213,7 +218,7 @@ object PrepareInlineable {
213218
* to have the inline method as owner.
214219
*/
215220
def registerInlineInfo(
216-
inlined: Symbol, treeExpr: Context => Tree)(implicit ctx: Context): Unit =
221+
inlined: Symbol, treeExpr: Context ?=> Tree)(implicit ctx: Context): Unit =
217222
inlined.unforcedAnnotation(defn.BodyAnnot) match {
218223
case Some(ann: ConcreteBodyAnnotation) =>
219224
case Some(ann: LazyBodyAnnotation) if ann.isEvaluated || ann.isEvaluating =>
@@ -223,7 +228,7 @@ object PrepareInlineable {
223228
inlined.updateAnnotation(LazyBodyAnnotation {
224229
given ctx as Context = inlineCtx
225230
val initialErrorCount = ctx.reporter.errorCount
226-
var inlinedBody = treeExpr(ctx)
231+
var inlinedBody = treeExpr(using ctx)
227232
if (ctx.reporter.errorCount == initialErrorCount) {
228233
inlinedBody = ctx.compilationUnit.inlineAccessors.makeInlineable(inlinedBody)
229234
checkInlineMethod(inlined, inlinedBody)

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1735,9 +1735,10 @@ class Typer extends Namer
17351735

17361736
if (sym.isInlineMethod) rhsCtx.addMode(Mode.InlineableBody)
17371737
val rhs1 = typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(rhsCtx)
1738+
val rhsToInline = PrepareInlineable.wrapRHS(ddef, tpt1, rhs1)
17381739

17391740
if (sym.isInlineMethod)
1740-
PrepareInlineable.registerInlineInfo(sym, _ => rhs1)
1741+
PrepareInlineable.registerInlineInfo(sym, rhsToInline)
17411742

17421743
if (sym.isConstructor && !sym.isPrimaryConstructor) {
17431744
val ename = sym.erasedName

docs/docs/internals/syntax.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ yield
104104
### Soft keywords
105105

106106
```
107-
as derives extension inline on opaque open using
107+
as derives extension inline on opaque open transparent
108+
using
108109
* + -
109110
```
110111

0 commit comments

Comments
 (0)