From ff328ceef6eb3dcb96745e64a1d25d842e33d9a3 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 29 Mar 2020 13:35:43 +0200 Subject: [PATCH 1/8] 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. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 11 +--- compiler/src/dotty/tools/dotc/ast/untpd.scala | 4 +- .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../dotty/tools/dotc/parsing/Parsers.scala | 15 ++++-- .../src/dotty/tools/dotc/parsing/Tokens.scala | 2 +- .../tools/dotc/printing/RefinedPrinter.scala | 4 +- .../src/dotty/tools/dotc/typer/Namer.scala | 9 ++-- .../tools/dotc/typer/PrepareInlineable.scala | 9 +++- .../src/dotty/tools/dotc/typer/Typer.scala | 3 +- docs/docs/internals/syntax.md | 3 +- docs/docs/reference/contextual/givens.md | 13 +++-- docs/docs/reference/metaprogramming/inline.md | 53 +++++++++---------- tests/explicit-nulls/pos/flow4.scala | 3 +- tests/neg/i7078.scala | 2 +- tests/pos/inline-rewrite.scala | 4 +- tests/run-macros/i7887/Macro_1.scala | 2 +- 16 files changed, 72 insertions(+), 66 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index d6b69420596b..2d8d09a6340d 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -247,18 +247,9 @@ object desugar { cpy.TypeDef(tparam)(rhs = desugarContextBounds(tparam.rhs)) } - var meth1 = addEvidenceParams( + val meth1 = addEvidenceParams( cpy.DefDef(meth)(name = methName, tparams = tparams1), epbuf.toList) - if (meth1.mods.is(Inline)) - meth1.tpt match { - case TypeBoundsTree(_, tpt1, _) => - meth1 = cpy.DefDef(meth1)(tpt = tpt1) - case tpt if !tpt.isEmpty && !meth1.rhs.isEmpty => - meth1 = cpy.DefDef(meth1)(rhs = Typed(meth1.rhs, tpt)) - case _ => - } - /** The longest prefix of parameter lists in vparamss whose total length does not exceed `n` */ def takeUpTo(vparamss: List[List[ValDef]], n: Int): List[List[ValDef]] = vparamss match { case vparams :: vparamss1 => diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 24dc1611d229..7251fa986885 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -195,6 +195,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Lazy()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Lazy) case class Inline()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Inline) + + case class Transparent()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.EmptyFlags) } /** Modifiers and annotations for definitions @@ -326,7 +328,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def derivedTree(originalSym: Symbol)(implicit ctx: Context): tpd.Tree } - /** Property key containing TypeTrees whose type is computed + /** Property key containing TypeTrees whose type is computed * from the symbol in this type. These type trees have marker trees * TypeRefOfSym or InfoOfSym as their originals. */ diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 6ae10e0765bb..3f79794fe21a 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -593,6 +593,7 @@ object StdNames { val toString_ : N = "toString" val toTypeConstructor: N = "toTypeConstructor" val tpe : N = "tpe" + val transparent : N = "transparent" val tree : N = "tree" val true_ : N = "true" val typedProductIterator: N = "typedProductIterator" diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index cacfbc9d0ff9..ed7975d5a8f1 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -2692,6 +2692,7 @@ object Parsers { case nme.inline => Mod.Inline() case nme.opaque => Mod.Opaque() case nme.open => Mod.Open() + case nme.transparent => Mod.Transparent() } } @@ -2748,7 +2749,7 @@ object Parsers { * | AccessModifier * | override * | opaque - * LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased | inline + * LocalModifier ::= abstract | final | sealed | open | implicit | lazy | erased | inline | transparent */ def modifiers(allowed: BitSet = modifierTokens, start: Modifiers = Modifiers()): Modifiers = { @tailrec @@ -2766,7 +2767,11 @@ object Parsers { } else mods - normalize(loop(start)) + val result = normalize(loop(start)) + for case mod @ Mod.Transparent() <- result.mods do + if !result.is(Inline) then + syntaxError(em"`transparent` can only be used in conjunction with `inline`", mod.span) + result } val funTypeArgMods: BitSet = BitSet(ERASED) @@ -3250,7 +3255,8 @@ object Parsers { var tpt = fromWithinReturnType { if in.token == SUBTYPE && mods.is(Inline) then in.nextToken() - TypeBoundsTree(EmptyTree, toplevelTyp()) + mods1 = addMod(mods1, Mod.Transparent()) + toplevelTyp() else typedOpt() } if (in.isScala2CompatMode) newLineOptWhenFollowedBy(LBRACE) @@ -3521,7 +3527,8 @@ object Parsers { syntaxError("`_ <:` is only allowed for given with `inline` modifier") in.nextToken() accept(SUBTYPE) - givenAlias(TypeBoundsTree(EmptyTree, toplevelTyp())) + mods1 = addMod(mods1, Mod.Transparent()) + givenAlias(toplevelTyp()) else val parents = constrApps(commaOK = true, templateCanFollow = true) if in.token == EQUALS && parents.length == 1 && parents.head.isType then diff --git a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala index 31a77e51933e..c194108a0484 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Tokens.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Tokens.scala @@ -282,5 +282,5 @@ object Tokens extends TokensCommon { final val scala3keywords = BitSet(ENUM, ERASED, GIVEN) - final val softModifierNames = Set(nme.inline, nme.opaque, nme.open) + final val softModifierNames = Set(nme.inline, nme.opaque, nme.open, nme.transparent) } diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index dd5d6742c2c5..a30506ba4e69 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -864,7 +864,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { val rawFlags = if (sym.exists) sym.flags else mods.flags if (rawFlags.is(Param)) flagMask = flagMask &~ Given val flags = rawFlags & flagMask - val flagsText = toTextFlags(sym, flags) + var flagsText = toTextFlags(sym, flags) + if mods.hasMod(classOf[untpd.Mod.Transparent]) then + flagsText = "transparent " ~ flagsText val annotations = if (sym.exists) sym.annotations.filterNot(ann => dropAnnotForModText(ann.symbol)).map(_.tree) else mods.annotations.filterNot(tree => dropAnnotForModText(tree.symbol)) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 41a721e91c0e..1116c69515a5 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -863,10 +863,11 @@ class Namer { typer: Typer => private def addInlineInfo(sym: Symbol) = original match { case original: untpd.DefDef if sym.isInlineMethod => - PrepareInlineable.registerInlineInfo( - sym, - implicit ctx => typedAheadExpr(original).asInstanceOf[tpd.DefDef].rhs - )(localContext(sym)) + def rhsToInline(using Context): tpd.Tree = + val mdef = typedAheadExpr(original).asInstanceOf[tpd.DefDef] + if original.mods.hasMod(classOf[untpd.Mod.Transparent]) then mdef.rhs + else tpd.Typed(mdef.rhs, mdef.tpt) + PrepareInlineable.registerInlineInfo(sym, rhsToInline)(localContext(sym)) case _ => } diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala index 1cf6983fedec..9f976768ecbd 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala @@ -204,6 +204,11 @@ object PrepareInlineable { def isLocal(sym: Symbol, inlineMethod: Symbol)(implicit ctx: Context): Boolean = isLocalOrParam(sym, inlineMethod) && !(sym.is(Param) && sym.owner == inlineMethod) + /** The type ascription `rhs: tpt`, unless `original` is `transparent`. */ + def wrapRHS(original: untpd.DefDef, tpt: Tree, rhs: Tree)(using Context): Tree = + if original.mods.mods.exists(_.isInstanceOf[untpd.Mod.Transparent]) then rhs + else Typed(rhs, tpt) + /** Register inline info for given inlineable method `sym`. * * @param sym The symbol denotation of the inlineable method for which info is registered @@ -213,7 +218,7 @@ object PrepareInlineable { * to have the inline method as owner. */ def registerInlineInfo( - inlined: Symbol, treeExpr: Context => Tree)(implicit ctx: Context): Unit = + inlined: Symbol, treeExpr: Context ?=> Tree)(implicit ctx: Context): Unit = inlined.unforcedAnnotation(defn.BodyAnnot) match { case Some(ann: ConcreteBodyAnnotation) => case Some(ann: LazyBodyAnnotation) if ann.isEvaluated || ann.isEvaluating => @@ -223,7 +228,7 @@ object PrepareInlineable { inlined.updateAnnotation(LazyBodyAnnotation { given ctx as Context = inlineCtx val initialErrorCount = ctx.reporter.errorCount - var inlinedBody = treeExpr(ctx) + var inlinedBody = treeExpr(using ctx) if (ctx.reporter.errorCount == initialErrorCount) { inlinedBody = ctx.compilationUnit.inlineAccessors.makeInlineable(inlinedBody) checkInlineMethod(inlined, inlinedBody) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 426f1487dcf8..6307817963fa 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1735,9 +1735,10 @@ class Typer extends Namer if (sym.isInlineMethod) rhsCtx.addMode(Mode.InlineableBody) val rhs1 = typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(rhsCtx) + val rhsToInline = PrepareInlineable.wrapRHS(ddef, tpt1, rhs1) if (sym.isInlineMethod) - PrepareInlineable.registerInlineInfo(sym, _ => rhs1) + PrepareInlineable.registerInlineInfo(sym, rhsToInline) if (sym.isConstructor && !sym.isPrimaryConstructor) { val ename = sym.erasedName diff --git a/docs/docs/internals/syntax.md b/docs/docs/internals/syntax.md index c8434db96410..96d0506c06d6 100644 --- a/docs/docs/internals/syntax.md +++ b/docs/docs/internals/syntax.md @@ -104,7 +104,8 @@ yield ### Soft keywords ``` -as derives extension inline on opaque open using +as derives extension inline on opaque open transparent +using * + - ``` diff --git a/docs/docs/reference/contextual/givens.md b/docs/docs/reference/contextual/givens.md index 284997dd5673..000681379ab4 100644 --- a/docs/docs/reference/contextual/givens.md +++ b/docs/docs/reference/contextual/givens.md @@ -79,17 +79,16 @@ given (using config: Config) as Factory = MemoizingFactory(config) An alias given can have type parameters and context parameters just like any other given, but it can only implement a single type. -## Given Whitebox Macro Instances +## Given Macros -An `inline` alias given can be marked as a whitebox macro by writing -`_ <:` in front of the implemented type. Example: +Given aliases can have the `inline` and `transparent` modifiers. +Example: ```scala -inline given mkAnnotations[A, T] as _ <: Annotations[A, T] = ${ +transparent inline given mkAnnotations[A, T] as Annotations[A, T] = ${ // code producing a value of a subtype of Annotations } ``` -The type of an application of `mkAnnotations` is the type of its right hand side, -which can be a proper subtype of the declared result type `Annotations[A, T]`. +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]`. ## Given Instance Initialization @@ -104,7 +103,7 @@ Here is the new syntax for given instances, seen as a delta from the [standard c ``` TmplDef ::= ... | ‘given’ GivenDef -GivenDef ::= [GivenSig] [‘_’ ‘<:’] Type ‘=’ Expr +GivenDef ::= [GivenSig] Type ‘=’ Expr | [GivenSig] ConstrApp {‘,’ ConstrApp } [TemplateBody] GivenSig ::= [id] [DefTypeParamClause] {UsingParamClause} ‘as’ ``` diff --git a/docs/docs/reference/metaprogramming/inline.md b/docs/docs/reference/metaprogramming/inline.md index b47d21bbe0cb..9e1ba3bb002f 100644 --- a/docs/docs/reference/metaprogramming/inline.md +++ b/docs/docs/reference/metaprogramming/inline.md @@ -228,38 +228,33 @@ constant expressions in the sense defined by the [SLS § including _platform-specific_ extensions such as constant folding of pure numeric computations. -## Specializing Inline (Whitebox) +## Transparent Inline Methods -Inline methods support the ` <: T` return type syntax. This means that the return type -of the inline method is going to be specialized to a more precise type upon -expansion. Example: +Inline methods can additionally be declared `transparent`. +This means that the return type of the inline method can be +specialized to a more precise type upon expansion. Example: ```scala class A class B extends A { - def meth() = true + def m() = true } -inline def choose(b: Boolean) <: A = { - if (b) new A() - else new B() -} +transparent inline def choose(b: Boolean): A = + if b then A() else B val obj1 = choose(true) // static type is A val obj2 = choose(false) // static type is B -// obj1.meth() // compile-time error: `meth` is not defined on `A` -obj2.meth() // OK +// obj1.m() // compile-time error: `m` is not defined on `A` +obj2.m() // OK ``` -Here, the inline method `choose` returns an object of either of the two dynamic types -`A` and `B`. If `choose` had been declared with a normal return type `: A`, the result -of its expansion would always be of type `A`, even though the computed value might be -of type `B`. The inline method is a "blackbox" in the sense that details of its -implementation do not leak out. But with the specializing return type `<: A`, -the type of the expansion is the type of the expanded body. If the argument `b` +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 +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, +the expansion is the type of the expanded body. If the argument `b` is `true`, that type is `A`, otherwise it is `B`. Consequently, calling `meth` on `obj2` type-checks since `obj2` has the same type as the expansion of `choose(false)`, which is `B`. -Inline methods with specializing return types are a "whitebox" in the sense that the type +Transparent inline methods are "whitebox" in the sense that the type of an application of such a method can be more specialized than its declared return type, depending on how the method expands. @@ -268,7 +263,7 @@ the singleton type `0` permitting the addition to be ascribed with the correct type `1`. ```scala -inline def zero() <: Int = 0 +transparent inline def zero(): Int = 0 val one: 1 = zero() + 1 ``` @@ -313,7 +308,7 @@ The example below defines an inline method with a single inline match expression that picks a case based on its static type: ```scala -inline def g(x: Any) <: Any = inline x match { +transparent inline def g(x: Any): Any = inline x match { case x: String => (x, x) // Tuple2[String, String](x, x) case x: Double => x } @@ -323,8 +318,7 @@ g("test") // Has type (String, String) ``` The scrutinee `x` is examined statically and the inline match is reduced -accordingly returning the corresponding value (with the type specialized due to -the `<:` in the return type). This example performs a simple type test over the +accordingly returning the corresponding value (with the type specialized because `g` is declared `transparent`). This example performs a simple type test over the scrutinee. The type can have a richer structure like the simple ADT below. `toInt` matches the structure of a number in [Church-encoding](https://en.wikipedia.org/wiki/Church_encoding) and _computes_ the corresponding integer. @@ -334,7 +328,7 @@ trait Nat case object Zero extends Nat case class Succ[N <: Nat](n: N) extends Nat -inline def toInt(n: Nat) <: Int = inline n match { +transparent inline def toInt(n: Nat): Int = inline n match { case Zero => 0 case Succ(n1) => toInt(n1) + 1 } @@ -357,7 +351,7 @@ type. ```scala import scala.compiletime.{constValue, S} -inline def toIntC[N] <: Int = +transparent inline def toIntC[N]: Int = inline constValue[N] match { case 0 => 0 case _: S[n1] => 1 + toIntC[n1] @@ -423,10 +417,11 @@ Consider the definitions of numbers as in the _Inline Match_ section above. Here is how `toIntT` can be defined: ```scala -inline def toIntT[N <: Nat] <: Int = inline scala.compiletime.erasedValue[N] match { - case _: Zero.type => 0 - case _: Succ[n] => toIntT[n] + 1 -} +transparent inline def toIntT[N <: Nat]: Int = + inline scala.compiletime.erasedValue[N] match { + case _: Zero.type => 0 + case _: Succ[n] => toIntT[n] + 1 + } final val two = toIntT[Succ[Succ[Zero.type]]] ``` @@ -595,7 +590,7 @@ inline def f: Any = summonFrom { The shorthand `summonInline` provides a simple way to write a `summon` that is delayed until the call is inlined. ```scala -inline def summonInline[T] <: T = summonFrom { +transparent inline def summonInline[T]: T = summonFrom { case t: T => t } ``` diff --git a/tests/explicit-nulls/pos/flow4.scala b/tests/explicit-nulls/pos/flow4.scala index 8327606bcd13..994b76065d8c 100644 --- a/tests/explicit-nulls/pos/flow4.scala +++ b/tests/explicit-nulls/pos/flow4.scala @@ -6,7 +6,8 @@ class TreeOps { abstract class Tree[A, B](val key: A, val value: B) class RedTree[A, B](override val key: A, override val value: B) extends Tree[A, B](key, value) - private[this] inline def isRedTree(tree: Tree[_, _] | Null) = (tree != null) && tree.isInstanceOf[RedTree[_, _]] + private transparent inline def isRedTree(tree: Tree[_, _] | Null) = + (tree != null) && tree.isInstanceOf[RedTree[_, _]] def foo[A, B](tree: Tree[A, B] | Null): Unit = { if (isRedTree(tree)) { diff --git a/tests/neg/i7078.scala b/tests/neg/i7078.scala index d49f8a8fee51..9ca3a007c191 100644 --- a/tests/neg/i7078.scala +++ b/tests/neg/i7078.scala @@ -1,7 +1,7 @@ trait A class B extends A -given g1 as _ <: A = B() // error: `_ <:' is only allowed for given with `inline' modifier // error +given g1 as _ <: A = B() // error: `_ <:' is only allowed for given with `inline' modifier inline given g2 as _ <: A: // error: `=' expected def foo = 2 diff --git a/tests/pos/inline-rewrite.scala b/tests/pos/inline-rewrite.scala index 3214fa6ad1cc..ce7f0de4de9f 100644 --- a/tests/pos/inline-rewrite.scala +++ b/tests/pos/inline-rewrite.scala @@ -1,6 +1,6 @@ object Test { - inline def f(x: Int) = inline x match { + transparent inline def f(x: Int) = inline x match { case 1 => "a" case 2 => 22 } @@ -9,7 +9,7 @@ object Test { val y = f(2) val yc: Int = y - inline def g(x: Any) = inline x match { + transparent inline def g(x: Any) = inline x match { case x: String => (x, x) case x: Double => x } diff --git a/tests/run-macros/i7887/Macro_1.scala b/tests/run-macros/i7887/Macro_1.scala index be293fdd76ca..74349c8364cb 100644 --- a/tests/run-macros/i7887/Macro_1.scala +++ b/tests/run-macros/i7887/Macro_1.scala @@ -12,6 +12,6 @@ def myMacroImpl(a: quoted.Expr[_])(using qctx: quoted.QuoteContext) = { } -inline def myMacro(a: => Any) = ${ +inline transparent def myMacro(a: => Any) = ${ myMacroImpl('a) } From 5b31ecc09b67b0a53f3447c68fd221b4442f312b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 29 Mar 2020 13:42:43 +0200 Subject: [PATCH 2/8] Add tests --- .../src/dotty/tools/dotc/ast/Desugar.scala | 8 +--- tests/neg/specializing-inline.scala | 2 +- tests/neg/transparent-inline.scala | 37 +++++++++++++++++++ tests/pos/transparent-inline.scala | 4 ++ 4 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 tests/neg/transparent-inline.scala create mode 100644 tests/pos/transparent-inline.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 2d8d09a6340d..128d7710a04f 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -203,13 +203,7 @@ object desugar { * def f$default$1[T] = 1 * def f$default$2[T](x: Int) = x + "m" * - * 3. Convert <: T to : T in specializing inline methods. E.g. - * - * inline def f(x: Boolean) <: Any = if (x) 1 else "" - * ==> - * inline def f(x: Boolean): Any = if (x) 1 else "" - * - * 4. Upcast non-specializing inline methods. E.g. + * 3. Upcast non-specializing inline methods. E.g. * * inline def f(x: Boolean): Any = if (x) 1 else "" * ==> diff --git a/tests/neg/specializing-inline.scala b/tests/neg/specializing-inline.scala index 450c24708b4d..424ff500b6c2 100644 --- a/tests/neg/specializing-inline.scala +++ b/tests/neg/specializing-inline.scala @@ -2,7 +2,7 @@ object Test { inline def h(x: Boolean) = if (x) 1 else "" val z = h(true) - val zc: Int = z + val zc: Int = z // error inline def g <: Any = 1 val y = g diff --git a/tests/neg/transparent-inline.scala b/tests/neg/transparent-inline.scala new file mode 100644 index 000000000000..a5158d9b8a27 --- /dev/null +++ b/tests/neg/transparent-inline.scala @@ -0,0 +1,37 @@ +transparent def bar: Any = 2 // error: transparent can be used only with inline + +object test1: + + def x: Int = baz(true) // error: type mismatch + inline def baz(x: Boolean): Any = + if x then 1 else "" + inline def bam(x: Boolean): Any = + if x then 1 else "" + def y: Int = bam(true) // error: type mismatch + +object test2: + + def x: 1 = baz(true) // OK + transparent inline def baz(x: Boolean) = + if x then 1 else "" + transparent inline def bam(x: Boolean) = + if x then 1 else "" + def y: 1 = bam(true) // OK + +object test3: + + def x: Int = baz(true) // error: type mismatch + inline def baz(x: Boolean) = + if x then 1 else "" + inline def bam(x: Boolean) = + if x then 1 else "" + def y: Int = bam(true) // error: type mismatch + +object test4: + + def x: 1 = baz(true) // OK + transparent inline def baz(x: Boolean): Any = + if x then 1 else "" + transparent inline def bam(x: Boolean): Any = + if x then 1 else "" + def y: 1 = bam(true) // OK diff --git a/tests/pos/transparent-inline.scala b/tests/pos/transparent-inline.scala new file mode 100644 index 000000000000..bffebddd6bb2 --- /dev/null +++ b/tests/pos/transparent-inline.scala @@ -0,0 +1,4 @@ +inline def foo: Any = 1 +inline transparent def bar: Any = 2 +val x: 2 = bar + From eedcb8769d32f13c61031dc5c282fb8ba80cd777 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 29 Mar 2020 13:47:01 +0200 Subject: [PATCH 3/8] Change more tests and docs --- .../tools/backend/jvm/InlineBytecodeTests.scala | 16 ++++++++-------- docs/docs/reference/metaprogramming/macros.md | 2 +- tests/disabled/pos/quote-whitebox/Macro_1.scala | 2 +- tests/invalid/run/typelevel-patmat.scala | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala index c70826a71934..501cc35a278b 100644 --- a/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala @@ -75,7 +75,7 @@ class InlineBytecodeTests extends DottyBytecodeTest { @Test def i4947 = { val source = """class Foo { - | inline def track[T](inline f: T) <: T = { + | transparent inline def track[T](inline f: T): T = { | foo("tracking") // line 3 | f // line 4 | } @@ -134,11 +134,11 @@ class InlineBytecodeTests extends DottyBytecodeTest { @Test def i4947b = { val source = """class Foo { - | inline def track2[T](inline f: T) <: T = { + | transparent inline def track2[T](inline f: T): T = { | foo("tracking2") // line 3 | f // line 4 | } - | inline def track[T](inline f: T) <: T = { + | transparent inline def track[T](inline f: T): T = { | foo("tracking") // line 7 | track2 { // line 8 | f // line 9 @@ -194,11 +194,11 @@ class InlineBytecodeTests extends DottyBytecodeTest { @Test def i4947c = { val source = """class Foo { - | inline def track2[T](inline f: T) <: T = { + | transparent inline def track2[T](inline f: T): T = { | foo("tracking2") // line 3 | f // line 4 | } - | inline def track[T](inline f: T) <: T = { + | transparent inline def track[T](inline f: T): T = { | track2 { // line 7 | foo("fgh") // line 8 | f // line 9 @@ -254,11 +254,11 @@ class InlineBytecodeTests extends DottyBytecodeTest { @Test def i4947d = { val source = """class Foo { - | inline def track2[T](inline f: T) <: T = { + | transparent inline def track2[T](inline f: T): T = { | foo("tracking2") // line 3 | f // line 4 | } - | inline def track[T](inline f: T) <: T = { + | transparent inline def track[T](inline f: T): T = { | track2 { // line 7 | track2 { // line 8 | f // line 9 @@ -319,7 +319,7 @@ class InlineBytecodeTests extends DottyBytecodeTest { | def test: Int = { | var a = 10 | - | inline def f() = { + | transparent inline def f() = { | a += 1 | } | diff --git a/docs/docs/reference/metaprogramming/macros.md b/docs/docs/reference/metaprogramming/macros.md index 8d93d24927ee..3907cab24b51 100644 --- a/docs/docs/reference/metaprogramming/macros.md +++ b/docs/docs/reference/metaprogramming/macros.md @@ -601,7 +601,7 @@ inline method that can calculate either a value of type `Int` or a value of type `String`. ```scala -inline def defaultOf(inline str: String) <: Any = ${ defaultOfImpl('str) } +transparent inline def defaultOf(inline str: String) = ${ defaultOfImpl('str) } def defaultOfImpl(strExpr: Expr[String])(using QuoteContext): Expr[Any] = strExpr.unliftOrError match diff --git a/tests/disabled/pos/quote-whitebox/Macro_1.scala b/tests/disabled/pos/quote-whitebox/Macro_1.scala index 7f15fc13ae93..f0af21382772 100644 --- a/tests/disabled/pos/quote-whitebox/Macro_1.scala +++ b/tests/disabled/pos/quote-whitebox/Macro_1.scala @@ -3,7 +3,7 @@ import scala.quoted._ object Macro { - inline def charOrString(inline str: String) <: Char | String = ${ impl(str) } + transparent inline def charOrString(inline str: String): Char | String = ${ impl(str) } def impl(str: String) = if (str.length == 1) Expr(str.charAt(0)) else Expr(str) diff --git a/tests/invalid/run/typelevel-patmat.scala b/tests/invalid/run/typelevel-patmat.scala index 62c5225479e1..9f463638c125 100644 --- a/tests/invalid/run/typelevel-patmat.scala +++ b/tests/invalid/run/typelevel-patmat.scala @@ -21,7 +21,7 @@ object Test extends App { type HNil = HNil.type type Z = Z.type - inline def ToNat(inline n: Int) <: Typed[Nat] = + transparent inline def ToNat(inline n: Int): Typed[Nat] = if n == 0 then Typed(Z) else Typed(S(ToNat(n - 1).value)) @@ -35,7 +35,7 @@ object Test extends App { println(x1) println(x2) - inline def toInt(n: Nat) <: Int = inline n match { + transparent inline def toInt(n: Nat): Int = inline n match { case Z => 0 case S(n1) => toInt(n1) + 1 } From 8f4c8ed6aafa4b609f59ce88f601b17e77e1c125 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 29 Mar 2020 14:57:57 +0200 Subject: [PATCH 4/8] Fix another InlineBytecodeTest --- compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala index 501cc35a278b..1d6ad20c5a1a 100644 --- a/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/InlineBytecodeTests.scala @@ -356,7 +356,7 @@ class InlineBytecodeTests extends DottyBytecodeTest { val source = """class Test: | given Int = 0 | def f(): Int ?=> Boolean = true : (Int ?=> Boolean) - | inline def g(): Int ?=> Boolean = true + | transparent inline def g(): Int ?=> Boolean = true | def test = g() """.stripMargin From 366137defa926b06323e37d19b2fefd6c6bcf817 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 29 Mar 2020 15:00:40 +0200 Subject: [PATCH 5/8] Mark regions in parser that support old whitebox syntax --- compiler/src/dotty/tools/dotc/parsing/Parsers.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index ed7975d5a8f1..093224b2e5c5 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -33,6 +33,8 @@ object Parsers { import reporting.Message import reporting.messages._ + val AllowOldWhiteboxSyntax = true + case class OpInfo(operand: Tree, operator: Ident, offset: Offset) class ParensCounters { @@ -3253,7 +3255,7 @@ object Parsers { case rparamss => leadingVparamss ::: rparamss var tpt = fromWithinReturnType { - if in.token == SUBTYPE && mods.is(Inline) then + if in.token == SUBTYPE && mods.is(Inline) && AllowOldWhiteboxSyntax then in.nextToken() mods1 = addMod(mods1, Mod.Transparent()) toplevelTyp() @@ -3522,7 +3524,7 @@ object Parsers { accept(EQUALS) mods1 |= Final DefDef(name, tparams, vparamss, tpt, subExpr()) - if in.token == USCORE then + if in.token == USCORE && AllowOldWhiteboxSyntax then if !mods.is(Inline) then syntaxError("`_ <:` is only allowed for given with `inline` modifier") in.nextToken() From f3e01706bfc59d0808536e826f0e4e47c1114ce2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Mar 2020 21:28:32 +0200 Subject: [PATCH 6/8] Update another doc page This was missed before. --- docs/docs/reference/metaprogramming/erased-terms.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/reference/metaprogramming/erased-terms.md b/docs/docs/reference/metaprogramming/erased-terms.md index 80347677eedd..2313538c570a 100644 --- a/docs/docs/reference/metaprogramming/erased-terms.md +++ b/docs/docs/reference/metaprogramming/erased-terms.md @@ -171,11 +171,11 @@ final class On extends State final class Off extends State class Machine[S <: State] { - inline def turnOn() <: Machine[On] = inline erasedValue[S] match { + transparent inline def turnOn(): Machine[On] = inline erasedValue[S] match { case _: Off => new Machine[On] case _: On => error("Turning on an already turned on machine") } - inline def turnOff() <: Machine[Off] = inline erasedValue[S] match { + transparent inline def turnOff(): Machine[Off] = inline erasedValue[S] match { case _: On => new Machine[Off] case _: Off => error("Turning off an already turned off machine") } From f77b3a903e40593d7819fb5a07b15bf97a59870a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Mar 2020 21:35:23 +0200 Subject: [PATCH 7/8] Address review comments --- compiler/src/dotty/tools/dotc/ast/Desugar.scala | 6 ------ compiler/src/dotty/tools/dotc/typer/Namer.scala | 3 +-- compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala | 4 ++-- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 128d7710a04f..124a23e7069c 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -202,12 +202,6 @@ object desugar { * def f[T](x: Int)(y: String)(implicit evidence$0: B[T]) = ... * def f$default$1[T] = 1 * def f$default$2[T](x: Int) = x + "m" - * - * 3. Upcast non-specializing inline methods. E.g. - * - * inline def f(x: Boolean): Any = if (x) 1 else "" - * ==> - * inline def f(x: Boolean): Any = (if (x) 1 else ""): Any */ private def defDef(meth0: DefDef, isPrimaryConstructor: Boolean = false)(implicit ctx: Context): Tree = { val meth @ DefDef(_, tparams, vparamss, tpt, rhs) = transformQuotedPatternName(meth0) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 1116c69515a5..71f0d1e03559 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -865,8 +865,7 @@ class Namer { typer: Typer => case original: untpd.DefDef if sym.isInlineMethod => def rhsToInline(using Context): tpd.Tree = val mdef = typedAheadExpr(original).asInstanceOf[tpd.DefDef] - if original.mods.hasMod(classOf[untpd.Mod.Transparent]) then mdef.rhs - else tpd.Typed(mdef.rhs, mdef.tpt) + PrepareInlineable.wrapRHS(original, mdef.tpt, mdef.rhs) PrepareInlineable.registerInlineInfo(sym, rhsToInline)(localContext(sym)) case _ => } diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala index 9f976768ecbd..d19e4f5b6f0d 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala @@ -206,7 +206,7 @@ object PrepareInlineable { /** The type ascription `rhs: tpt`, unless `original` is `transparent`. */ def wrapRHS(original: untpd.DefDef, tpt: Tree, rhs: Tree)(using Context): Tree = - if original.mods.mods.exists(_.isInstanceOf[untpd.Mod.Transparent]) then rhs + if original.mods.hasMod(classOf[untpd.Mod.Transparent]) then rhs else Typed(rhs, tpt) /** Register inline info for given inlineable method `sym`. @@ -228,7 +228,7 @@ object PrepareInlineable { inlined.updateAnnotation(LazyBodyAnnotation { given ctx as Context = inlineCtx val initialErrorCount = ctx.reporter.errorCount - var inlinedBody = treeExpr(using ctx) + var inlinedBody = treeExpr if (ctx.reporter.errorCount == initialErrorCount) { inlinedBody = ctx.compilationUnit.inlineAccessors.makeInlineable(inlinedBody) checkInlineMethod(inlined, inlinedBody) From bb01e6f81f8e7e7738e9671df651de237a2f25ff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 30 Mar 2020 21:37:41 +0200 Subject: [PATCH 8/8] Add test for whitebox givens --- tests/pos/whitebox-given.scala | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tests/pos/whitebox-given.scala diff --git a/tests/pos/whitebox-given.scala b/tests/pos/whitebox-given.scala new file mode 100644 index 000000000000..2c434d36319d --- /dev/null +++ b/tests/pos/whitebox-given.scala @@ -0,0 +1,2 @@ +transparent inline given Int = 1 +val x: 1 = summon[Int]