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

docs/docs/reference/contextual/givens.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,16 @@ given (using config: Config) as Factory = MemoizingFactory(config)
7979
An alias given can have type parameters and context parameters just like any other given,
8080
but it can only implement a single type.
8181

82-
## Given Whitebox Macro Instances
82+
## Given Macros
8383

84-
An `inline` alias given can be marked as a whitebox macro by writing
85-
`_ <:` in front of the implemented type. Example:
84+
Given aliases can have the `inline` and `transparent` modifiers.
85+
Example:
8686
```scala
87-
inline given mkAnnotations[A, T] as _ <: Annotations[A, T] = ${
87+
transparent inline given mkAnnotations[A, T] as Annotations[A, T] = ${
8888
// code producing a value of a subtype of Annotations
8989
}
9090
```
91-
The type of an application of `mkAnnotations` is the type of its right hand side,
92-
which can be a proper subtype of the declared result type `Annotations[A, T]`.
91+
Since `mkAnnotations` is `transparent`, the type of an application is the type of its right hand side, which can be a proper subtype of the declared result type `Annotations[A, T]`.
9392

9493
## Given Instance Initialization
9594

@@ -104,7 +103,7 @@ Here is the new syntax for given instances, seen as a delta from the [standard c
104103
```
105104
TmplDef ::= ...
106105
| ‘given’ GivenDef
107-
GivenDef ::= [GivenSig] [‘_’ ‘<:’] Type ‘=’ Expr
106+
GivenDef ::= [GivenSig] Type ‘=’ Expr
108107
| [GivenSig] ConstrApp {‘,’ ConstrApp } [TemplateBody]
109108
GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘as’
110109
```

docs/docs/reference/metaprogramming/inline.md

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -228,38 +228,33 @@ constant expressions in the sense defined by the [SLS §
228228
including _platform-specific_ extensions such as constant folding of pure
229229
numeric computations.
230230

231-
## Specializing Inline (Whitebox)
231+
## Transparent Inline Methods
232232

233-
Inline methods support the ` <: T` return type syntax. This means that the return type
234-
of the inline method is going to be specialized to a more precise type upon
235-
expansion. Example:
233+
Inline methods can additionally be declared `transparent`.
234+
This means that the return type of the inline method can be
235+
specialized to a more precise type upon expansion. Example:
236236

237237
```scala
238238
class A
239239
class B extends A {
240-
def meth() = true
240+
def m() = true
241241
}
242242

243-
inline def choose(b: Boolean) <: A = {
244-
if (b) new A()
245-
else new B()
246-
}
243+
transparent inline def choose(b: Boolean): A =
244+
if b then A() else B
247245

248246
val obj1 = choose(true) // static type is A
249247
val obj2 = choose(false) // static type is B
250248

251-
// obj1.meth() // compile-time error: `meth` is not defined on `A`
252-
obj2.meth() // OK
249+
// obj1.m() // compile-time error: `m` is not defined on `A`
250+
obj2.m() // OK
253251
```
254-
Here, the inline method `choose` returns an object of either of the two dynamic types
255-
`A` and `B`. If `choose` had been declared with a normal return type `: A`, the result
256-
of its expansion would always be of type `A`, even though the computed value might be
257-
of type `B`. The inline method is a "blackbox" in the sense that details of its
258-
implementation do not leak out. But with the specializing return type `<: A`,
259-
the type of the expansion is the type of the expanded body. If the argument `b`
252+
Here, the inline method `choose` returns an object of either of the two types `A` and `B`. If `choose` had been declared with a normal return type `: A`, the result
253+
of its expansion would always be of type `A`, even though the computed value might be of the subtype `B`. The inline method is a "blackbox" in the sense that details of its implementation do not leak out. But if a `transparent` modifier is given,
254+
the expansion is the type of the expanded body. If the argument `b`
260255
is `true`, that type is `A`, otherwise it is `B`. Consequently, calling `meth` on `obj2`
261256
type-checks since `obj2` has the same type as the expansion of `choose(false)`, which is `B`.
262-
Inline methods with specializing return types are a "whitebox" in the sense that the type
257+
Transparent inline methods are "whitebox" in the sense that the type
263258
of an application of such a method can be more specialized than its declared
264259
return type, depending on how the method expands.
265260

@@ -268,7 +263,7 @@ the singleton type `0` permitting the addition to be ascribed with the correct
268263
type `1`.
269264

270265
```scala
271-
inline def zero() <: Int = 0
266+
transparent inline def zero(): Int = 0
272267

273268
val one: 1 = zero() + 1
274269
```
@@ -313,7 +308,7 @@ The example below defines an inline method with a
313308
single inline match expression that picks a case based on its static type:
314309

315310
```scala
316-
inline def g(x: Any) <: Any = inline x match {
311+
transparent inline def g(x: Any): Any = inline x match {
317312
case x: String => (x, x) // Tuple2[String, String](x, x)
318313
case x: Double => x
319314
}
@@ -323,8 +318,7 @@ g("test") // Has type (String, String)
323318
```
324319

325320
The scrutinee `x` is examined statically and the inline match is reduced
326-
accordingly returning the corresponding value (with the type specialized due to
327-
the `<:` in the return type). This example performs a simple type test over the
321+
accordingly returning the corresponding value (with the type specialized because `g` is declared `transparent`). This example performs a simple type test over the
328322
scrutinee. The type can have a richer structure like the simple ADT below.
329323
`toInt` matches the structure of a number in [Church-encoding](https://en.wikipedia.org/wiki/Church_encoding)
330324
and _computes_ the corresponding integer.
@@ -334,7 +328,7 @@ trait Nat
334328
case object Zero extends Nat
335329
case class Succ[N <: Nat](n: N) extends Nat
336330

337-
inline def toInt(n: Nat) <: Int = inline n match {
331+
transparent inline def toInt(n: Nat): Int = inline n match {
338332
case Zero => 0
339333
case Succ(n1) => toInt(n1) + 1
340334
}
@@ -357,7 +351,7 @@ type.
357351
```scala
358352
import scala.compiletime.{constValue, S}
359353

360-
inline def toIntC[N] <: Int =
354+
transparent inline def toIntC[N]: Int =
361355
inline constValue[N] match {
362356
case 0 => 0
363357
case _: S[n1] => 1 + toIntC[n1]
@@ -423,10 +417,11 @@ Consider the definitions of numbers as in the _Inline
423417
Match_ section above. Here is how `toIntT` can be defined:
424418

425419
```scala
426-
inline def toIntT[N <: Nat] <: Int = inline scala.compiletime.erasedValue[N] match {
427-
case _: Zero.type => 0
428-
case _: Succ[n] => toIntT[n] + 1
429-
}
420+
transparent inline def toIntT[N <: Nat]: Int =
421+
inline scala.compiletime.erasedValue[N] match {
422+
case _: Zero.type => 0
423+
case _: Succ[n] => toIntT[n] + 1
424+
}
430425

431426
final val two = toIntT[Succ[Succ[Zero.type]]]
432427
```
@@ -595,7 +590,7 @@ inline def f: Any = summonFrom {
595590

596591
The shorthand `summonInline` provides a simple way to write a `summon` that is delayed until the call is inlined.
597592
```scala
598-
inline def summonInline[T] <: T = summonFrom {
593+
transparent inline def summonInline[T]: T = summonFrom {
599594
case t: T => t
600595
}
601596
```

tests/explicit-nulls/pos/flow4.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ class TreeOps {
66
abstract class Tree[A, B](val key: A, val value: B)
77
class RedTree[A, B](override val key: A, override val value: B) extends Tree[A, B](key, value)
88

9-
private[this] inline def isRedTree(tree: Tree[_, _] | Null) = (tree != null) && tree.isInstanceOf[RedTree[_, _]]
9+
private transparent inline def isRedTree(tree: Tree[_, _] | Null) =
10+
(tree != null) && tree.isInstanceOf[RedTree[_, _]]
1011

1112
def foo[A, B](tree: Tree[A, B] | Null): Unit = {
1213
if (isRedTree(tree)) {

tests/neg/i7078.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
trait A
22
class B extends A
33

4-
given g1 as _ <: A = B() // error: `_ <:' is only allowed for given with `inline' modifier // error
4+
given g1 as _ <: A = B() // error: `_ <:' is only allowed for given with `inline' modifier
55

66
inline given g2 as _ <: A: // error: `=' expected
77
def foo = 2

tests/pos/inline-rewrite.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
object Test {
22

3-
inline def f(x: Int) = inline x match {
3+
transparent inline def f(x: Int) = inline x match {
44
case 1 => "a"
55
case 2 => 22
66
}
@@ -9,7 +9,7 @@ object Test {
99
val y = f(2)
1010
val yc: Int = y
1111

12-
inline def g(x: Any) = inline x match {
12+
transparent inline def g(x: Any) = inline x match {
1313
case x: String => (x, x)
1414
case x: Double => x
1515
}

tests/run-macros/i7887/Macro_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ def myMacroImpl(a: quoted.Expr[_])(using qctx: quoted.QuoteContext) = {
1212
}
1313

1414

15-
inline def myMacro(a: => Any) = ${
15+
inline transparent def myMacro(a: => Any) = ${
1616
myMacroImpl('a)
1717
}

0 commit comments

Comments
 (0)