diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index bb8da0d59d72..78b8cba20415 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -196,7 +196,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { case class Inline()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Inline) - case class Transparent()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.EmptyFlags) + case class Transparent()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Transparent) } /** Modifiers and annotations for definitions diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 1bb664b63673..783041921615 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -347,8 +347,11 @@ object Flags { /** Labeled with `erased` modifier (erased value) */ val (_, Erased @ _, _) = newFlags(42, "erased") + /** Labelled with `transparent` modifier */ + val (Transparent @ _, _, _) = newFlags(43, "transparent") + /** An opaque type alias or a class containing one */ - val (Opaque @ _, _, _) = newFlags(43, "opaque") + val (Opaque @ _, _, _) = newFlags(44, "opaque") // ------------ Flags following this one are not pickled ---------------------------------- diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 98d231de643c..1141a1c2dd31 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -865,8 +865,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if (rawFlags.is(Param)) flagMask = flagMask &~ Given val flags = rawFlags & flagMask 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/tastyreflect/ReflectionCompilerInterface.scala b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala index c2bb1173aced..e733dd2e04c5 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala @@ -1847,6 +1847,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend def Flags_Lazy: Flags = core.Flags.Lazy def Flags_Override: Flags = core.Flags.Override def Flags_Inline: Flags = core.Flags.Inline + def Flags_Transparent: Flags = core.Flags.Transparent def Flags_Macro: Flags = core.Flags.Macro def Flags_Static: Flags = core.Flags.JavaStatic def Flags_JavaDefined: Flags = core.Flags.JavaDefined diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 14584820ee3e..35c63bdd14c6 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -882,18 +882,27 @@ trait Checking { } } - /** Check that `tree` can be right hand-side or argument to `inline` value or parameter. */ - def checkInlineConformant(tpt: Tree, tree: Tree, sym: Symbol)(using Context): Unit = { + /** Check that `tree` can be right hand-side or argument to `inline` value. + * Returns the type (tpt) of the val, possibly adapted. + */ + def checkInlineConformant(tpt: Tree, tree: Tree, sym: Symbol)(using Context): Tree = { if sym.is(Inline, butNot = DeferredOrTermParamOrAccessor) && !ctx.erasedTypes && !ctx.inInlineMethod then // final vals can be marked inline even if they're not pure, see Typer#patchFinalVals val purityLevel = if (sym.is(Final)) Idempotent else Pure - tpt.tpe.widenTermRefExpr.dealias match - case tp: ConstantType => - if !(exprPurity(tree) >= purityLevel) then - ctx.error(em"inline value must be pure", tree.sourcePos) + if !(exprPurity(tree) >= purityLevel) then + ctx.error("inline value must be pure", tree.sourcePos) + tpt + else tpt.tpe.widenTermRefExpr.dealias match + case tp: ConstantType => tpt case _ => - val pos = if tpt.span.isZeroExtent then tree.sourcePos else tpt.sourcePos - ctx.error(em"inline value must have a literal constant type", pos) + if sym.is(Transparent) then + TypeTree(tree.tpe).withSpan(tpt.span) + else + val pos = if tpt.span.isZeroExtent then tree.sourcePos else tpt.sourcePos + ctx.error("inline value must have a literal constant type", pos) + tpt + else + tpt } /** A hook to exclude selected symbols from double declaration check */ @@ -1222,7 +1231,7 @@ trait NoChecking extends ReChecking { override def checkImplicitConversionDefOK(sym: Symbol)(using Context): Unit = () override def checkImplicitConversionUseOK(sym: Symbol, posd: Positioned)(using Context): Unit = () override def checkFeasibleParent(tp: Type, pos: SourcePosition, where: => String = "")(using Context): Type = tp - override def checkInlineConformant(tpt: Tree, tree: Tree, sym: Symbol)(using Context): Unit = () + override def checkInlineConformant(tpt: Tree, tree: Tree, sym: Symbol)(using Context): Tree = tpt override def checkNoAlphaConflict(stats: List[Tree])(using Context): Unit = () override def checkParentCall(call: Tree, caller: ClassSymbol)(using Context): Unit = () override def checkSimpleKinded(tpt: Tree)(using Context): Tree = tpt diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 9c6e83af0522..3a5e6a8529d3 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -866,7 +866,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] - PrepareInlineable.wrapRHS(original, mdef.tpt, mdef.rhs) + PrepareInlineable.wrapRHS(sym, mdef.tpt, mdef.rhs) PrepareInlineable.registerInlineInfo(sym, rhsToInline)(using localContext(sym)) case _ => } diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala index cf7286be340f..352e4134fe5f 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala @@ -204,9 +204,9 @@ object PrepareInlineable { def isLocal(sym: Symbol, inlineMethod: Symbol)(using 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.hasMod(classOf[untpd.Mod.Transparent]) then rhs + /** The type ascription `rhs: tpt`, unless `sym` is `transparent`. */ + def wrapRHS(sym: Symbol, tpt: Tree, rhs: Tree)(using Context): Tree = + if sym.is(Transparent) then rhs else Typed(rhs, tpt) /** Register inline info for given inlineable method `sym`. diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 9c4ba0b47128..ffbf7cfce7d1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1801,9 +1801,9 @@ class Typer extends Namer case rhs @ Ident(nme.WILDCARD) => rhs withType tpt1.tpe case rhs => typedExpr(rhs, tpt1.tpe.widenExpr) } - val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt1, rhs1), sym) + val tpt2 = checkInlineConformant(tpt1, rhs1, sym) + val vdef1 = assignType(cpy.ValDef(vdef)(name, tpt2, rhs1), sym) checkSignatureRepeatedParam(sym) - checkInlineConformant(tpt1, rhs1, sym) patchFinalVals(vdef1) vdef1.setDefTree } @@ -1865,7 +1865,7 @@ class Typer extends Namer if (sym.isInlineMethod) rhsCtx.addMode(Mode.InlineableBody) val rhs1 = typedExpr(ddef.rhs, tpt1.tpe.widenExpr)(using rhsCtx) - val rhsToInline = PrepareInlineable.wrapRHS(ddef, tpt1, rhs1) + val rhsToInline = PrepareInlineable.wrapRHS(sym, tpt1, rhs1) if (sym.isInlineMethod) PrepareInlineable.registerInlineInfo(sym, rhsToInline) diff --git a/docs/docs/reference/metaprogramming/inline.md b/docs/docs/reference/metaprogramming/inline.md index 5e79fabfd2bf..b26c3eac6c7c 100644 --- a/docs/docs/reference/metaprogramming/inline.md +++ b/docs/docs/reference/metaprogramming/inline.md @@ -235,18 +235,6 @@ inline val four = 4 inline val four: 4 = 4 ``` -It is also possible to have inline vals of types that do not have a syntax, such as `Short(4)`. - -```scala -trait InlineConstants { - inline val myShort: Short -} - -object Constants extends InlineConstants { - inline val myShort/*: Short(4)*/ = 4 -} -``` - ## Transparent Inline Methods Inline methods can additionally be declared `transparent`. @@ -287,6 +275,15 @@ transparent inline def zero(): Int = 0 val one: 1 = zero() + 1 ``` +All `inline val`s effectively are transparent. This allows any `inline val` to be costant folded. + +```scala +inline val one: Int = 1 + +val two: 2 = one + one +``` + + ## Inline Conditionals If the condition of an if-then-else expressions is a constant expression then it simplifies to diff --git a/library/src/scala/tasty/Reflection.scala b/library/src/scala/tasty/Reflection.scala index d3f6e63719b7..0cbc8f57cf64 100644 --- a/library/src/scala/tasty/Reflection.scala +++ b/library/src/scala/tasty/Reflection.scala @@ -2607,6 +2607,9 @@ class Reflection(private[scala] val internal: CompilerInterface) { self => /** Is this symbol `inline` */ def Inline: Flags = internal.Flags_Inline + /** Is this symbol a `transparent` */ + def Transparent: Flags = internal.Flags_Transparent + /** Is this symbol marked as a macro. An inline method containing toplevel splices */ def Macro: Flags = internal.Flags_Macro diff --git a/library/src/scala/tasty/reflect/CompilerInterface.scala b/library/src/scala/tasty/reflect/CompilerInterface.scala index 93bffe1b72cf..c602a8e8d919 100644 --- a/library/src/scala/tasty/reflect/CompilerInterface.scala +++ b/library/src/scala/tasty/reflect/CompilerInterface.scala @@ -1397,6 +1397,7 @@ trait CompilerInterface { def Flags_Lazy: Flags def Flags_Override: Flags def Flags_Inline: Flags + def Flags_Transparent: Flags def Flags_Macro: Flags def Flags_Static: Flags def Flags_JavaDefined: Flags diff --git a/library/src/scala/tasty/reflect/SourceCodePrinter.scala b/library/src/scala/tasty/reflect/SourceCodePrinter.scala index 75da3d55750b..504e6cc2e37a 100644 --- a/library/src/scala/tasty/reflect/SourceCodePrinter.scala +++ b/library/src/scala/tasty/reflect/SourceCodePrinter.scala @@ -34,6 +34,7 @@ class SourceCodePrinter[R <: Reflection & Singleton](val tasty: R)(syntaxHighlig if (flags.is(Flags.Lazy)) flagList += "lazy" if (flags.is(Flags.Override)) flagList += "override" if (flags.is(Flags.Inline)) flagList += "inline" + if (flags.is(Flags.Transparent)) flagList += "transparent" if (flags.is(Flags.Macro)) flagList += "macro" if (flags.is(Flags.JavaDefined)) flagList += "javaDefined" if (flags.is(Flags.Static)) flagList += "javaStatic" diff --git a/tasty/src/dotty/tools/tasty/TastyFormat.scala b/tasty/src/dotty/tools/tasty/TastyFormat.scala index f330254e4ff2..6b8a71b28328 100644 --- a/tasty/src/dotty/tools/tasty/TastyFormat.scala +++ b/tasty/src/dotty/tools/tasty/TastyFormat.scala @@ -248,7 +248,7 @@ Standard Section: "Comments" Comment* object TastyFormat { final val header: Array[Int] = Array(0x5C, 0xA1, 0xAB, 0x1F) - val MajorVersion: Int = 22 + val MajorVersion: Int = 23 val MinorVersion: Int = 0 /** Tags used to serialize names, should update [[nameTagToString]] if a new constant is added */ diff --git a/tests/pos/inline-vals.scala b/tests/pos/inline-vals.scala index b6d9cc44bdb0..50b08487028d 100644 --- a/tests/pos/inline-vals.scala +++ b/tests/pos/inline-vals.scala @@ -21,3 +21,15 @@ object Constants extends InlineConstants { inline val myInlinedChar = 'a' inline val myInlinedString = "abc" } + +class Constants2 { + transparent inline val myInlinedBoolean: Boolean = true + transparent inline val myInlinedByte: Byte = 1 + transparent inline val myInlinedShort: Short = 2 + transparent inline val myInlinedInt: Int = 3 + transparent inline val myInlinedLong: Long = 4 + transparent inline val myInlinedFloat: Float = 5 + transparent inline val myInlinedDouble: Double = 6 + transparent inline val myInlinedChar: Char = 'a' + transparent inline val myInlinedString: String = "abc" +}